Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the new way for AnimatedImageViewWrapper, fix the transition visual jump between placeholderImage and final image #325

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 50 additions & 52 deletions SDWebImageSwiftUI/Classes/AnimatedImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,26 +218,29 @@ public struct AnimatedImage : PlatformViewRepresentable {
updateView(uiView, context: context)
}

// public func sizeThatFits(_ proposal: ProposedViewSize, uiView: AnimatedImageViewWrapper, context: Context) -> CGSize? {}
// func _overrideSizeThatFits(_ size: inout CGSize, in proposedSize: _ProposedSize, uiView: UIViewType) {}

public static func dismantleUIView(_ uiView: AnimatedImageViewWrapper, coordinator: Coordinator) {
dismantleView(uiView, coordinator: coordinator)
}
#endif

func setupIndicator(_ view: AnimatedImageViewWrapper, context: Context) {
view.wrapped.sd_imageIndicator = imageConfiguration.indicator
view.wrapped.sd_imageTransition = imageConfiguration.transition
view.sd_imageIndicator = imageConfiguration.indicator
view.sd_imageTransition = imageConfiguration.transition
if let placeholderView = imageModel.placeholderView {
placeholderView.removeFromSuperview()
placeholderView.isHidden = true
// Placeholder View should below the Indicator View
if let indicatorView = imageConfiguration.indicator?.indicatorView {
#if os(macOS)
view.wrapped.addSubview(placeholderView, positioned: .below, relativeTo: indicatorView)
view.addSubview(placeholderView, positioned: .below, relativeTo: indicatorView)
#else
view.wrapped.insertSubview(placeholderView, belowSubview: indicatorView)
view.insertSubview(placeholderView, belowSubview: indicatorView)
#endif
} else {
view.wrapped.addSubview(placeholderView)
view.addSubview(placeholderView)
}
placeholderView.bindFrameToSuperviewBounds()
}
Expand All @@ -253,7 +256,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
}
var webContext = imageModel.webContext ?? [:]
webContext[.animatedImageClass] = SDAnimatedImage.self
view.wrapped.sd_internalSetImage(with: imageModel.url, placeholderImage: imageModel.placeholderImage, options: webOptions, context: webContext, setImageBlock: nil, progress: { (receivedSize, expectedSize, _) in
view.sd_internalSetImage(with: imageModel.url, placeholderImage: imageModel.placeholderImage, options: webOptions, context: webContext, setImageBlock: nil, progress: { (receivedSize, expectedSize, _) in
let progress: Double
if (expectedSize > 0) {
progress = Double(receivedSize) / Double(expectedSize)
Expand All @@ -276,14 +279,14 @@ public struct AnimatedImage : PlatformViewRepresentable {
self.imageHandler.failureBlock?(error ?? NSError())
}
// Finished loading, async
finishUpdateView(view, context: context, image: image)
finishUpdateView(view, context: context)
}
}

func makeView(context: Context) -> AnimatedImageViewWrapper {
let view = AnimatedImageViewWrapper()
if let viewCreateBlock = imageHandler.viewCreateBlock {
viewCreateBlock(view.wrapped, context)
viewCreateBlock(view, context)
}
return view
}
Expand All @@ -308,7 +311,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
}
#endif
context.coordinator.imageLoading.imageName = name
view.wrapped.image = image
view.image = image
}

private func updateViewForData(_ data: Data?, view: AnimatedImageViewWrapper, context: Context) {
Expand All @@ -321,7 +324,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
image = PlatformImage.sd_image(with: data, scale: imageModel.scale)
}
context.coordinator.imageLoading.imageData = data
view.wrapped.image = image
view.image = image
}

private func updateViewForURL(_ url: URL?, view: AnimatedImageViewWrapper, context: Context) {
Expand All @@ -337,7 +340,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
shouldLoad = false
} else if let image = context.coordinator.imageLoading.image {
shouldLoad = false
view.wrapped.image = image
view.image = image
} else {
shouldLoad = true
}
Expand All @@ -364,32 +367,27 @@ public struct AnimatedImage : PlatformViewRepresentable {
}

// Finished loading, sync
finishUpdateView(view, context: context, image: view.wrapped.image)
finishUpdateView(view, context: context)

if let viewUpdateBlock = imageHandler.viewUpdateBlock {
viewUpdateBlock(view.wrapped, context)
viewUpdateBlock(view, context)
}
}

