Fix the State change behavior again
Using the `StateObject` to check and refresh to the latest status, using `currentURL` and `currentAnimatedImage` to check lazily
This commit is contained in:
parent
ce5340fd08
commit
d281bde037
|
@ -34,6 +34,42 @@ extension Indicator where T == ProgressView<EmptyView, EmptyView> {
|
|||
}
|
||||
#endif
|
||||
|
||||
// Test Switching url using @State
|
||||
struct ContentView2: View {
|
||||
@State var imageURLs = [
|
||||
"https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_1.jpg",
|
||||
"https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_2.jpg",
|
||||
"http://assets.sbnation.com/assets/2512203/dogflops.gif",
|
||||
"https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif"
|
||||
]
|
||||
@State var animated: Bool = false // You can change between WebImage/AnimatedImage
|
||||
@State var imageIndex : Int = 0
|
||||
var body: some View {
|
||||
Group {
|
||||
Text("\(animated ? "AnimatedImage" : "WebImage") - \((imageURLs[imageIndex] as NSString).lastPathComponent)")
|
||||
Spacer()
|
||||
if self.animated {
|
||||
AnimatedImage(url:URL(string: imageURLs[imageIndex]))
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
} else {
|
||||
WebImage(url:URL(string: imageURLs[imageIndex]))
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
}
|
||||
Spacer()
|
||||
Button("Next") {
|
||||
if imageIndex + 1 >= imageURLs.count {
|
||||
imageIndex = 0
|
||||
} else {
|
||||
imageIndex += 1
|
||||
}
|
||||
}
|
||||
Toggle("Switch", isOn: $animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@State var imageURLs = [
|
||||
"http://assets.sbnation.com/assets/2512203/dogflops.gif",
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
|
||||
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
|
||||
326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
|
||||
32B79C9528DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; };
|
||||
32B79C9628DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; };
|
||||
32B79C9728DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; };
|
||||
32B79C9828DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; };
|
||||
32B933E523659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; };
|
||||
32B933E623659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; };
|
||||
32B933E723659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; };
|
||||
|
@ -87,6 +91,7 @@
|
|||
326B8486236335110011BDFB /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; };
|
||||
326B848B236335400011BDFB /* ProgressIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = "<group>"; };
|
||||
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewWrapper.swift; sourceTree = "<group>"; };
|
||||
32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUICompatibility.swift; sourceTree = "<group>"; };
|
||||
32B933E423659A1900BB7CAD /* Transition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transition.swift; sourceTree = "<group>"; };
|
||||
32BC086F28D23D35002451BD /* StateObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateObject.swift; sourceTree = "<group>"; };
|
||||
32BC087028D23D35002451BD /* OnChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnChange.swift; sourceTree = "<group>"; };
|
||||
|
@ -233,6 +238,7 @@
|
|||
32C43DDE22FD54C600BE87F5 /* WebImage.swift */,
|
||||
32C43DDF22FD54C600BE87F5 /* AnimatedImage.swift */,
|
||||
32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */,
|
||||
32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */,
|
||||
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */,
|
||||
32D26A012446B546005905DA /* Image.swift */,
|
||||
);
|
||||
|
@ -457,6 +463,7 @@
|
|||
326B84822363350C0011BDFB /* Indicator.swift in Sources */,
|
||||
32C43E3222FD5DE100BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
|
||||
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
|
||||
32B79C9528DB40430088C432 /* SwiftUICompatibility.swift in Sources */,
|
||||
326B8487236335110011BDFB /* ActivityIndicator.swift in Sources */,
|
||||
32C43E1622FD583700BE87F5 /* ImageManager.swift in Sources */,
|
||||
32C43E1822FD583700BE87F5 /* AnimatedImage.swift in Sources */,
|
||||
|
@ -479,6 +486,7 @@
|
|||
326B84832363350C0011BDFB /* Indicator.swift in Sources */,
|
||||
32C43E3322FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
|
||||
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
|
||||
32B79C9628DB40430088C432 /* SwiftUICompatibility.swift in Sources */,
|
||||
326B8488236335110011BDFB /* ActivityIndicator.swift in Sources */,
|
||||
32C43E1922FD583700BE87F5 /* ImageManager.swift in Sources */,
|
||||
32C43E1B22FD583700BE87F5 /* AnimatedImage.swift in Sources */,
|
||||
|
@ -501,6 +509,7 @@
|
|||
326B84842363350C0011BDFB /* Indicator.swift in Sources */,
|
||||
32C43E3422FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
|
||||
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
|
||||
32B79C9728DB40430088C432 /* SwiftUICompatibility.swift in Sources */,
|
||||
326B8489236335110011BDFB /* ActivityIndicator.swift in Sources */,
|
||||
32C43E1C22FD583800BE87F5 /* ImageManager.swift in Sources */,
|
||||
32C43E1E22FD583800BE87F5 /* AnimatedImage.swift in Sources */,
|
||||
|
@ -523,6 +532,7 @@
|
|||
326B84852363350C0011BDFB /* Indicator.swift in Sources */,
|
||||
32C43E3522FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
|
||||
326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
|
||||
32B79C9828DB40430088C432 /* SwiftUICompatibility.swift in Sources */,
|
||||
326B848A236335110011BDFB /* ActivityIndicator.swift in Sources */,
|
||||
32C43E1F22FD583800BE87F5 /* ImageManager.swift in Sources */,
|
||||
32C43E2122FD583800BE87F5 /* AnimatedImage.swift in Sources */,
|
||||
|
|
|
@ -27,7 +27,8 @@ public final class ImageManager : ObservableObject {
|
|||
@Published public var indicatorStatus = IndicatorStatus()
|
||||
|
||||
weak var currentOperation: SDWebImageOperation? = nil
|
||||
|
||||
|
||||
var currentURL: URL?
|
||||
var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)?
|
||||
var failureBlock: ((Error) -> Void)?
|
||||
var progressBlock: ((Int, Int) -> Void)?
|
||||
|
@ -45,10 +46,12 @@ public final class ImageManager : ObservableObject {
|
|||
} else {
|
||||
manager = .shared
|
||||
}
|
||||
if currentOperation != nil {
|
||||
if (currentOperation != nil && currentURL == url) {
|
||||
return
|
||||
}
|
||||
self.indicatorStatus.isLoading = true
|
||||
currentURL = url
|
||||
indicatorStatus.isLoading = true
|
||||
indicatorStatus.progress = 0
|
||||
currentOperation = manager.loadImage(with: url, options: options, context: context, progress: { [weak self] (receivedSize, expectedSize, _) in
|
||||
guard let self = self else {
|
||||
return
|
||||
|
|
|
@ -45,6 +45,8 @@ public final class ImagePlayer : ObservableObject {
|
|||
/// Current playing loop count
|
||||
@Published public var currentLoopCount: UInt = 0
|
||||
|
||||
var currentAnimatedImage: (PlatformImage & SDAnimatedImageProvider)?
|
||||
|
||||
/// Whether current player is valid for playing. This will check the internal player exist or not
|
||||
public var isValid: Bool {
|
||||
player != nil
|
||||
|
@ -97,10 +99,11 @@ public final class ImagePlayer : ObservableObject {
|
|||
/// Setup the player using Animated Image.
|
||||
/// After setup, you can always check `isValid` status, or call `startPlaying` to play the animation.
|
||||
/// - Parameter image: animated image
|
||||
public func setupPlayer(animatedImage: SDAnimatedImageProvider) {
|
||||
public func setupPlayer(animatedImage: PlatformImage & SDAnimatedImageProvider) {
|
||||
if isValid {
|
||||
return
|
||||
}
|
||||
currentAnimatedImage = animatedImage
|
||||
if let imagePlayer = SDAnimatedImagePlayer(provider: animatedImage) {
|
||||
imagePlayer.animationFrameHandler = { [weak self] (index, frame) in
|
||||
self?.currentFrameIndex = index
|
||||
|
|
|
@ -84,7 +84,7 @@ extension View {
|
|||
/// - Returns: Some view
|
||||
func onPlatformAppear(appear: @escaping () -> Void = {}, disappear: @escaping () -> Void = {}) -> some View {
|
||||
#if os(iOS) || os(tvOS) || os(macOS)
|
||||
return self.overlay(PlatformAppear(appearAction: appear, disappearAction: disappear))
|
||||
return self.background(PlatformAppear(appearAction: appear, disappearAction: disappear))
|
||||
#else
|
||||
return self.onAppear(perform: appear).onDisappear(perform: disappear)
|
||||
#endif
|
||||
|
|
|
@ -14,8 +14,8 @@ import SDWebImage
|
|||
final class WebImageModel : ObservableObject {
|
||||
/// URL image
|
||||
@Published var url: URL?
|
||||
@Published var webOptions: SDWebImageOptions = []
|
||||
@Published var webContext: [SDWebImageContextOption : Any]? = nil
|
||||
@Published var options: SDWebImageOptions = []
|
||||
@Published var context: [SDWebImageContextOption : Any]? = nil
|
||||
}
|
||||
|
||||
/// Completion Handler Binding Object, supports dynamic @State changes
|
||||
|
@ -61,11 +61,13 @@ public struct WebImage : View {
|
|||
/// A observed object to pass through the image configuration to player
|
||||
@ObservedObject var imageConfiguration = WebImageConfiguration()
|
||||
|
||||
@ObservedObject var indicatorStatus : IndicatorStatus
|
||||
|
||||
// FIXME: Use SwiftUI StateObject and remove onPlatformAppear once drop iOS 13 support
|
||||
@Backport.StateObject var imagePlayer = ImagePlayer()
|
||||
|
||||
// FIXME: Use SwiftUI StateObject and remove onPlatformAppear once drop iOS 13 support
|
||||
@Backport.StateObject var imageManager = ImageManager()
|
||||
@Backport.StateObject var imageManager : ImageManager
|
||||
|
||||
/// Create a web image with url, placeholder, custom options and context. Optional can support animated image using Binding.
|
||||
/// - Parameter url: The image url
|
||||
|
@ -83,9 +85,12 @@ public struct WebImage : View {
|
|||
}
|
||||
let imageModel = WebImageModel()
|
||||
imageModel.url = url
|
||||
imageModel.webOptions = options
|
||||
imageModel.webContext = context
|
||||
imageModel.options = options
|
||||
imageModel.context = context
|
||||
_imageModel = ObservedObject(wrappedValue: imageModel)
|
||||
let imageManager = ImageManager()
|
||||
_imageManager = Backport.StateObject(wrappedValue: imageManager)
|
||||
_indicatorStatus = ObservedObject(wrappedValue: imageManager.indicatorStatus)
|
||||
}
|
||||
|
||||
/// Create a web image with url, placeholder, custom options and context.
|
||||
|
@ -98,7 +103,7 @@ public struct WebImage : View {
|
|||
|
||||
public var body: some View {
|
||||
return Group {
|
||||
if let image = imageManager.image {
|
||||
if imageManager.image != nil && imageModel.url == imageManager.currentURL {
|
||||
if isAnimating && !imageManager.isIncremental {
|
||||
setupPlayer()
|
||||
.onDisappear {
|
||||
|
@ -118,7 +123,7 @@ public struct WebImage : View {
|
|||
if let currentFrame = imagePlayer.currentFrame {
|
||||
configure(image: currentFrame)
|
||||
} else {
|
||||
configure(image: image)
|
||||
configure(image: imageManager.image!)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -127,17 +132,19 @@ public struct WebImage : View {
|
|||
self.imageManager.successBlock = self.imageHandler.successBlock
|
||||
self.imageManager.failureBlock = self.imageHandler.failureBlock
|
||||
self.imageManager.progressBlock = self.imageHandler.progressBlock
|
||||
// Load remote image when first appear
|
||||
self.imageManager.load(url: imageModel.url, options: imageModel.webOptions, context: imageModel.webContext)
|
||||
if (self.imageManager.error == nil) {
|
||||
// Load remote image when first appear
|
||||
self.imageManager.load(url: imageModel.url, options: imageModel.options, context: imageModel.context)
|
||||
}
|
||||
guard self.imageConfiguration.retryOnAppear else { return }
|
||||
// When using prorgessive loading, the new partial image will cause onAppear. Filter this case
|
||||
if self.imageManager.image == nil && !self.imageManager.isIncremental {
|
||||
self.imageManager.load(url: imageModel.url, options: imageModel.webOptions, context: imageModel.webContext)
|
||||
if self.imageManager.error != nil && !self.imageManager.isIncremental {
|
||||
self.imageManager.load(url: imageModel.url, options: imageModel.options, context: imageModel.context)
|
||||
}
|
||||
}, disappear: {
|
||||
guard self.imageConfiguration.cancelOnDisappear else { return }
|
||||
// When using prorgessive loading, the previous partial image will cause onDisappear. Filter this case
|
||||
if self.imageManager.image == nil && !self.imageManager.isIncremental {
|
||||
if self.imageManager.error != nil && !self.imageManager.isIncremental {
|
||||
self.imageManager.cancel()
|
||||
}
|
||||
})
|
||||
|
@ -196,18 +203,25 @@ public struct WebImage : View {
|
|||
|
||||
/// Animated Image Support
|
||||
func setupPlayer() -> some View {
|
||||
if let currentFrame = imagePlayer.currentFrame {
|
||||
if let currentFrame = imagePlayer.currentFrame, imagePlayer.currentAnimatedImage == imageManager.image! {
|
||||
return configure(image: currentFrame).onAppear {
|
||||
self.imagePlayer.startPlaying()
|
||||
}
|
||||
} else {
|
||||
return configure(image: imageManager.image!).onAppear {
|
||||
if let animatedImage = imageManager.image as? SDAnimatedImageProvider {
|
||||
self.imagePlayer.stopPlaying()
|
||||
if let animatedImage = imageManager.image as? PlatformImage & SDAnimatedImageProvider {
|
||||
// Clear previous status
|
||||
self.imagePlayer.player = nil;
|
||||
self.imagePlayer.currentFrame = nil;
|
||||
self.imagePlayer.currentFrameIndex = 0;
|
||||
self.imagePlayer.currentLoopCount = 0;
|
||||
self.imagePlayer.customLoopCount = self.imageConfiguration.customLoopCount
|
||||
self.imagePlayer.maxBufferSize = self.imageConfiguration.maxBufferSize
|
||||
self.imagePlayer.runLoopMode = self.imageConfiguration.runLoopMode
|
||||
self.imagePlayer.playbackMode = self.imageConfiguration.playbackMode
|
||||
self.imagePlayer.playbackRate = self.imageConfiguration.playbackRate
|
||||
// Setup new player
|
||||
self.imagePlayer.setupPlayer(animatedImage: animatedImage)
|
||||
self.imagePlayer.startPlaying()
|
||||
}
|
||||
|
@ -220,7 +234,7 @@ public struct WebImage : View {
|
|||
// Don't use `Group` because it will trigger `.onAppear` and `.onDisappear` when condition view removed, treat placeholder as an entire component
|
||||
if let placeholder = placeholder {
|
||||
// If use `.delayPlaceholder`, the placeholder is applied after loading failed, hide during loading :)
|
||||
if imageModel.webOptions.contains(.delayPlaceholder) && imageManager.error == nil {
|
||||
if imageModel.options.contains(.delayPlaceholder) && imageManager.error == nil {
|
||||
return AnyView(configure(image: .empty))
|
||||
} else {
|
||||
return placeholder
|
||||
|
@ -347,7 +361,7 @@ extension WebImage {
|
|||
/// Associate a indicator when loading image with url
|
||||
/// - Parameter indicator: The indicator type, see `Indicator`
|
||||
public func indicator<T>(_ indicator: Indicator<T>) -> some View where T : View {
|
||||
return self.modifier(IndicatorViewModifier(status: imageManager.indicatorStatus, indicator: indicator))
|
||||
return self.modifier(IndicatorViewModifier(status: indicatorStatus, indicator: indicator))
|
||||
}
|
||||
|
||||
/// Associate a indicator when loading image with url, convenient method with block
|
||||
|
|
Loading…
Reference in New Issue