Merge pull request #324 from SDWebImage/bugfix/animatedimage_aspect_ratio_related_issues
Re-implements the aspectRatio support on AnimatedImage, fix issue like cornerRadius
This commit is contained in:
commit
1ba96a0a8a
|
@ -17,6 +17,19 @@ class UserSettings: ObservableObject {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(watchOS)
|
||||||
|
struct ContentView4: View {
|
||||||
|
var url = URL(string: "https://github.com/SDWebImage/SDWebImageSwiftUI/assets/97430818/72d27f90-e9d8-48d7-b144-82ada828a027")!
|
||||||
|
var body: some View {
|
||||||
|
AnimatedImage(url: url)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
// .aspectRatio(nil, contentMode: .fit)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 50, style: .continuous))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Test Switching nil url
|
// Test Switching nil url
|
||||||
struct ContentView3: View {
|
struct ContentView3: View {
|
||||||
@State var isOn = false
|
@State var isOn = false
|
||||||
|
|
|
@ -275,6 +275,8 @@ public struct AnimatedImage : PlatformViewRepresentable {
|
||||||
self.imageModel.placeholderView?.isHidden = false
|
self.imageModel.placeholderView?.isHidden = false
|
||||||
self.imageHandler.failureBlock?(error ?? NSError())
|
self.imageHandler.failureBlock?(error ?? NSError())
|
||||||
}
|
}
|
||||||
|
// Finished loading, async
|
||||||
|
finishUpdateView(view, context: context, image: image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,22 +363,9 @@ public struct AnimatedImage : PlatformViewRepresentable {
|
||||||
break // impossible
|
break // impossible
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(macOS)
|
// Finished loading, sync
|
||||||
if self.isAnimating != view.wrapped.animates {
|
finishUpdateView(view, context: context, image: view.wrapped.image)
|
||||||
view.wrapped.animates = self.isAnimating
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if self.isAnimating != view.wrapped.isAnimating {
|
|
||||||
if self.isAnimating {
|
|
||||||
view.wrapped.startAnimating()
|
|
||||||
} else {
|
|
||||||
view.wrapped.stopAnimating()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
configureView(view, context: context)
|
|
||||||
layoutView(view, context: context)
|
|
||||||
if let viewUpdateBlock = imageHandler.viewUpdateBlock {
|
if let viewUpdateBlock = imageHandler.viewUpdateBlock {
|
||||||
viewUpdateBlock(view.wrapped, context)
|
viewUpdateBlock(view.wrapped, context)
|
||||||
}
|
}
|
||||||
|
@ -394,6 +383,17 @@ public struct AnimatedImage : PlatformViewRepresentable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func finishUpdateView(_ view: AnimatedImageViewWrapper, context: Context, image: PlatformImage?) {
|
||||||
|
// Finished loading
|
||||||
|
if let imageSize = image?.size {
|
||||||
|
view.imageSize = imageSize
|
||||||
|
} else {
|
||||||
|
view.imageSize = nil
|
||||||
|
}
|
||||||
|
configureView(view, context: context)
|
||||||
|
layoutView(view, context: context)
|
||||||
|
}
|
||||||
|
|
||||||
func layoutView(_ view: AnimatedImageViewWrapper, context: Context) {
|
func layoutView(_ view: AnimatedImageViewWrapper, context: Context) {
|
||||||
// AspectRatio && ContentMode
|
// AspectRatio && ContentMode
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
@ -442,9 +442,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Resizable
|
// Resizable
|
||||||
if let _ = imageLayout.resizingMode {
|
view.resizingMode = imageLayout.resizingMode
|
||||||
view.resizable = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animated Image does not support resizing mode and rendering mode
|
// Animated Image does not support resizing mode and rendering mode
|
||||||
if let image = view.wrapped.image {
|
if let image = view.wrapped.image {
|
||||||
|
@ -587,6 +585,21 @@ public struct AnimatedImage : PlatformViewRepresentable {
|
||||||
} else {
|
} else {
|
||||||
view.wrapped.playbackMode = .normal
|
view.wrapped.playbackMode = .normal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Animation
|
||||||
|
#if os(macOS)
|
||||||
|
if self.isAnimating != view.wrapped.animates {
|
||||||
|
view.wrapped.animates = self.isAnimating
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if self.isAnimating != view.wrapped.isAnimating {
|
||||||
|
if self.isAnimating {
|
||||||
|
view.wrapped.startAnimating()
|
||||||
|
} else {
|
||||||
|
view.wrapped.stopAnimating()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,68 +643,6 @@ extension AnimatedImage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aspect Ratio
|
|
||||||
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
|
|
||||||
extension AnimatedImage {
|
|
||||||
func setImageLayoutAspectRatio(_ aspectRatio: CGFloat?, contentMode: ContentMode) {
|
|
||||||
self.imageLayout.aspectRatio = aspectRatio
|
|
||||||
self.imageLayout.contentMode = contentMode
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constrains this view's dimensions to the specified aspect ratio.
|
|
||||||
/// - Parameters:
|
|
||||||
/// - aspectRatio: The ratio of width to height to use for the resulting
|
|
||||||
/// view. If `aspectRatio` is `nil`, the resulting view maintains this
|
|
||||||
/// view's aspect ratio.
|
|
||||||
/// - contentMode: A flag indicating whether this view should fit or
|
|
||||||
/// fill the parent context.
|
|
||||||
/// - Returns: A view that constrains this view's dimensions to
|
|
||||||
/// `aspectRatio`, using `contentMode` as its scaling algorithm.
|
|
||||||
@ViewBuilder
|
|
||||||
public func aspectRatio(_ aspectRatio: CGFloat? = nil, contentMode: ContentMode) -> some View {
|
|
||||||
// The `SwifUI.View.aspectRatio(_:contentMode:)` says:
|
|
||||||
// If `aspectRatio` is `nil`, the resulting view maintains this view's aspect ratio
|
|
||||||
// But 1: there are no public API to declare what `this view's aspect ratio` is
|
|
||||||
// So, if we don't override this method, SwiftUI ignore the content mode on actual ImageView
|
|
||||||
// To workaround, we want to call the default `SwifUI.View.aspectRatio(_:contentMode:)` method
|
|
||||||
// But 2: there are no way to call a Protocol Extention default implementation in Swift 5.1
|
|
||||||
// So, we directly call the implementation detail modifier instead
|
|
||||||
// Fired Radar: FB7413534
|
|
||||||
let _ = self.setImageLayoutAspectRatio(aspectRatio, contentMode: contentMode)
|
|
||||||
if let aspectRatio {
|
|
||||||
self.modifier(_AspectRatioLayout(aspectRatio: aspectRatio, contentMode: contentMode))
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constrains this view's dimensions to the aspect ratio of the given size.
|
|
||||||
/// - Parameters:
|
|
||||||
/// - aspectRatio: A size specifying the ratio of width to height to use
|
|
||||||
/// for the resulting view.
|
|
||||||
/// - contentMode: A flag indicating whether this view should fit or
|
|
||||||
/// fill the parent context.
|
|
||||||
/// - Returns: A view that constrains this view's dimensions to
|
|
||||||
/// `aspectRatio`, using `contentMode` as its scaling algorithm.
|
|
||||||
public func aspectRatio(_ aspectRatio: CGSize, contentMode: ContentMode) -> some View {
|
|
||||||
return self.aspectRatio(aspectRatio.width / aspectRatio.height, contentMode: contentMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scales this view to fit its parent.
|
|
||||||
/// - Returns: A view that scales this view to fit its parent,
|
|
||||||
/// maintaining this view's aspect ratio.
|
|
||||||
public func scaledToFit() -> some View {
|
|
||||||
return self.aspectRatio(nil, contentMode: .fit)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scales this view to fill its parent.
|
|
||||||
/// - Returns: A view that scales this view to fit its parent,
|
|
||||||
/// maintaining this view's aspect ratio.
|
|
||||||
public func scaledToFill() -> some View {
|
|
||||||
return self.aspectRatio(nil, contentMode: .fill)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AnimatedImage Modifier
|
// AnimatedImage Modifier
|
||||||
@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, *)
|
||||||
extension AnimatedImage {
|
extension AnimatedImage {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SDWebImage
|
import SDWebImage
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
#if !os(watchOS)
|
#if !os(watchOS)
|
||||||
|
|
||||||
|
@ -18,7 +19,8 @@ public class AnimatedImageViewWrapper : PlatformView {
|
||||||
public var wrapped = SDAnimatedImageView()
|
public var wrapped = SDAnimatedImageView()
|
||||||
var interpolationQuality = CGInterpolationQuality.default
|
var interpolationQuality = CGInterpolationQuality.default
|
||||||
var shouldAntialias = false
|
var shouldAntialias = false
|
||||||
var resizable = false
|
var resizingMode: Image.ResizingMode?
|
||||||
|
var imageSize: CGSize?
|
||||||
|
|
||||||
public override func draw(_ rect: CGRect) {
|
public override func draw(_ rect: CGRect) {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
@ -48,11 +50,27 @@ 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
|
||||||
if resizable {
|
var contentSize = wrapped.intrinsicContentSize
|
||||||
return super.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 {
|
||||||
|
/// Keep aspect ratio
|
||||||
|
if contentSize.width > 0 && contentSize.height > 0 {
|
||||||
|
let ratio = contentSize.width / contentSize.height
|
||||||
|
let size = CGSize(width: ratio, height: 1)
|
||||||
|
return size
|
||||||
|
} else {
|
||||||
|
return contentSize
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/// Not resizable, always use image size, like SwiftUI.Image
|
/// Not resizable, always use image size, like SwiftUI.Image
|
||||||
return wrapped.intrinsicContentSize
|
return contentSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue