Skip to content

Commit c60b04b

Browse files
authored
Merge pull request #236 from SDWebImage/bugfix_webimage_no_stop_playing_quick_scroll
Fix the case which sometimes the player does not stop when WebImage it out of screen
2 parents 53ac75c + 657c64a commit c60b04b

File tree

3 files changed

+70
-40
lines changed

3 files changed

+70
-40
lines changed

SDWebImageSwiftUI/Classes/ImagePlayer.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ public final class ImagePlayer : ObservableObject {
3333

3434
deinit {
3535
player?.stopPlaying()
36-
currentFrame = nil
3736
}
3837

3938
/// Current playing frame image
@@ -57,7 +56,7 @@ public final class ImagePlayer : ObservableObject {
5756
if let player = player {
5857
return player.isPlaying && waitingPlaying
5958
}
60-
return false
59+
return true
6160
}
6261

6362
/// Current playing status
@@ -106,11 +105,21 @@ public final class ImagePlayer : ObservableObject {
106105
currentAnimatedImage = animatedImage
107106
if let imagePlayer = SDAnimatedImagePlayer(provider: animatedImage) {
108107
imagePlayer.animationFrameHandler = { [weak self] (index, frame) in
109-
self?.currentFrameIndex = index
110-
self?.currentFrame = frame
108+
guard let self = self else {
109+
return
110+
}
111+
if (self.isPlaying) {
112+
self.currentFrameIndex = index
113+
self.currentFrame = frame
114+
}
111115
}
112116
imagePlayer.animationLoopHandler = { [weak self] (loopCount) in
113-
self?.currentLoopCount = loopCount
117+
guard let self = self else {
118+
return
119+
}
120+
if (self.isPlaying) {
121+
self.currentLoopCount = loopCount
122+
}
114123
}
115124
// Setup configuration
116125
if let maxBufferSize = maxBufferSize {

SDWebImageSwiftUI/Classes/WebImage.swift

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,17 @@ public struct WebImage : View {
102102
}
103103

104104
public var body: some View {
105-
return Group {
106-
// Render Logic
105+
// Container
106+
return ZStack {
107+
// This empty Image is used to receive container's level appear/disappear to start/stop player, reduce CPU usage
108+
Image(platformImage: .empty)
109+
.onAppear {
110+
self.appearAction()
111+
}
112+
.onDisappear {
113+
self.disappearAction()
114+
}
115+
// Render Logic for actual animated image frame or static image
107116
if imageManager.image != nil && imageModel.url == imageManager.currentURL {
108117
if isAnimating && !imageManager.isIncremental {
109118
setupPlayer()
@@ -118,7 +127,7 @@ public struct WebImage : View {
118127
// Load Logic
119128
setupPlaceholder()
120129
.onPlatformAppear(appear: {
121-
setupManager()
130+
self.setupManager()
122131
if (self.imageManager.error == nil) {
123132
// Load remote image when first appear
124133
self.imageManager.load(url: imageModel.url, options: imageModel.options, context: imageModel.context)
@@ -205,36 +214,50 @@ public struct WebImage : View {
205214
}
206215
}
207216

217+
/// Container level to resume animation when appear
218+
func appearAction() {
219+
self.imagePlayer.startPlaying()
220+
}
221+
222+
/// Container level to stop animation when disappear
223+
func disappearAction() {
224+
if self.imageConfiguration.pausable {
225+
self.imagePlayer.pausePlaying()
226+
} else {
227+
self.imagePlayer.stopPlaying()
228+
}
229+
if self.imageConfiguration.purgeable {
230+
self.imagePlayer.clearFrameBuffer()
231+
}
232+
}
233+
208234
/// Animated Image Support
209235
func setupPlayer() -> some View {
210-
let disappearAction = {
211-
// Only stop the player which is not intermediate status
212-
if !imagePlayer.isWaiting {
213-
if self.imageConfiguration.pausable {
214-
self.imagePlayer.pausePlaying()
215-
} else {
216-
self.imagePlayer.stopPlaying()
217-
}
218-
if self.imageConfiguration.purgeable {
219-
self.imagePlayer.clearFrameBuffer()
220-
}
221-
}
236+
let shouldResetPlayer: Bool
237+
// Image compare should use ===/!==, which is faster than isEqual:
238+
if let animatedImage = imagePlayer.currentAnimatedImage, animatedImage !== imageManager.image! {
239+
shouldResetPlayer = true
240+
} else {
241+
shouldResetPlayer = false
222242
}
223-
if let currentFrame = imagePlayer.currentFrame, imagePlayer.currentAnimatedImage == imageManager.image! {
224-
return configure(image: currentFrame).onPlatformAppear(appear: {
225-
self.imagePlayer.startPlaying()
226-
}, disappear: {
227-
disappearAction()
228-
})
243+
if let currentFrame = imagePlayer.currentFrame, !shouldResetPlayer {
244+
// Bind frame index to ID to ensure onDisappear called with sync
245+
return configure(image: currentFrame)
246+
.id("\(imageModel.url!):\(imagePlayer.currentFrameIndex)")
247+
.onAppear {}
229248
} else {
230-
return configure(image: imageManager.image!).onPlatformAppear(appear: {
231-
self.imagePlayer.stopPlaying()
232-
if let animatedImage = imageManager.image as? PlatformImage & SDAnimatedImageProvider {
249+
return configure(image: imageManager.image!)
250+
.id("\(imageModel.url!):\(imagePlayer.currentFrameIndex)")
251+
.onAppear {
252+
if shouldResetPlayer {
233253
// Clear previous status
234-
self.imagePlayer.player = nil;
254+
self.imagePlayer.stopPlaying()
255+
self.imagePlayer.player = nil
235256
self.imagePlayer.currentFrame = nil;
236257
self.imagePlayer.currentFrameIndex = 0;
237258
self.imagePlayer.currentLoopCount = 0;
259+
}
260+
if let animatedImage = imageManager.image as? PlatformImage & SDAnimatedImageProvider {
238261
self.imagePlayer.customLoopCount = self.imageConfiguration.customLoopCount
239262
self.imagePlayer.maxBufferSize = self.imageConfiguration.maxBufferSize
240263
self.imagePlayer.runLoopMode = self.imageConfiguration.runLoopMode
@@ -244,9 +267,7 @@ public struct WebImage : View {
244267
self.imagePlayer.setupPlayer(animatedImage: animatedImage)
245268
self.imagePlayer.startPlaying()
246269
}
247-
}, disappear: {
248-
disappearAction()
249-
})
270+
}
250271
}
251272
}
252273

Tests/WebImageTests.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ class WebImageTests: XCTestCase {
2323
let imageView = WebImage(url: imageUrl)
2424
let introspectView = imageView.onSuccess { image, data, cacheType in
2525
#if os(macOS)
26-
let displayImage = try? imageView.inspect().group().image(0).actualImage().nsImage()
26+
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().nsImage()
2727
#else
28-
let displayImage = try? imageView.inspect().group().image(0).actualImage().cgImage()
28+
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().cgImage()
2929
#endif
3030
XCTAssertNotNil(displayImage)
3131
expectation.fulfill()
@@ -47,10 +47,10 @@ class WebImageTests: XCTestCase {
4747
if let animatedImage = image as? SDAnimatedImage {
4848
XCTAssertTrue(imageView.isAnimating)
4949
#if os(macOS)
50-
let displayImage = try? imageView.inspect().group().image(0).actualImage().nsImage()
50+
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().nsImage()
5151
let size = displayImage?.size
5252
#else
53-
let displayImage = try? imageView.inspect().group().image(0).actualImage().cgImage()
53+
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().cgImage()
5454
let size = CGSize(width: displayImage?.width ?? 0, height: displayImage?.height ?? 0)
5555
#endif
5656
XCTAssertNotNil(displayImage)
@@ -161,11 +161,11 @@ class WebImageTests: XCTestCase {
161161
let imageView = WebImage(url: imageUrl)
162162
let introspectView = imageView.onSuccess { image, data, cacheType in
163163
#if os(macOS)
164-
let displayImage = try? imageView.inspect().group().image(0).actualImage().nsImage()
164+
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().nsImage()
165165
XCTAssertNotNil(displayImage)
166166
#else
167-
let displayImage = try? imageView.inspect().group().image(0).actualImage().cgImage()
168-
let orientation = try? imageView.inspect().group().image(0).actualImage().orientation()
167+
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().cgImage()
168+
let orientation = try? imageView.inspect().zStack().image(1).actualImage().orientation()
169169
XCTAssertNotNil(displayImage)
170170
XCTAssertEqual(orientation, .leftMirrored)
171171
#endif

0 commit comments

Comments
 (0)