diff --git a/SDWebImageSwiftUI/Classes/ImagePlayer.swift b/SDWebImageSwiftUI/Classes/ImagePlayer.swift index e0ceded6..4fdc405e 100644 --- a/SDWebImageSwiftUI/Classes/ImagePlayer.swift +++ b/SDWebImageSwiftUI/Classes/ImagePlayer.swift @@ -33,7 +33,6 @@ public final class ImagePlayer : ObservableObject { deinit { player?.stopPlaying() - currentFrame = nil } /// Current playing frame image @@ -57,7 +56,7 @@ public final class ImagePlayer : ObservableObject { if let player = player { return player.isPlaying && waitingPlaying } - return false + return true } /// Current playing status @@ -106,11 +105,21 @@ public final class ImagePlayer : ObservableObject { currentAnimatedImage = animatedImage if let imagePlayer = SDAnimatedImagePlayer(provider: animatedImage) { imagePlayer.animationFrameHandler = { [weak self] (index, frame) in - self?.currentFrameIndex = index - self?.currentFrame = frame + guard let self = self else { + return + } + if (self.isPlaying) { + self.currentFrameIndex = index + self.currentFrame = frame + } } imagePlayer.animationLoopHandler = { [weak self] (loopCount) in - self?.currentLoopCount = loopCount + guard let self = self else { + return + } + if (self.isPlaying) { + self.currentLoopCount = loopCount + } } // Setup configuration if let maxBufferSize = maxBufferSize { diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index 680a4969..2290182c 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -102,8 +102,17 @@ public struct WebImage : View { } public var body: some View { - return Group { - // Render Logic + // Container + return ZStack { + // This empty Image is used to receive container's level appear/disappear to start/stop player, reduce CPU usage + Image(platformImage: .empty) + .onAppear { + self.appearAction() + } + .onDisappear { + self.disappearAction() + } + // Render Logic for actual animated image frame or static image if imageManager.image != nil && imageModel.url == imageManager.currentURL { if isAnimating && !imageManager.isIncremental { setupPlayer() @@ -118,7 +127,7 @@ public struct WebImage : View { // Load Logic setupPlaceholder() .onPlatformAppear(appear: { - setupManager() + self.setupManager() if (self.imageManager.error == nil) { // Load remote image when first appear self.imageManager.load(url: imageModel.url, options: imageModel.options, context: imageModel.context) @@ -205,36 +214,50 @@ public struct WebImage : View { } } + /// Container level to resume animation when appear + func appearAction() { + self.imagePlayer.startPlaying() + } + + /// Container level to stop animation when disappear + func disappearAction() { + if self.imageConfiguration.pausable { + self.imagePlayer.pausePlaying() + } else { + self.imagePlayer.stopPlaying() + } + if self.imageConfiguration.purgeable { + self.imagePlayer.clearFrameBuffer() + } + } + /// Animated Image Support func setupPlayer() -> some View { - let disappearAction = { - // Only stop the player which is not intermediate status - if !imagePlayer.isWaiting { - if self.imageConfiguration.pausable { - self.imagePlayer.pausePlaying() - } else { - self.imagePlayer.stopPlaying() - } - if self.imageConfiguration.purgeable { - self.imagePlayer.clearFrameBuffer() - } - } + let shouldResetPlayer: Bool + // Image compare should use ===/!==, which is faster than isEqual: + if let animatedImage = imagePlayer.currentAnimatedImage, animatedImage !== imageManager.image! { + shouldResetPlayer = true + } else { + shouldResetPlayer = false } - if let currentFrame = imagePlayer.currentFrame, imagePlayer.currentAnimatedImage == imageManager.image! { - return configure(image: currentFrame).onPlatformAppear(appear: { - self.imagePlayer.startPlaying() - }, disappear: { - disappearAction() - }) + if let currentFrame = imagePlayer.currentFrame, !shouldResetPlayer { + // Bind frame index to ID to ensure onDisappear called with sync + return configure(image: currentFrame) + .id("\(imageModel.url!):\(imagePlayer.currentFrameIndex)") + .onAppear {} } else { - return configure(image: imageManager.image!).onPlatformAppear(appear: { - self.imagePlayer.stopPlaying() - if let animatedImage = imageManager.image as? PlatformImage & SDAnimatedImageProvider { + return configure(image: imageManager.image!) + .id("\(imageModel.url!):\(imagePlayer.currentFrameIndex)") + .onAppear { + if shouldResetPlayer { // Clear previous status - self.imagePlayer.player = nil; + self.imagePlayer.stopPlaying() + self.imagePlayer.player = nil self.imagePlayer.currentFrame = nil; self.imagePlayer.currentFrameIndex = 0; self.imagePlayer.currentLoopCount = 0; + } + if let animatedImage = imageManager.image as? PlatformImage & SDAnimatedImageProvider { self.imagePlayer.customLoopCount = self.imageConfiguration.customLoopCount self.imagePlayer.maxBufferSize = self.imageConfiguration.maxBufferSize self.imagePlayer.runLoopMode = self.imageConfiguration.runLoopMode @@ -244,9 +267,7 @@ public struct WebImage : View { self.imagePlayer.setupPlayer(animatedImage: animatedImage) self.imagePlayer.startPlaying() } - }, disappear: { - disappearAction() - }) + } } } diff --git a/Tests/WebImageTests.swift b/Tests/WebImageTests.swift index fce4d24c..d51efdbc 100644 --- a/Tests/WebImageTests.swift +++ b/Tests/WebImageTests.swift @@ -23,9 +23,9 @@ class WebImageTests: XCTestCase { let imageView = WebImage(url: imageUrl) let introspectView = imageView.onSuccess { image, data, cacheType in #if os(macOS) - let displayImage = try? imageView.inspect().group().image(0).actualImage().nsImage() + let displayImage = try? imageView.inspect().zStack().image(1).actualImage().nsImage() #else - let displayImage = try? imageView.inspect().group().image(0).actualImage().cgImage() + let displayImage = try? imageView.inspect().zStack().image(1).actualImage().cgImage() #endif XCTAssertNotNil(displayImage) expectation.fulfill() @@ -47,10 +47,10 @@ class WebImageTests: XCTestCase { if let animatedImage = image as? SDAnimatedImage { XCTAssertTrue(imageView.isAnimating) #if os(macOS) - let displayImage = try? imageView.inspect().group().image(0).actualImage().nsImage() + let displayImage = try? imageView.inspect().zStack().image(1).actualImage().nsImage() let size = displayImage?.size #else - let displayImage = try? imageView.inspect().group().image(0).actualImage().cgImage() + let displayImage = try? imageView.inspect().zStack().image(1).actualImage().cgImage() let size = CGSize(width: displayImage?.width ?? 0, height: displayImage?.height ?? 0) #endif XCTAssertNotNil(displayImage) @@ -161,11 +161,11 @@ class WebImageTests: XCTestCase { let imageView = WebImage(url: imageUrl) let introspectView = imageView.onSuccess { image, data, cacheType in #if os(macOS) - let displayImage = try? imageView.inspect().group().image(0).actualImage().nsImage() + let displayImage = try? imageView.inspect().zStack().image(1).actualImage().nsImage() XCTAssertNotNil(displayImage) #else - let displayImage = try? imageView.inspect().group().image(0).actualImage().cgImage() - let orientation = try? imageView.inspect().group().image(0).actualImage().orientation() + let displayImage = try? imageView.inspect().zStack().image(1).actualImage().cgImage() + let orientation = try? imageView.inspect().zStack().image(1).actualImage().orientation() XCTAssertNotNil(displayImage) XCTAssertEqual(orientation, .leftMirrored) #endif