Skip to content

Commit 1ba96a0

Browse files
authored
Merge pull request #324 from SDWebImage/bugfix/animatedimage_aspect_ratio_related_issues
Re-implements the aspectRatio support on AnimatedImage, fix issue like cornerRadius
2 parents 03c468b + c8320d4 commit 1ba96a0

File tree

3 files changed

+66
-84
lines changed

3 files changed

+66
-84
lines changed

Example/SDWebImageSwiftUIDemo/ContentView.swift

+13
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ class UserSettings: ObservableObject {
1717
#endif
1818
}
1919

20+
#if !os(watchOS)
21+
struct ContentView4: View {
22+
var url = URL(string: "https://github.com/SDWebImage/SDWebImageSwiftUI/assets/97430818/72d27f90-e9d8-48d7-b144-82ada828a027")!
23+
var body: some View {
24+
AnimatedImage(url: url)
25+
.resizable()
26+
.scaledToFit()
27+
// .aspectRatio(nil, contentMode: .fit)
28+
.clipShape(RoundedRectangle(cornerRadius: 50, style: .continuous))
29+
}
30+
}
31+
#endif
32+
2033
// Test Switching nil url
2134
struct ContentView3: View {
2235
@State var isOn = false

SDWebImageSwiftUI/Classes/AnimatedImage.swift

+31-80
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ public struct AnimatedImage : PlatformViewRepresentable {
275275
self.imageModel.placeholderView?.isHidden = false
276276
self.imageHandler.failureBlock?(error ?? NSError())
277277
}
278+
// Finished loading, async
279+
finishUpdateView(view, context: context, image: image)
278280
}
279281
}
280282

@@ -361,22 +363,9 @@ public struct AnimatedImage : PlatformViewRepresentable {
361363
break // impossible
362364
}
363365

364-
#if os(macOS)
365-
if self.isAnimating != view.wrapped.animates {
366-
view.wrapped.animates = self.isAnimating
367-
}
368-
#else
369-
if self.isAnimating != view.wrapped.isAnimating {
370-
if self.isAnimating {
371-
view.wrapped.startAnimating()
372-
} else {
373-
view.wrapped.stopAnimating()
374-
}
375-
}
376-
#endif
366+
// Finished loading, sync
367+
finishUpdateView(view, context: context, image: view.wrapped.image)
377368

378-
configureView(view, context: context)
379-
layoutView(view, context: context)
380369
if let viewUpdateBlock = imageHandler.viewUpdateBlock {
381370
viewUpdateBlock(view.wrapped, context)
382371
}
@@ -394,6 +383,17 @@ public struct AnimatedImage : PlatformViewRepresentable {
394383
}
395384
}
396385

386+
func finishUpdateView(_ view: AnimatedImageViewWrapper, context: Context, image: PlatformImage?) {
387+
// Finished loading
388+
if let imageSize = image?.size {
389+
view.imageSize = imageSize
390+
} else {
391+
view.imageSize = nil
392+
}
393+
configureView(view, context: context)
394+
layoutView(view, context: context)
395+
}
396+
397397
func layoutView(_ view: AnimatedImageViewWrapper, context: Context) {
398398
// AspectRatio && ContentMode
399399
#if os(macOS)
@@ -442,9 +442,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
442442
#endif
443443

444444
// Resizable
445-
if let _ = imageLayout.resizingMode {
446-
view.resizable = true
447-
}
445+
view.resizingMode = imageLayout.resizingMode
448446

449447
// Animated Image does not support resizing mode and rendering mode
450448
if let image = view.wrapped.image {
@@ -587,6 +585,21 @@ public struct AnimatedImage : PlatformViewRepresentable {
587585
} else {
588586
view.wrapped.playbackMode = .normal
589587
}
588+
589+
// Animation
590+
#if os(macOS)
591+
if self.isAnimating != view.wrapped.animates {
592+
view.wrapped.animates = self.isAnimating
593+
}
594+
#else
595+
if self.isAnimating != view.wrapped.isAnimating {
596+
if self.isAnimating {
597+
view.wrapped.startAnimating()
598+
} else {
599+
view.wrapped.stopAnimating()
600+
}
601+
}
602+
#endif
590603
}
591604
}
592605

@@ -630,68 +643,6 @@ extension AnimatedImage {
630643
}
631644
}
632645

