Update the readme about when using in List/LazyStack/LazyGrid
This commit is contained in:
parent
d18693909b
commit
abd9102f6b
|
@ -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
|
||||
|
|
58
README.md
58
README.md
|
@ -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:
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue