Merge pull request #326 from SDWebImage/bugfix/transition_animatedImage

Fix the transition visual jump between placeholderImage and final image for AnimatedImage
This commit is contained in:
DreamPiggy 2024-07-01 19:46:04 +08:00 committed by GitHub
commit fc52658f03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 32 additions and 34 deletions

View File

@ -276,7 +276,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
self.imageHandler.failureBlock?(error ?? NSError()) self.imageHandler.failureBlock?(error ?? NSError())
} }
// Finished loading, async // Finished loading, async
finishUpdateView(view, context: context, image: image) finishUpdateView(view, context: context)
} }
} }
@ -364,7 +364,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
} }
// Finished loading, sync // Finished loading, sync
finishUpdateView(view, context: context, image: view.wrapped.image) finishUpdateView(view, context: context)
if let viewUpdateBlock = imageHandler.viewUpdateBlock { if let viewUpdateBlock = imageHandler.viewUpdateBlock {
viewUpdateBlock(view.wrapped, context) viewUpdateBlock(view.wrapped, context)
@ -383,13 +383,8 @@ public struct AnimatedImage : PlatformViewRepresentable {
} }
} }
func finishUpdateView(_ view: AnimatedImageViewWrapper, context: Context, image: PlatformImage?) { func finishUpdateView(_ view: AnimatedImageViewWrapper, context: Context) {
// Finished loading // Finished loading
if let imageSize = image?.size {
view.imageSize = imageSize
} else {
view.imageSize = nil
}
configureView(view, context: context) configureView(view, context: context)
layoutView(view, context: context) layoutView(view, context: context)
} }

View File

@ -60,6 +60,7 @@ public final class ImageManager : ObservableObject {
weak var currentOperation: SDWebImageOperation? = nil weak var currentOperation: SDWebImageOperation? = nil
var currentURL: URL? var currentURL: URL?
var transaction = Transaction()
var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)? var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)?
var failureBlock: ((Error) -> Void)? var failureBlock: ((Error) -> Void)?
var progressBlock: ((Int, Int) -> Void)? var progressBlock: ((Int, Int) -> Void)?
@ -106,18 +107,20 @@ public final class ImageManager : ObservableObject {
// So previous View struct call `onDisappear` and cancel the currentOperation // So previous View struct call `onDisappear` and cancel the currentOperation
return return
} }
self.image = image withTransaction(transaction) {
self.error = error self.image = image
self.isIncremental = !finished self.error = error
if finished { self.isIncremental = !finished
self.imageData = data if finished {
self.cacheType = cacheType self.imageData = data
self.indicatorStatus.isLoading = false self.cacheType = cacheType
self.indicatorStatus.progress = 1 self.indicatorStatus.isLoading = false
if let image = image { self.indicatorStatus.progress = 1
self.successBlock?(image, data, cacheType) if let image = image {
} else { self.successBlock?(image, data, cacheType)
self.failureBlock?(error ?? NSError()) } else {
self.failureBlock?(error ?? NSError())
}
} }
} }
} }

View File

@ -16,11 +16,15 @@ import SwiftUI
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public class AnimatedImageViewWrapper : PlatformView { public class AnimatedImageViewWrapper : PlatformView {
/// The wrapped actual image view, using SDWebImage's aniamted image view /// The wrapped actual image view, using SDWebImage's aniamted image view
public var wrapped = SDAnimatedImageView() @objc dynamic public var wrapped = SDAnimatedImageView()
var observation: NSKeyValueObservation?
var interpolationQuality = CGInterpolationQuality.default var interpolationQuality = CGInterpolationQuality.default
var shouldAntialias = false var shouldAntialias = false
var resizingMode: Image.ResizingMode? var resizingMode: Image.ResizingMode?
var imageSize: CGSize?
deinit {
observation?.invalidate()
}
public override func draw(_ rect: CGRect) { public override func draw(_ rect: CGRect) {
#if os(macOS) #if os(macOS)
@ -50,15 +54,7 @@ public class AnimatedImageViewWrapper : PlatformView {
public override var intrinsicContentSize: CGSize { public override var intrinsicContentSize: CGSize {
/// Match the behavior of SwiftUI.Image, only when image is resizable, use the super implementation to calculate size /// Match the behavior of SwiftUI.Image, only when image is resizable, use the super implementation to calculate size
var contentSize = wrapped.intrinsicContentSize let contentSize = wrapped.intrinsicContentSize
/// Sometimes, like during the transaction, the wrapped.image == nil, which cause contentSize invalid
/// Use image size as backup
/// TODO: This mixed use of UIKit/SwiftUI animation will cause visial issue because the intrinsicContentSize during animation may be changed
if let imageSize = imageSize {
if contentSize != imageSize {
contentSize = imageSize
}
}
if let _ = resizingMode { if let _ = resizingMode {
/// Keep aspect ratio /// Keep aspect ratio
if contentSize.width > 0 && contentSize.height > 0 { if contentSize.width > 0 && contentSize.height > 0 {
@ -77,11 +73,17 @@ public class AnimatedImageViewWrapper : PlatformView {
public override init(frame frameRect: CGRect) { public override init(frame frameRect: CGRect) {
super.init(frame: frameRect) super.init(frame: frameRect)
addSubview(wrapped) addSubview(wrapped)
observation = observe(\.wrapped.image, options: [.new]) { _, _ in
self.invalidateIntrinsicContentSize()
}
} }
public required init?(coder: NSCoder) { public required init?(coder: NSCoder) {
super.init(coder: coder) super.init(coder: coder)
addSubview(wrapped) addSubview(wrapped)
observation = observe(\.wrapped.image, options: [.new]) { _, _ in
self.invalidateIntrinsicContentSize()
}
} }
} }

View File

@ -81,8 +81,6 @@ final class WebImageConfiguration: ObservableObject {
/// A Image View type to load image from url. Supports static/animated image format. /// A Image View type to load image from url. Supports static/animated image format.
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public struct WebImage<Content> : View where Content: View { public struct WebImage<Content> : View where Content: View {
var transaction: Transaction
var configurations: [(Image) -> Image] = [] var configurations: [(Image) -> Image] = []
var content: (WebImagePhase) -> Content var content: (WebImagePhase) -> Content
@ -146,10 +144,10 @@ public struct WebImage<Content> : View where Content: View {
imageModel.context = context imageModel.context = context
_imageModel = ObservedObject(wrappedValue: imageModel) _imageModel = ObservedObject(wrappedValue: imageModel)
let imageManager = ImageManager() let imageManager = ImageManager()
imageManager.transaction = transaction
_imageManager = StateObject(wrappedValue: imageManager) _imageManager = StateObject(wrappedValue: imageManager)
_indicatorStatus = ObservedObject(wrappedValue: imageManager.indicatorStatus) _indicatorStatus = ObservedObject(wrappedValue: imageManager.indicatorStatus)
self.transaction = transaction
self.content = { phase in self.content = { phase in
content(phase) content(phase)
} }