633-
// Aspect Ratio
634-
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
635-
extension AnimatedImage {
636-
func setImageLayoutAspectRatio(_ aspectRatio: CGFloat?, contentMode: ContentMode) {
637-
self.imageLayout.aspectRatio = aspectRatio
638-
self.imageLayout.contentMode = contentMode
639-
}
640-
641-
/// Constrains this view's dimensions to the specified aspect ratio.
642-
/// - Parameters:
643-
/// - aspectRatio: The ratio of width to height to use for the resulting
644-
/// view. If `aspectRatio` is `nil`, the resulting view maintains this
645-
/// view's aspect ratio.
646-
/// - contentMode: A flag indicating whether this view should fit or
647-
/// fill the parent context.
648-
/// - Returns: A view that constrains this view's dimensions to
649-
/// `aspectRatio`, using `contentMode` as its scaling algorithm.
650-
@ViewBuilder
651-
public func aspectRatio(_ aspectRatio: CGFloat? = nil, contentMode: ContentMode) -> some View {
652-
// The `SwifUI.View.aspectRatio(_:contentMode:)` says:
653-
// If `aspectRatio` is `nil`, the resulting view maintains this view's aspect ratio
654-
// But 1: there are no public API to declare what `this view's aspect ratio` is
655-
// So, if we don't override this method, SwiftUI ignore the content mode on actual ImageView
656-
// To workaround, we want to call the default `SwifUI.View.aspectRatio(_:contentMode:)` method
657-
// But 2: there are no way to call a Protocol Extention default implementation in Swift 5.1
658-
// So, we directly call the implementation detail modifier instead
659-
// Fired Radar: FB7413534
660-
let _ = self.setImageLayoutAspectRatio(aspectRatio, contentMode: contentMode)
661-
if let aspectRatio {
662-
self.modifier(_AspectRatioLayout(aspectRatio: aspectRatio, contentMode: contentMode))
663-
} else {
664-
self
665-
}
666-
}
667-
668-
/// Constrains this view's dimensions to the aspect ratio of the given size.
669-
/// - Parameters:
670-
/// - aspectRatio: A size specifying the ratio of width to height to use
671-
/// for the resulting view.
672-
/// - contentMode: A flag indicating whether this view should fit or
673-
/// fill the parent context.
674-
/// - Returns: A view that constrains this view's dimensions to
675-
/// `aspectRatio`, using `contentMode` as its scaling algorithm.
676-
public func aspectRatio(_ aspectRatio: CGSize, contentMode: ContentMode) -> some View {
677-
return self.aspectRatio(aspectRatio.width / aspectRatio.height, contentMode: contentMode)
678-
}
679-
680-
/// Scales this view to fit its parent.
681-
/// - Returns: A view that scales this view to fit its parent,
682-
/// maintaining this view's aspect ratio.
683-
public func scaledToFit() -> some View {
684-
return self.aspectRatio(nil, contentMode: .fit)
685-
}
686-
687-
/// Scales this view to fill its parent.
688-
/// - Returns: A view that scales this view to fit its parent,
689-
/// maintaining this view's aspect ratio.
690-
public func scaledToFill() -> some View {
691-
return self.aspectRatio(nil, contentMode: .fill)
692-
}
693-
}
694-
695646
// AnimatedImage Modifier
696647
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
697648
extension AnimatedImage {

SDWebImageSwiftUI/Classes/ImageViewWrapper.swift

+22-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import Foundation
1010
import SDWebImage
11+
import SwiftUI
1112

1213
#if !os(watchOS)
1314

@@ -18,7 +19,8 @@ public class AnimatedImageViewWrapper : PlatformView {
1819
public var wrapped = SDAnimatedImageView()
1920
var interpolationQuality = CGInterpolationQuality.default
2021
var shouldAntialias = false
21-
var resizable = false
22+
var resizingMode: Image.ResizingMode?
23+
var imageSize: CGSize?
2224

2325
public override func draw(_ rect: CGRect) {
2426
#if os(macOS)
@@ -48,11 +50,27 @@ public class AnimatedImageViewWrapper : PlatformView {
4850

4951
public override var intrinsicContentSize: CGSize {
5052
/// Match the behavior of SwiftUI.Image, only when image is resizable, use the super implementation to calculate size
51-
if resizable {
52-
return super.intrinsicContentSize
53+
var contentSize = wrapped.intrinsicContentSize
54+
/// Sometimes, like during the transaction, the wrapped.image == nil, which cause contentSize invalid
55+
/// Use image size as backup
56+
/// TODO: This mixed use of UIKit/SwiftUI animation will cause visial issue because the intrinsicContentSize during animation may be changed
57+
if let imageSize = imageSize {
58+
if contentSize != imageSize {
59+
contentSize = imageSize
60+
}
61+
}
62+
if let _ = resizingMode {
63+
/// Keep aspect ratio
64+
if contentSize.width > 0 && contentSize.height > 0 {
65+
let ratio = contentSize.width / contentSize.height
66+
let size = CGSize(width: ratio, height: 1)
67+
return size
68+
} else {
69+
return contentSize
70+
}
5371
} else {
5472
/// Not resizable, always use image size, like SwiftUI.Image
55-
return wrapped.intrinsicContentSize
73+
return contentSize
5674
}
5775
}
5876

0 commit comments

Comments
 (0)