Skip to content

Commit 9a82da2

Browse files
authored
Merge pull request #232 from SDWebImage/fix_backport_ios13
Fix iOS 13 compatibility && State changes
2 parents 83d46c0 + 5a5690e commit 9a82da2

File tree

12 files changed

+423
-171
lines changed

12 files changed

+423
-171
lines changed

Example/SDWebImageSwiftUI.xcodeproj/project.pbxproj

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
322E0E2228D332130003A55F /* Images.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 322E0DF228D331A20003A55F /* Images.bundle */; };
3535
322E0E2328D332130003A55F /* Images.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 322E0DF228D331A20003A55F /* Images.bundle */; };
3636
326B0D712345C01900D28269 /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B0D702345C01900D28269 /* DetailView.swift */; };
37+
327B90F228DC4EBB003E8BD9 /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = 327B90F128DC4EBB003E8BD9 /* ViewInspector */; };
38+
327B90F428DC4EC0003E8BD9 /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = 327B90F328DC4EC0003E8BD9 /* ViewInspector */; };
3739
32DCFE9528D333E8001A17BF /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = 32DCFE9428D333E8001A17BF /* ViewInspector */; };
3840
32E5290C2348A0C700EA46FF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32E5290B2348A0C700EA46FF /* AppDelegate.swift */; };
3941
32E529102348A0C900EA46FF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 32E5290F2348A0C900EA46FF /* Assets.xcassets */; };
@@ -220,6 +222,7 @@
220222
buildActionMask = 2147483647;
221223
files = (
222224
833A61715BAAB31702D867CC /* Pods_SDWebImageSwiftUITests_macOS.framework in Frameworks */,
225+
327B90F228DC4EBB003E8BD9 /* ViewInspector in Frameworks */,
223226
);
224227
runOnlyForDeploymentPostprocessing = 0;
225228
};
@@ -228,6 +231,7 @@
228231
buildActionMask = 2147483647;
229232
files = (
230233
2E3D81A12C757E01A3C420F2 /* Pods_SDWebImageSwiftUITests_tvOS.framework in Frameworks */,
234+
327B90F428DC4EC0003E8BD9 /* ViewInspector in Frameworks */,
231235
);
232236
runOnlyForDeploymentPostprocessing = 0;
233237
};
@@ -517,9 +521,13 @@
517521
buildRules = (
518522
);
519523
dependencies = (
524+
327B90EE28DC4EAA003E8BD9 /* PBXTargetDependency */,
520525
322E0E0728D331F00003A55F /* PBXTargetDependency */,
521526
);
522527
name = "SDWebImageSwiftUITests macOS";
528+
packageProductDependencies = (
529+
327B90F128DC4EBB003E8BD9 /* ViewInspector */,
530+
);
523531
productName = "SDWebImageSwiftUITests macOS";
524532
productReference = 322E0E0228D331F00003A55F /* SDWebImageSwiftUITests macOS.xctest */;
525533
productType = "com.apple.product-type.bundle.unit-test";
@@ -537,9 +545,13 @@
537545
buildRules = (
538546
);
539547
dependencies = (
548+
327B90F028DC4EAE003E8BD9 /* PBXTargetDependency */,
540549
322E0E1428D332050003A55F /* PBXTargetDependency */,
541550
);
542551
name = "SDWebImageSwiftUITests tvOS";
552+
packageProductDependencies = (
553+
327B90F328DC4EC0003E8BD9 /* ViewInspector */,
554+
);
543555
productName = "SDWebImageSwiftUITests tvOS";
544556
productReference = 322E0E0F28D332050003A55F /* SDWebImageSwiftUITests tvOS.xctest */;
545557
productType = "com.apple.product-type.bundle.unit-test";
@@ -698,7 +710,7 @@
698710
);
699711
mainGroup = 607FACC71AFB9204008FA782;
700712
packageReferences = (
701-
32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector.git" */,
713+
32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector" */,
702714
);
703715
productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
704716
projectDirPath = "";
@@ -1225,6 +1237,14 @@
12251237
target = 32E5291F2348A0D300EA46FF /* SDWebImageSwiftUIDemo-tvOS */;
12261238
targetProxy = 322E0E1328D332050003A55F /* PBXContainerItemProxy */;
12271239
};
1240+
327B90EE28DC4EAA003E8BD9 /* PBXTargetDependency */ = {
1241+
isa = PBXTargetDependency;
1242+
productRef = 327B90ED28DC4EAA003E8BD9 /* ViewInspector */;
1243+
};
1244+
327B90F028DC4EAE003E8BD9 /* PBXTargetDependency */ = {
1245+
isa = PBXTargetDependency;
1246+
productRef = 327B90EF28DC4EAE003E8BD9 /* ViewInspector */;
1247+
};
12281248
32DCFE9728D333F1001A17BF /* PBXTargetDependency */ = {
12291249
isa = PBXTargetDependency;
12301250
productRef = 32DCFE9628D333F1001A17BF /* ViewInspector */;
@@ -2044,7 +2064,7 @@
20442064
/* End XCConfigurationList section */
20452065

20462066
/* Begin XCRemoteSwiftPackageReference section */
2047-
32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector.git" */ = {
2067+
32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector" */ = {
20482068
isa = XCRemoteSwiftPackageReference;
20492069
repositoryURL = "https://github.com/nalexn/ViewInspector.git";
20502070
requirement = {
@@ -2055,14 +2075,34 @@
20552075
/* End XCRemoteSwiftPackageReference section */
20562076

20572077
/* Begin XCSwiftPackageProductDependency section */
2078+
327B90ED28DC4EAA003E8BD9 /* ViewInspector */ = {
2079+
isa = XCSwiftPackageProductDependency;
2080+
package = 32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector" */;
2081+
productName = ViewInspector;
2082+
};
2083+
327B90EF28DC4EAE003E8BD9 /* ViewInspector */ = {
2084+
isa = XCSwiftPackageProductDependency;
2085+
package = 32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector" */;
2086+
productName = ViewInspector;
2087+
};
2088+
327B90F128DC4EBB003E8BD9 /* ViewInspector */ = {
2089+
isa = XCSwiftPackageProductDependency;
2090+
package = 32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector" */;
2091+
productName = ViewInspector;
2092+
};
2093+
327B90F328DC4EC0003E8BD9 /* ViewInspector */ = {
2094+
isa = XCSwiftPackageProductDependency;
2095+
package = 32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector" */;
2096+
productName = ViewInspector;
2097+
};
20582098
32DCFE9428D333E8001A17BF /* ViewInspector */ = {
20592099
isa = XCSwiftPackageProductDependency;
2060-
package = 32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector.git" */;
2100+
package = 32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector" */;
20612101
productName = ViewInspector;
20622102
};
20632103
32DCFE9628D333F1001A17BF /* ViewInspector */ = {
20642104
isa = XCSwiftPackageProductDependency;
2065-
package = 32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector.git" */;
2105+
package = 32DCFE8D28D333B0001A17BF /* XCRemoteSwiftPackageReference "ViewInspector" */;
20662106
productName = ViewInspector;
20672107
};
20682108
/* End XCSwiftPackageProductDependency section */

Example/SDWebImageSwiftUIDemo/ContentView.swift

Lines changed: 100 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,52 @@ extension Indicator where T == ProgressView<EmptyView, EmptyView> {
3434
}
3535
#endif
3636

37+
// Test Switching url using @State
38+
struct ContentView2: View {
39+
@State var imageURLs = [
40+
"https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_1.jpg",
41+
"https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_2.jpg",
42+
"http://assets.sbnation.com/assets/2512203/dogflops.gif",
43+
"https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif"
44+
]
45+
@State var animated: Bool = false // You can change between WebImage/AnimatedImage
46+
@State var imageIndex : Int = 0
47+
var body: some View {
48+
Group {
49+
Text("\(animated ? "AnimatedImage" : "WebImage") - \((imageURLs[imageIndex] as NSString).lastPathComponent)")
50+
Spacer()
51+
#if os(watchOS)
52+
WebImage(url:URL(string: imageURLs[imageIndex]))
53+
.resizable()
54+
.aspectRatio(contentMode: .fit)
55+
#else
56+
if self.animated {
57+
AnimatedImage(url:URL(string: imageURLs[imageIndex]))
58+
.resizable()
59+
.aspectRatio(contentMode: .fit)
60+
} else {
61+
WebImage(url:URL(string: imageURLs[imageIndex]))
62+
.resizable()
63+
.aspectRatio(contentMode: .fit)
64+
}
65+
#endif
66+
Spacer()
67+
Button("Next") {
68+
if imageIndex + 1 >= imageURLs.count {
69+
imageIndex = 0
70+
} else {
71+
imageIndex += 1
72+
}
73+
}
74+
Button("Reload") {
75+
SDImageCache.shared.clearMemory()
76+
SDImageCache.shared.clearDisk(onCompletion: nil)
77+
}
78+
Toggle("Switch", isOn: $animated)
79+
}
80+
}
81+
}
82+
3783
struct ContentView: View {
3884
@State var imageURLs = [
3985
"http://assets.sbnation.com/assets/2512203/dogflops.gif",
@@ -58,6 +104,58 @@ struct ContentView: View {
58104
@State var animated: Bool = false // You can change between WebImage/AnimatedImage
59105
@EnvironmentObject var settings: UserSettings
60106

107+
// Used to avoid https://twitter.com/fatbobman/status/1572507700436807683?s=20&t=5rfj6BUza5Jii-ynQatCFA
108+
struct ItemView: View {
109+
@Binding var animated: Bool
110+
@State var url: String
111+
var body: some View {
112+
NavigationLink(destination: DetailView(url: url, animated: self.animated)) {
113+
HStack {
114+
if self.animated {
115+
#if os(macOS) || os(iOS) || os(tvOS)
116+
AnimatedImage(url: URL(string:url), isAnimating: .constant(true))
117+
.onViewUpdate { view, context in
118+
#if os(macOS)
119+
view.toolTip = url
120+
#endif
121+
}
122+
.indicator(SDWebImageActivityIndicator.medium)
123+
/**
124+
.placeholder(UIImage(systemName: "photo"))
125+
*/
126+
.transition(.fade)
127+
.resizable()
128+
.scaledToFit()
129+
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
130+
#else
131+
WebImage(url: URL(string:url), isAnimating: self.$animated)
132+
.resizable()
133+
.indicator(.activity)
134+
.transition(.fade(duration: 0.5))
135+
.scaledToFit()
136+
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
137+
#endif
138+
} else {
139+
WebImage(url: URL(string:url), isAnimating: .constant(true))
140+
.resizable()
141+
/**
142+
.placeholder {
143+
Image(systemName: "photo")
144+
}
145+
*/
146+
.indicator(.activity)
147+
.transition(.fade(duration: 0.5))
148+
.scaledToFit()
149+
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
150+
}
151+
Text((url as NSString).lastPathComponent)
152+
}
153+
}
154+
.buttonStyle(PlainButtonStyle())
155+
}
156+
}
157+
158+
61159
var body: some View {
62160
#if os(iOS)
63161
return NavigationView {
@@ -119,49 +217,8 @@ struct ContentView: View {
119217
func contentView() -> some View {
120218
List {
121219
ForEach(imageURLs, id: \.self) { url in
122-
NavigationLink(destination: DetailView(url: url, animated: self.animated)) {
123-
HStack {
124-
if self.animated {
125-
#if os(macOS) || os(iOS) || os(tvOS)
126-
AnimatedImage(url: URL(string:url), isAnimating: .constant(true))
127-
.onViewUpdate { view, context in
128-
#if os(macOS)
129-
view.toolTip = url
130-
#endif
131-
}
132-
.indicator(SDWebImageActivityIndicator.medium)
133-
/**
134-
.placeholder(UIImage(systemName: "photo"))
135-
*/
136-
.transition(.fade)
137-
.resizable()
138-
.scaledToFit()
139-
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
140-
#else
141-
WebImage(url: URL(string:url), isAnimating: self.$animated)
142-
.resizable()
143-
.indicator(.activity)
144-
.transition(.fade(duration: 0.5))
145-
.scaledToFit()
146-
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
147-
#endif
148-
} else {
149-
WebImage(url: URL(string:url), isAnimating: .constant(true))
150-
.resizable()
151-
/**
152-
.placeholder {
153-
Image(systemName: "photo")
154-
}
155-
*/
156-
.indicator(.activity)
157-
.transition(.fade(duration: 0.5))
158-
.scaledToFit()
159-
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
160-
}
161-
Text((url as NSString).lastPathComponent)
162-
}
163-
}
164-
.buttonStyle(PlainButtonStyle())
220+
// Must use top level view instead of inlined view structure
221+
ItemView(animated: $animated, url: url)
165222
}
166223
.onDelete { indexSet in
167224
indexSet.forEach { index in

README.md

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ It looks familiar like `SDWebImageManager`, but it's built for SwiftUI world, wh
270270

271271
```swift
272272
struct MyView : View {
273-
@ObservedObject var imageManager: ImageManager
273+
@ObservedObject var imageManager = ImageManager()
274274
var body: some View {
275275
// Your custom complicated view graph
276276
Group {
@@ -281,17 +281,11 @@ struct MyView : View {
281281
}
282282
}
283283
// Trigger image loading when appear
284-
.onAppear { self.imageManager.load() }
284+
.onAppear { self.imageManager.load(url: url) }
285285
// Cancel image loading when disappear
286286
.onDisappear { self.imageManager.cancel() }
287287
}
288288
}
289-
290-
struct MyView_Previews: PreviewProvider {
291-
static var previews: some View {
292-
MyView(imageManager: ImageManager(url: URL(string: "https://via.placeholder.com/200x200.jpg"))
293-
}
294-
}
295289
```
296290

297291
### Customization and configuration setup
@@ -337,6 +331,54 @@ For more information, it's really recommended to check our demo, to learn detail
337331

338332
### Common Problems
339333

334+
#### Using WebImage/AnimatedImage in List/LazyStack/LazyGrid and ForEach
335+
336+
SwiftUI has a known behavior(bug?) when using stateful view in `List/LazyStack/LazyGrid`.
337+
Only the **Top Level** view can hold its own `@State/@StateObject`, but the sub structure will lose state when scroll out of screen.
338+
However, WebImage/Animated is both stateful. To ensure the state keep in sync even when scroll out of screen. you may use some tricks.
339+
340+
See more: https://twitter.com/fatbobman/status/1572507700436807683?s=21&t=z4FkAWTMvjsgL-wKdJGreQ
341+
342+
In short, it's not recommanded to do so:
343+
344+
```swift
345+
struct ContentView {
346+
@State var imageURLs: [String]
347+
var body: some View {
348+
List {
349+
ForEach(imageURLs, id: \.self) { url in
350+
VStack {
351+
WebImage(url) // The top level is `VStack`
352+
}
353+
}
354+
}
355+
}
356+
}
357+
```
358+
359+
instead, using this approach:
360+
361+
```swift
362+
struct ContentView {
363+
struct BodyView {
364+
@State var url: String
365+
var body: some View {
366+
VStack {
367+
WebImage(url)
368+
}
369+
}
370+
}
371+
@State var imageURLs: [String]
372+
var body: some View {
373+
List {
374+
ForEach(imageURLs, id: \.self) { url in
375+
BodyView(url: url)
376+
}
377+
}
378+
}
379+
}
380+
```
381+
340382
#### Using Image/WebImage/AnimatedImage in Button/NavigationLink
341383

342384
SwiftUI's `Button` apply overlay to its content (except `Text`) by default, this is common mistake to write code like this, which cause strange behavior:

0 commit comments

Comments
 (0)