Fix the WebImage onSuccess does not get called because of StateObject get touched before onAppear

This commit is contained in:
DreamPiggy 2022-09-15 20:36:24 +08:00
parent ac0e73b1f8
commit ef03282068
1 changed files with 57 additions and 28 deletions

View File

@ -9,21 +9,46 @@
import SwiftUI
import SDWebImage
/// Completion Handler Binding Object, supports dynamic @State changes
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
final class WebImageHandler: ObservableObject {
// Completion Handler
@Published var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)?
@Published var failureBlock: ((Error) -> Void)?
@Published var progressBlock: ((Int, Int) -> Void)?
}
/// Configuration Binding Object, supports dynamic @State changes
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
final class WebImageConfiguration: ObservableObject {
var retryOnAppear: Bool = true
var cancelOnDisappear: Bool = true
var maxBufferSize: UInt?
var customLoopCount: UInt?
var runLoopMode: RunLoop.Mode = .common
var pausable: Bool = true
var purgeable: Bool = false
var playbackRate: Double = 1.0
var playbackMode: SDAnimatedImagePlaybackMode = .normal
}
/// A Image View type to load image from url. Supports static/animated image format.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct WebImage : View {
var configurations: [(Image) -> Image] = []
var placeholder: AnyView?
var retryOnAppear: Bool = true
var cancelOnDisappear: Bool = true
var pausable: Bool = true
var purgeable: Bool = false
/// A Binding to control the animation. You can bind external logic to control the animation status.
/// True to start animation, false to stop animation.
@Binding public var isAnimating: Bool
/// A observed object to pass through the image handler to manager
@ObservedObject var imageHandler = WebImageHandler()
/// A observed object to pass through the image configuration to player
@ObservedObject var imageConfiguration = WebImageConfiguration()
/// A observed object to pass through the image manager loading status to indicator
@ObservedObject var indicatorStatus = IndicatorStatus()
@ -84,12 +109,12 @@ public struct WebImage : View {
.onDisappear {
// Only stop the player which is not intermediate status
if !imagePlayer.isWaiting {
if self.pausable {
if self.imageConfiguration.pausable {
self.imagePlayer.pausePlaying()
} else {
self.imagePlayer.stopPlaying()
}
if self.purgeable {
if self.imageConfiguration.purgeable {
self.imagePlayer.clearFrameBuffer()
}
}
@ -104,15 +129,18 @@ public struct WebImage : View {
} else {
setupPlaceholder()
.onAppear {
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()
guard self.retryOnAppear else { return }
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()
}
}.onDisappear {
guard self.cancelOnDisappear else { return }
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 {
self.imageManager.cancel()
@ -183,6 +211,11 @@ public struct WebImage : View {
} else {
return configure(image: imageManager.image!).onAppear {
if let animatedImage = imageManager.image as? SDAnimatedImageProvider {
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
self.imagePlayer.setupPlayer(animatedImage: animatedImage)
self.imagePlayer.startPlaying()
}
@ -253,7 +286,7 @@ extension WebImage {
/// - action: The action to perform. The first arg is the error during loading. If `action` is `nil`, the call has no effect.
/// - Returns: A view that triggers `action` when this image load fails.
public func onFailure(perform action: ((Error) -> Void)? = nil) -> WebImage {
self.imageManager.failureBlock = action
self.imageHandler.failureBlock = action
return self
}
@ -262,7 +295,7 @@ extension WebImage {
/// - action: The action to perform. The first arg is the loaded image, the second arg is the loaded image data, the third arg is the cache type loaded from. If `action` is `nil`, the call has no effect.
/// - Returns: A view that triggers `action` when this image load successes.
public func onSuccess(perform action: ((PlatformImage, Data?, SDImageCacheType) -> Void)? = nil) -> WebImage {
self.imageManager.successBlock = action
self.imageHandler.successBlock = action
return self
}
@ -271,7 +304,7 @@ extension WebImage {
/// - action: The action to perform. The first arg is the received size, the second arg is the total size, all in bytes. If `action` is `nil`, the call has no effect.
/// - Returns: A view that triggers `action` when this image load successes.
public func onProgress(perform action: ((Int, Int) -> Void)? = nil) -> WebImage {
self.imageManager.progressBlock = action
self.imageHandler.progressBlock = action
return self
}
}
@ -303,17 +336,15 @@ extension WebImage {
/// Control the behavior to retry the failed loading when view become appears again
/// - Parameter flag: Whether or not to retry the failed loading
public func retryOnAppear(_ flag: Bool) -> WebImage {
var result = self
result.retryOnAppear = flag
return result
self.imageConfiguration.retryOnAppear = flag
return self
}
/// Control the behavior to cancel the pending loading when view become disappear again
/// - Parameter flag: Whether or not to cancel the pending loading
public func cancelOnDisappear(_ flag: Bool) -> WebImage {
var result = self
result.cancelOnDisappear = flag
return result
self.imageConfiguration.cancelOnDisappear = flag
return self
}
}
@ -342,7 +373,7 @@ extension WebImage {
/// - Note: Pass nil to disable customization, use the image itself loop count (`animatedImageLoopCount`) instead
/// - Parameter loopCount: The animation loop count
public func customLoopCount(_ loopCount: UInt?) -> WebImage {
self.imagePlayer.customLoopCount = loopCount
self.imageConfiguration.customLoopCount = loopCount
return self
}
@ -353,7 +384,7 @@ extension WebImage {
/// `UInt.max` means cache all the buffer. (Lowest CPU and Highest Memory)
/// - Parameter bufferSize: The max buffer size
public func maxBufferSize(_ bufferSize: UInt?) -> WebImage {
self.imagePlayer.maxBufferSize = bufferSize
self.imageConfiguration.maxBufferSize = bufferSize
return self
}
@ -362,7 +393,7 @@ extension WebImage {
/// - Note: This is useful for some cases, for example, always specify NSDefaultRunLoopMode, if you want to pause the animation when user scroll (for Mac user, drag the mouse or touchpad)
/// - Parameter runLoopMode: The runLoopMode for animation
public func runLoopMode(_ runLoopMode: RunLoop.Mode) -> WebImage {
self.imagePlayer.runLoopMode = runLoopMode
self.imageConfiguration.runLoopMode = runLoopMode
return self
}
@ -370,18 +401,16 @@ extension WebImage {
/// - Note: For some of use case, you may want to reset the frame index to 0 when stop, but some other want to keep the current frame index.
/// - Parameter pausable: Whether or not to pause the animation instead of stop the animation.
public func pausable(_ pausable: Bool) -> WebImage {
var result = self
result.pausable = pausable
return result
self.imageConfiguration.pausable = pausable
return self
}
/// Whether or not to clear frame buffer cache when stopped. Defaults is false.
/// Note: This is useful when you want to limit the memory usage during frequently visibility changes (such as image view inside a list view, then push and pop)
/// - Parameter purgeable: Whether or not to clear frame buffer cache when stopped.
public func purgeable(_ purgeable: Bool) -> WebImage {
var result = self
result.purgeable = purgeable
return result
self.imageConfiguration.purgeable = purgeable
return self
}
/// Control the animation playback rate. Default is 1.0.
@ -392,14 +421,14 @@ extension WebImage {
/// `< 0.0` is not supported currently and stop animation. (may support reverse playback in the future)
/// - Parameter playbackRate: The animation playback rate.
public func playbackRate(_ playbackRate: Double) -> WebImage {
self.imagePlayer.playbackRate = playbackRate
self.imageConfiguration.playbackRate = playbackRate
return self
}
/// Control the animation playback mode. Default is .normal
/// - Parameter playbackMode: The playback mode, including normal order, reverse order, bounce order and reversed bounce order.
public func playbackMode(_ playbackMode: SDAnimatedImagePlaybackMode) -> WebImage {
self.imagePlayer.playbackMode = playbackMode
self.imageConfiguration.playbackMode = playbackMode
return self
}
}