Update the readme about when using in List/LazyStack/LazyGrid

This commit is contained in:
DreamPiggy 2022-09-22 15:03:20 +08:00
parent d18693909b
commit abd9102f6b
3 changed files with 111 additions and 54 deletions

View File

@ -104,6 +104,58 @@ struct ContentView: View {
@State var animated: Bool = false // You can change between WebImage/AnimatedImage
@EnvironmentObject var settings: UserSettings
// Used to avoid https://twitter.com/fatbobman/status/1572507700436807683?s=20&t=5rfj6BUza5Jii-ynQatCFA
struct ItemView: View {
@Binding var animated: Bool
@State var url: String
var body: some View {
NavigationLink(destination: DetailView(url: url, animated: self.animated)) {
HStack {
if self.animated {
#if os(macOS) || os(iOS) || os(tvOS)
AnimatedImage(url: URL(string:url), isAnimating: .constant(true))
.onViewUpdate { view, context in
#if os(macOS)
view.toolTip = url
#endif
}
.indicator(SDWebImageActivityIndicator.medium)
/**
.placeholder(UIImage(systemName: "photo"))
*/
.transition(.fade)
.resizable()
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
#else
WebImage(url: URL(string:url), isAnimating: self.$animated)
.resizable()
.indicator(.activity)
.transition(.fade(duration: 0.5))
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
#endif
} else {
WebImage(url: URL(string:url), isAnimating: .constant(true))
.resizable()
/**
.placeholder {
Image(systemName: "photo")
}
*/
.indicator(.activity)
.transition(.fade(duration: 0.5))
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
}
Text((url as NSString).lastPathComponent)
}
}
.buttonStyle(PlainButtonStyle())
}
}
var body: some View {
#if os(iOS)
return NavigationView {
@ -165,49 +217,8 @@ struct ContentView: View {
func contentView() -> some View {
List {
ForEach(imageURLs, id: \.self) { url in
NavigationLink(destination: DetailView(url: url, animated: self.animated)) {
HStack {
if self.animated {
#if os(macOS) || os(iOS) || os(tvOS)
AnimatedImage(url: URL(string:url), isAnimating: .constant(true))
.onViewUpdate { view, context in
#if os(macOS)
view.toolTip = url
#endif
}
.indicator(SDWebImageActivityIndicator.medium)
/**
.placeholder(UIImage(systemName: "photo"))
*/
.transition(.fade)
.resizable()
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
#else
WebImage(url: URL(string:url), isAnimating: self.$animated)
.resizable()
.indicator(.activity)
.transition(.fade(duration: 0.5))
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
#endif
} else {
WebImage(url: URL(string:url), isAnimating: .constant(true))
.resizable()
/**
.placeholder {
Image(systemName: "photo")
}
*/
.indicator(.activity)
.transition(.fade(duration: 0.5))
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
}
Text((url as NSString).lastPathComponent)
}
}
.buttonStyle(PlainButtonStyle())
// Must use top level view instead of inlined view structure
ItemView(animated: $animated, url: url)
}
.onDelete { indexSet in
indexSet.forEach { index in

View File

@ -270,7 +270,7 @@ It looks familiar like `SDWebImageManager`, but it's built for SwiftUI world, wh
```swift
struct MyView : View {
@ObservedObject var imageManager: ImageManager
@ObservedObject var imageManager = ImageManager()
var body: some View {
// Your custom complicated view graph
Group {
@ -281,17 +281,11 @@ struct MyView : View {
}
}
// Trigger image loading when appear
.onAppear { self.imageManager.load() }
.onAppear { self.imageManager.load(url: url) }
// Cancel image loading when disappear
.onDisappear { self.imageManager.cancel() }
}
}
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView(imageManager: ImageManager(url: URL(string: "https://via.placeholder.com/200x200.jpg"))
}
}
```
### Customization and configuration setup
@ -337,6 +331,54 @@ For more information, it's really recommended to check our demo, to learn detail
### Common Problems
#### Using WebImage/AnimatedImage in List/LazyStack/LazyGrid and ForEach
SwiftUI has a known behavior(bug?) when using stateful view in `List/LazyStack/LazyGrid`.
Only the **Top Level** view can hold its own `@State/@StateObject`, but the sub structure will lose state when scroll out of screen.
However, WebImage/Animated is both stateful. To ensure the state keep in sync even when scroll out of screen. you may use some tricks.
See more: https://twitter.com/fatbobman/status/1572507700436807683?s=21&t=z4FkAWTMvjsgL-wKdJGreQ
In short, it's not recommanded to do so:
```swift
struct ContentView {
@State var imageURLs: [String]
var body: some View {
List {
ForEach(imageURLs, id: \.self) { url in
VStack {
WebImage(url) // The top level is `VStack`
}
}
}
}
}
```
instead, using this approach:
```swift
struct ContentView {
struct BodyView {
@State var url: String
var body: some View {
VStack {
WebImage(url)
}
}
}
@State var imageURLs: [String]
var body: some View {
List {
ForEach(imageURLs, id: \.self) { url in
BodyView(url: url)
}
}
}
}
```
#### Using Image/WebImage/AnimatedImage in Button/NavigationLink
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:

View File

@ -253,16 +253,20 @@ public struct WebImage : View {
/// Placeholder View Support
func setupPlaceholder() -> some View {
// Don't use `Group` because it will trigger `.onAppear` and `.onDisappear` when condition view removed, treat placeholder as an entire component
let result: AnyView
if let placeholder = placeholder {
// If use `.delayPlaceholder`, the placeholder is applied after loading failed, hide during loading :)
if imageModel.options.contains(.delayPlaceholder) && imageManager.error == nil {
return AnyView(configure(image: .empty).id(UUID())) // UUID to avoid SwiftUI engine cache the status and does not call `onAppear`
result = AnyView(configure(image: .empty))
} else {
return placeholder
result = placeholder
}
} else {
return AnyView(configure(image: .empty).id(UUID())) // UUID to avoid SwiftUI engine cache the status and does not call `onAppear`
result = AnyView(configure(image: .empty))
}
// UUID to avoid SwiftUI engine cache the status, and does not call `onAppear` when placeholder not changed (See `ContentView.swift/ContentView2` case)
// Because we load the image url in `onAppear`, it should be called to sync with state changes :)
return result.id(UUID())
}
}