static func dismantleView(_ view: AnimatedImageViewWrapper, coordinator: Coordinator) {
view.wrapped.sd_cancelCurrentImageLoad()
view.sd_cancelCurrentImageLoad()
#if os(macOS)
view.wrapped.animates = false
view.animates = false
#else
view.wrapped.stopAnimating()
view.stopAnimating()
#endif
if let viewDestroyBlock = viewDestroyBlock {
viewDestroyBlock(view.wrapped, coordinator)
viewDestroyBlock(view, coordinator)
}
}

func finishUpdateView(_ view: AnimatedImageViewWrapper, context: Context, image: PlatformImage?) {
func finishUpdateView(_ view: AnimatedImageViewWrapper, context: Context) {
// Finished loading
if let imageSize = image?.size {
view.imageSize = imageSize
} else {
view.imageSize = nil
}
configureView(view, context: context)
layoutView(view, context: context)
}
Expand Down Expand Up @@ -436,16 +434,16 @@ public struct AnimatedImage : PlatformViewRepresentable {
}

#if os(macOS)
view.wrapped.imageScaling = contentMode
view.imageScaling = contentMode
#else
view.wrapped.contentMode = contentMode
view.contentMode = contentMode
#endif

// Resizable
view.resizingMode = imageLayout.resizingMode

// Animated Image does not support resizing mode and rendering mode
if let image = view.wrapped.image {
if let image = view.image {
var image = image
// ResizingMode
if let resizingMode = imageLayout.resizingMode, imageLayout.capInsets != EdgeInsets() {
Expand All @@ -462,15 +460,15 @@ public struct AnimatedImage : PlatformViewRepresentable {
#else
image = image.resizableImage(withCapInsets: capInsets, resizingMode: .stretch)
#endif
view.wrapped.image = image
view.image = image
case .tile:
#if os(macOS)
image.resizingMode = .tile
image.capInsets = capInsets
#else
image = image.resizableImage(withCapInsets: capInsets, resizingMode: .tile)
#endif
view.wrapped.image = image
view.image = image
@unknown default:
// Future cases, not implements
break
Expand All @@ -486,14 +484,14 @@ public struct AnimatedImage : PlatformViewRepresentable {
#else
image = image.withRenderingMode(.alwaysTemplate)
#endif
view.wrapped.image = image
view.image = image
case .original:
#if os(macOS)
image.isTemplate = false
#else
image = image.withRenderingMode(.alwaysOriginal)
#endif
view.wrapped.image = image
view.image = image
@unknown default:
// Future cases, not implements
break
Expand Down Expand Up @@ -529,74 +527,74 @@ public struct AnimatedImage : PlatformViewRepresentable {
func configureView(_ view: AnimatedImageViewWrapper, context: Context) {
// IncrementalLoad
if let incrementalLoad = imageConfiguration.incrementalLoad {
view.wrapped.shouldIncrementalLoad = incrementalLoad
view.shouldIncrementalLoad = incrementalLoad
} else {
view.wrapped.shouldIncrementalLoad = true
view.shouldIncrementalLoad = true
}

// MaxBufferSize
if let maxBufferSize = imageConfiguration.maxBufferSize {
view.wrapped.maxBufferSize = maxBufferSize
view.maxBufferSize = maxBufferSize
} else {
// automatically
view.wrapped.maxBufferSize = 0
view.maxBufferSize = 0
}

// CustomLoopCount
if let customLoopCount = imageConfiguration.customLoopCount {
view.wrapped.shouldCustomLoopCount = true
view.wrapped.animationRepeatCount = Int(customLoopCount)
view.shouldCustomLoopCount = true
view.animationRepeatCount = Int(customLoopCount)
} else {
// disable custom loop count
view.wrapped.shouldCustomLoopCount = false
view.shouldCustomLoopCount = false
}

// RunLoop Mode
if let runLoopMode = imageConfiguration.runLoopMode {
view.wrapped.runLoopMode = runLoopMode
view.runLoopMode = runLoopMode
} else {
view.wrapped.runLoopMode = .common
view.runLoopMode = .common
}

// Pausable
if let pausable = imageConfiguration.pausable {
view.wrapped.resetFrameIndexWhenStopped = !pausable
view.resetFrameIndexWhenStopped = !pausable
} else {
view.wrapped.resetFrameIndexWhenStopped = false
view.resetFrameIndexWhenStopped = false
}

// Clear Buffer
if let purgeable = imageConfiguration.purgeable {
view.wrapped.clearBufferWhenStopped = purgeable
view.clearBufferWhenStopped = purgeable
} else {
view.wrapped.clearBufferWhenStopped = false
view.clearBufferWhenStopped = false
}

// Playback Rate
if let playbackRate = imageConfiguration.playbackRate {
view.wrapped.playbackRate = playbackRate
view.playbackRate = playbackRate
} else {
view.wrapped.playbackRate = 1.0
view.playbackRate = 1.0
}

// Playback Mode
if let playbackMode = imageConfiguration.playbackMode {
view.wrapped.playbackMode = playbackMode
view.playbackMode = playbackMode
} else {
view.wrapped.playbackMode = .normal
view.playbackMode = .normal
}

// Animation
#if os(macOS)
if self.isAnimating != view.wrapped.animates {
view.wrapped.animates = self.isAnimating
if self.isAnimating != view.animates {
view.animates = self.isAnimating
}
#else
if self.isAnimating != view.wrapped.isAnimating {
if self.isAnimating != view.isAnimating {
if self.isAnimating {
view.wrapped.startAnimating()
view.startAnimating()
} else {
view.wrapped.stopAnimating()
view.stopAnimating()
}
}
#endif
Expand Down
27 changes: 15 additions & 12 deletions SDWebImageSwiftUI/Classes/ImageManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public final class ImageManager : ObservableObject {
weak var currentOperation: SDWebImageOperation? = nil

var currentURL: URL?
var transaction = Transaction()
var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)?
var failureBlock: ((Error) -> Void)?
var progressBlock: ((Int, Int) -> Void)?
Expand Down Expand Up @@ -106,18 +107,20 @@ public final class ImageManager : ObservableObject {
// So previous View struct call `onDisappear` and cancel the currentOperation
return
}
self.image = image
self.error = error
self.isIncremental = !finished
if finished {
self.imageData = data
self.cacheType = cacheType
self.indicatorStatus.isLoading = false
self.indicatorStatus.progress = 1
if let image = image {
self.successBlock?(image, data, cacheType)
} else {
self.failureBlock?(error ?? NSError())
withTransaction(transaction) {
self.image = image
self.error = error
self.isIncremental = !finished
if finished {
self.imageData = data
self.cacheType = cacheType
self.indicatorStatus.isLoading = false
self.indicatorStatus.progress = 1
if let image = image {
self.successBlock?(image, data, cacheType)
} else {
self.failureBlock?(error ?? NSError())
}
}
}
}
Expand Down
37 changes: 2 additions & 35 deletions SDWebImageSwiftUI/Classes/ImageViewWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@ import SwiftUI

/// Use wrapper to solve tne `UIImageView`/`NSImageView` frame size become image size issue (SwiftUI's Bug)
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public class AnimatedImageViewWrapper : PlatformView {
/// The wrapped actual image view, using SDWebImage's aniamted image view
public var wrapped = SDAnimatedImageView()
public class AnimatedImageViewWrapper : SDAnimatedImageView {
var interpolationQuality = CGInterpolationQuality.default
var shouldAntialias = false
var resizingMode: Image.ResizingMode?
var imageSize: CGSize?

public override func draw(_ rect: CGRect) {
#if os(macOS)
Expand All @@ -36,29 +33,9 @@ public class AnimatedImageViewWrapper : PlatformView {
ctx.setShouldAntialias(shouldAntialias)
}

#if os(macOS)
public override func layout() {
super.layout()
wrapped.frame = self.bounds
}
#else
public override func layoutSubviews() {
super.layoutSubviews()
wrapped.frame = self.bounds
}
#endif

public override var intrinsicContentSize: CGSize {
/// Match the behavior of SwiftUI.Image, only when image is resizable, use the super implementation to calculate size
var 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
}
}
let contentSize = super.intrinsicContentSize
if let _ = resizingMode {
/// Keep aspect ratio
if contentSize.width > 0 && contentSize.height > 0 {
Expand All @@ -73,16 +50,6 @@ public class AnimatedImageViewWrapper : PlatformView {
return contentSize
}
}

public override init(frame frameRect: CGRect) {
super.init(frame: frameRect)
addSubview(wrapped)
}

public required init?(coder: NSCoder) {
super.init(coder: coder)
addSubview(wrapped)
}
}

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
Expand Down
Loading
Loading