From 8b26cb7d0d72189babffbd9d5c389aeb784c7222 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 29 Apr 2024 18:13:46 +0800 Subject: [PATCH 01/19] Released v3.0.3 version --- CHANGELOG.md | 4 ++++ SDWebImageSwiftUI.podspec | 2 +- SDWebImageSwiftUI/Module/Info.plist | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 028fe23a..5337d231 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.0.3] - 2024-04-29 +- Added totally empty privacy manifest #315 +- People who facing the issue because of Privacy Manifest declaration during ITC validation can try this version + ## [3.0.2] - 2024-03-27 - Fix the assert crash then when using Data/Name in AnimatedImage #309 diff --git a/SDWebImageSwiftUI.podspec b/SDWebImageSwiftUI.podspec index 0983512d..c80c5e0c 100644 --- a/SDWebImageSwiftUI.podspec +++ b/SDWebImageSwiftUI.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageSwiftUI' - s.version = '3.0.2' + s.version = '3.0.3' s.summary = 'SwiftUI Image loading and Animation framework powered by SDWebImage' s.description = <<-DESC diff --git a/SDWebImageSwiftUI/Module/Info.plist b/SDWebImageSwiftUI/Module/Info.plist index bf7e0944..8b0ce867 100644 --- a/SDWebImageSwiftUI/Module/Info.plist +++ b/SDWebImageSwiftUI/Module/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 3.0.2 + 3.0.3 CFBundleVersion $(CURRENT_PROJECT_VERSION) From 5f8a8acf092b3616ee4460df991d06560b9721f5 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 29 Apr 2024 18:19:45 +0800 Subject: [PATCH 02/19] Trying to move the initial state setup before `onAppear` to fix the watchOS switching url or any other state issue This maybe a behavior changes, need testing --- SDWebImageSwiftUI.xcodeproj/project.pbxproj | 12 --- SDWebImageSwiftUI/Classes/ImageManager.swift | 50 +++++++--- .../Classes/Indicator/Indicator.swift | 16 +++- .../Classes/SwiftUICompatibility.swift | 92 ------------------- SDWebImageSwiftUI/Classes/WebImage.swift | 24 +++-- 5 files changed, 68 insertions(+), 126 deletions(-) delete mode 100644 SDWebImageSwiftUI/Classes/SwiftUICompatibility.swift diff --git a/SDWebImageSwiftUI.xcodeproj/project.pbxproj b/SDWebImageSwiftUI.xcodeproj/project.pbxproj index 4561eb49..61bba7e6 100644 --- a/SDWebImageSwiftUI.xcodeproj/project.pbxproj +++ b/SDWebImageSwiftUI.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 3243AFE62AA37EFF0049A43B /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; }; 3243AFE72AA37EFF0049A43B /* WebImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C43DDE22FD54C600BE87F5 /* WebImage.swift */; }; 3243AFE82AA37EFF0049A43B /* ImagePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBA77E25E4D7D800C6A8DC /* ImagePlayer.swift */; }; 3243AFE92AA37EFF0049A43B /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; }; @@ -26,10 +25,6 @@ 326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; }; 326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; }; 329885EE2AA37FCB0071F2BA /* SDWebImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 329885ED2AA37FCB0071F2BA /* SDWebImage.framework */; }; - 32B79C9528DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; }; - 32B79C9628DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; }; - 32B79C9728DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; }; - 32B79C9828DB40430088C432 /* SwiftUICompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */; }; 32B933E523659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; }; 32B933E623659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; }; 32B933E723659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; }; @@ -83,7 +78,6 @@ 326B84812363350C0011BDFB /* Indicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Indicator.swift; sourceTree = ""; }; 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewWrapper.swift; sourceTree = ""; }; 329885ED2AA37FCB0071F2BA /* SDWebImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDWebImage.framework; path = Carthage/Build/visionOS/SDWebImage.framework; sourceTree = ""; }; - 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUICompatibility.swift; sourceTree = ""; }; 32B933E423659A1900BB7CAD /* Transition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transition.swift; sourceTree = ""; }; 32C43DCC22FD540D00BE87F5 /* SDWebImageSwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SDWebImageSwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 32C43DDC22FD54C600BE87F5 /* ImageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = ""; }; @@ -221,7 +215,6 @@ 32C43DDE22FD54C600BE87F5 /* WebImage.swift */, 32C43DDF22FD54C600BE87F5 /* AnimatedImage.swift */, 32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */, - 32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */, 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */, 32D26A012446B546005905DA /* Image.swift */, ); @@ -491,7 +484,6 @@ 3243AFEB2AA37EFF0049A43B /* AnimatedImage.swift in Sources */, 3243AFE82AA37EFF0049A43B /* ImagePlayer.swift in Sources */, 3243AFED2AA37EFF0049A43B /* SDWebImageSwiftUI.swift in Sources */, - 3243AFE62AA37EFF0049A43B /* SwiftUICompatibility.swift in Sources */, 3243AFEE2AA37F010049A43B /* Indicator.swift in Sources */, 3243AFEA2AA37EFF0049A43B /* Image.swift in Sources */, ); @@ -507,7 +499,6 @@ 326B84822363350C0011BDFB /* Indicator.swift in Sources */, 32C43E3222FD5DE100BE87F5 /* SDWebImageSwiftUI.swift in Sources */, 326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */, - 32B79C9528DB40430088C432 /* SwiftUICompatibility.swift in Sources */, 32C43E1622FD583700BE87F5 /* ImageManager.swift in Sources */, 32C43E1822FD583700BE87F5 /* AnimatedImage.swift in Sources */, 32D26A022446B546005905DA /* Image.swift in Sources */, @@ -524,7 +515,6 @@ 326B84832363350C0011BDFB /* Indicator.swift in Sources */, 32C43E3322FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */, 326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */, - 32B79C9628DB40430088C432 /* SwiftUICompatibility.swift in Sources */, 32C43E1922FD583700BE87F5 /* ImageManager.swift in Sources */, 32C43E1B22FD583700BE87F5 /* AnimatedImage.swift in Sources */, 32D26A032446B546005905DA /* Image.swift in Sources */, @@ -541,7 +531,6 @@ 326B84842363350C0011BDFB /* Indicator.swift in Sources */, 32C43E3422FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */, 326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */, - 32B79C9728DB40430088C432 /* SwiftUICompatibility.swift in Sources */, 32C43E1C22FD583800BE87F5 /* ImageManager.swift in Sources */, 32C43E1E22FD583800BE87F5 /* AnimatedImage.swift in Sources */, 32D26A042446B546005905DA /* Image.swift in Sources */, @@ -558,7 +547,6 @@ 326B84852363350C0011BDFB /* Indicator.swift in Sources */, 32C43E3522FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */, 326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */, - 32B79C9828DB40430088C432 /* SwiftUICompatibility.swift in Sources */, 32C43E1F22FD583800BE87F5 /* ImageManager.swift in Sources */, 32C43E2122FD583800BE87F5 /* AnimatedImage.swift in Sources */, 32D26A052446B546005905DA /* Image.swift in Sources */, diff --git a/SDWebImageSwiftUI/Classes/ImageManager.swift b/SDWebImageSwiftUI/Classes/ImageManager.swift index 008b0a39..dd2bb8c1 100644 --- a/SDWebImageSwiftUI/Classes/ImageManager.swift +++ b/SDWebImageSwiftUI/Classes/ImageManager.swift @@ -15,17 +15,47 @@ import SDWebImage @available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public final class ImageManager : ObservableObject { /// loaded image, note when progressive loading, this will published multiple times with different partial image - @Published public var image: PlatformImage? + public var image: PlatformImage? { + didSet { + DispatchQueue.main.async { + self.objectWillChange.send() + } + } + } /// loaded image data, may be nil if hit from memory cache. This will only published once even on incremental image loading - @Published public var imageData: Data? + public var imageData: Data? { + didSet { + DispatchQueue.main.async { + self.objectWillChange.send() + } + } + } /// loaded image cache type, .none means from network - @Published public var cacheType: SDImageCacheType = .none + public var cacheType: SDImageCacheType = .none { + didSet { + DispatchQueue.main.async { + self.objectWillChange.send() + } + } + } /// loading error, you can grab the error code and reason listed in `SDWebImageErrorDomain`, to provide a user interface about the error reason - @Published public var error: Error? + public var error: Error? { + didSet { + DispatchQueue.main.async { + self.objectWillChange.send() + } + } + } /// true means during incremental loading - @Published public var isIncremental: Bool = false + public var isIncremental: Bool = false { + didSet { + DispatchQueue.main.async { + self.objectWillChange.send() + } + } + } /// A observed object to pass through the image manager loading status to indicator - @Published public var indicatorStatus = IndicatorStatus() + public var indicatorStatus = IndicatorStatus() weak var currentOperation: SDWebImageOperation? = nil @@ -51,8 +81,8 @@ public final class ImageManager : ObservableObject { return } currentURL = url - indicatorStatus.isLoading = true - indicatorStatus.progress = 0 + self.indicatorStatus.isLoading = true + self.indicatorStatus.progress = 0 currentOperation = manager.loadImage(with: url, options: options, context: context, progress: { [weak self] (receivedSize, expectedSize, _) in guard let self = self else { return @@ -63,9 +93,7 @@ public final class ImageManager : ObservableObject { } else { progress = 0 } - DispatchQueue.main.async { - self.indicatorStatus.progress = progress - } + self.indicatorStatus.progress = progress self.progressBlock?(receivedSize, expectedSize) }) { [weak self] (image, data, error, cacheType, finished, _) in guard let self = self else { diff --git a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift index 26f0162a..739212e1 100644 --- a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift +++ b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift @@ -28,9 +28,21 @@ public struct Indicator where T : View { @available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public class IndicatorStatus : ObservableObject { /// whether indicator is loading or not - @Published var isLoading: Bool = false + var isLoading: Bool = false { + didSet { + DispatchQueue.main.async { + self.objectWillChange.send() + } + } + } /// indicator progress, should only be used for indicator binding, value between [0.0, 1.0] - @Published var progress: Double = 0 + var progress: Double = 0 { + didSet { + DispatchQueue.main.async { + self.objectWillChange.send() + } + } + } } /// A implementation detail View Modifier with indicator diff --git a/SDWebImageSwiftUI/Classes/SwiftUICompatibility.swift b/SDWebImageSwiftUI/Classes/SwiftUICompatibility.swift deleted file mode 100644 index cb784465..00000000 --- a/SDWebImageSwiftUI/Classes/SwiftUICompatibility.swift +++ /dev/null @@ -1,92 +0,0 @@ -/* - * This file is part of the SDWebImage package. - * (c) DreamPiggy - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import Foundation -import SwiftUI - -#if !os(watchOS) - -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) -struct PlatformAppear: PlatformViewRepresentable { - let appearAction: () -> Void - let disappearAction: () -> Void - - #if os(iOS) || os(tvOS) || os(visionOS) - func makeUIView(context: Context) -> some UIView { - let view = PlatformAppearView() - view.appearAction = appearAction - view.disappearAction = disappearAction - return view - } - - func updateUIView(_ uiView: UIViewType, context: Context) {} - #endif - #if os(macOS) - func makeNSView(context: Context) -> some NSView { - let view = PlatformAppearView() - view.appearAction = appearAction - view.disappearAction = disappearAction - return view - } - - func updateNSView(_ nsView: NSViewType, context: Context) {} - #endif -} - -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) -class PlatformAppearView: PlatformView { - var appearAction: () -> Void = {} - var disappearAction: () -> Void = {} - - #if os(iOS) || os(tvOS) - override func willMove(toWindow newWindow: UIWindow?) { - if newWindow != nil { - DispatchQueue.main.async { - self.appearAction() - } - } else { - DispatchQueue.main.async { - self.disappearAction() - } - } - } - #endif - - #if os(macOS) - override func viewWillMove(toWindow newWindow: NSWindow?) { - if newWindow != nil { - DispatchQueue.main.async { - self.appearAction() - } - } else { - DispatchQueue.main.async { - self.disappearAction() - } - } - } - #endif -} - -#endif - -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) -extension View { - /// Used UIKit/AppKit behavior to detect the SwiftUI view's visibility. - /// This hack is because of SwiftUI 1.0/2.0 buggy behavior. The built-in `onAppear` and `onDisappear` is so massive on some cases. Where UIKit/AppKit is solid. - /// - Parameters: - /// - appear: The action when view appears - /// - disappear: The action when view disappears - /// - Returns: Some view - func onPlatformAppear(appear: @escaping () -> Void = {}, disappear: @escaping () -> Void = {}) -> some View { - #if os(iOS) || os(tvOS) || os(macOS) - return self.background(PlatformAppear(appearAction: appear, disappearAction: disappear)) - #else - return self.onAppear(perform: appear).onDisappear(perform: disappear) - #endif - } -} diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index 4b0d091c..19b47798 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -163,26 +163,22 @@ public struct WebImage : View where Content: View { } } else { content((imageManager.error != nil) ? .failure(imageManager.error!) : .empty) - setupPlaceholder() + setupInitialState() // Load Logic - .onPlatformAppear(appear: { - self.setupManager() - if (self.imageManager.error == nil) { - // Load remote image when first appear - self.imageManager.load(url: imageModel.url, options: imageModel.options, context: imageModel.context) - } + .onAppear { guard self.imageConfiguration.retryOnAppear else { return } // When using prorgessive loading, the new partial image will cause onAppear. Filter this case if self.imageManager.error != nil && !self.imageManager.isIncremental { self.imageManager.load(url: imageModel.url, options: imageModel.options, context: imageModel.context) } - }, disappear: { + } + .onDisappear { guard self.imageConfiguration.cancelOnDisappear else { return } // When using prorgessive loading, the previous partial image will cause onDisappear. Filter this case if self.imageManager.error != nil && !self.imageManager.isIncremental { self.imageManager.cancel() } - }) + } } } } @@ -328,6 +324,16 @@ public struct WebImage : View where Content: View { } } + /// Initial state management (update when imageModel.url changed) + func setupInitialState() -> some View { + self.setupManager() + if (self.imageManager.error == nil) { + // Load remote image when first appear + self.imageManager.load(url: imageModel.url, options: imageModel.options, context: imageModel.context) + } + return setupPlaceholder() + } + /// Placeholder View Support func setupPlaceholder() -> some View { let result = content((imageManager.error != nil) ? .failure(imageManager.error!) : .empty) From b7af5e6bd9c2987e41730400d1baad13d74a141a Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 29 Apr 2024 18:31:06 +0800 Subject: [PATCH 03/19] Released v3.0.4 version --- CHANGELOG.md | 4 ++++ SDWebImageSwiftUI.podspec | 2 +- SDWebImageSwiftUI/Module/Info.plist | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5337d231..51271238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.0.4] - 2024-04-30 +- Trying to move the initial state setup before onAppear to fix the watchOS switching url or any other state issue #316 +- This solve a issue in history when sometimes SwiftUI does not trigger the `onAppear` and cause state error, like #312 #314 + ## [3.0.3] - 2024-04-29 - Added totally empty privacy manifest #315 - People who facing the issue because of Privacy Manifest declaration during ITC validation can try this version diff --git a/SDWebImageSwiftUI.podspec b/SDWebImageSwiftUI.podspec index c80c5e0c..158dac83 100644 --- a/SDWebImageSwiftUI.podspec +++ b/SDWebImageSwiftUI.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageSwiftUI' - s.version = '3.0.3' + s.version = '3.0.4' s.summary = 'SwiftUI Image loading and Animation framework powered by SDWebImage' s.description = <<-DESC diff --git a/SDWebImageSwiftUI/Module/Info.plist b/SDWebImageSwiftUI/Module/Info.plist index 8b0ce867..4f3308f9 100644 --- a/SDWebImageSwiftUI/Module/Info.plist +++ b/SDWebImageSwiftUI/Module/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 3.0.3 + 3.0.4 CFBundleVersion $(CURRENT_PROJECT_VERSION) From 075405a3e83cc94d2b71faa5daf5122166521e08 Mon Sep 17 00:00:00 2001 From: woxtu Date: Wed, 12 Jun 2024 20:05:15 +0900 Subject: [PATCH 04/19] Update version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd3bf8be..516eb3b8 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ For downstream framework author, you should create a `Package.swift` file into y ```swift let package = Package( dependencies: [ - .package(url: "https://github.com/SDWebImage/SDWebImageSwiftUI.git", from: "2.0.0") + .package(url: "https://github.com/SDWebImage/SDWebImageSwiftUI.git", from: "3.0.0") ], ) ``` From 522e8bcbdf0e393fb2baa334574cbd1caceefc24 Mon Sep 17 00:00:00 2001 From: woxtu Date: Sat, 15 Jun 2024 18:20:01 +0900 Subject: [PATCH 05/19] Update platform names --- README.md | 2 +- SDWebImageSwiftUI/Classes/AnimatedImage.swift | 28 +++++++++---------- SDWebImageSwiftUI/Classes/Image.swift | 8 +++--- SDWebImageSwiftUI/Classes/ImageManager.swift | 4 +-- SDWebImageSwiftUI/Classes/ImagePlayer.swift | 2 +- .../Classes/ImageViewWrapper.swift | 4 +-- .../Classes/Indicator/Indicator.swift | 10 +++---- .../Classes/SDWebImageSwiftUI.swift | 22 +++++++-------- .../Classes/Transition/Transition.swift | 2 +- SDWebImageSwiftUI/Classes/WebImage.swift | 20 ++++++------- 10 files changed, 51 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index dd3bf8be..8a487e20 100644 --- a/README.md +++ b/README.md @@ -655,7 +655,7 @@ class ViewController: UIViewController { } // ContentView.swift -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) struct ContentView : View { var body: some View { Group { diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift index 6c1b3ba4..3070b0ab 100644 --- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift +++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift @@ -12,7 +12,7 @@ import SDWebImage #if !os(watchOS) /// A coordinator object used for `AnimatedImage`native view bridge for UIKit/AppKit. -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public final class AnimatedImageCoordinator: NSObject { /// Any user-provided object for actual coordinator, such as delegate method, taget-action @@ -25,7 +25,7 @@ public final class AnimatedImageCoordinator: NSObject { } /// Data Binding Object, only properties in this object can support changes from user with @State and refresh -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) final class AnimatedImageModel : ObservableObject { enum Kind { case url @@ -53,7 +53,7 @@ final class AnimatedImageModel : ObservableObject { } /// Loading Binding Object, only properties in this object can support changes from user with @State and refresh -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) final class AnimatedLoadingModel : ObservableObject { @Published var image: PlatformImage? // loaded image, note when progressive loading, this will published multiple times with different partial image @Published var isLoading: Bool = false // whether network is loading or cache is querying, should only be used for indicator binding @@ -66,7 +66,7 @@ final class AnimatedLoadingModel : ObservableObject { } /// Completion Handler Binding Object, supports dynamic @State changes -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) final class AnimatedImageHandler: ObservableObject { // Completion Handler @Published var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)? @@ -78,7 +78,7 @@ final class AnimatedImageHandler: ObservableObject { } /// Layout Binding Object, supports dynamic @State changes -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) final class AnimatedImageLayout : ObservableObject { var contentMode: ContentMode? var aspectRatio: CGFloat? @@ -90,7 +90,7 @@ final class AnimatedImageLayout : ObservableObject { } /// Configuration Binding Object, supports dynamic @State changes -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) final class AnimatedImageConfiguration: ObservableObject { var incrementalLoad: Bool? var maxBufferSize: UInt? @@ -106,7 +106,7 @@ final class AnimatedImageConfiguration: ObservableObject { } /// A Image View type to load image from url, data or bundle. Supports animated and static image format. -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public struct AnimatedImage : PlatformViewRepresentable { @ObservedObject var imageModel: AnimatedImageModel @ObservedObject var imageHandler = AnimatedImageHandler() @@ -591,7 +591,7 @@ public struct AnimatedImage : PlatformViewRepresentable { } // Layout -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Configurate this view's image with the specified cap insets and options. @@ -631,7 +631,7 @@ extension AnimatedImage { } // Aspect Ratio -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@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 @@ -693,7 +693,7 @@ extension AnimatedImage { } // AnimatedImage Modifier -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Total loop count for animated image rendering. Defaults to nil. @@ -770,7 +770,7 @@ extension AnimatedImage { } // Completion Handler -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Provide the action when image load fails. @@ -802,7 +802,7 @@ extension AnimatedImage { } // View Coordinator Handler -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Provide the action when view representable create the native view. @@ -839,7 +839,7 @@ extension SDWebImageIndicator where Self == SDWebImageProgressIndicator { } // Web Image convenience, based on UIKit/AppKit API -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Associate a indicator when loading image with url @@ -860,7 +860,7 @@ extension AnimatedImage { } #if DEBUG -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) struct AnimatedImage_Previews : PreviewProvider { static var previews: some View { Group { diff --git a/SDWebImageSwiftUI/Classes/Image.swift b/SDWebImageSwiftUI/Classes/Image.swift index 2d8400ed..8978df72 100644 --- a/SDWebImageSwiftUI/Classes/Image.swift +++ b/SDWebImageSwiftUI/Classes/Image.swift @@ -9,7 +9,7 @@ import Foundation import SwiftUI -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension Image { @inlinable init(platformImage: PlatformImage) { #if os(macOS) @@ -20,13 +20,13 @@ extension Image { } } -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension PlatformImage { static var empty = PlatformImage() } #if !os(macOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension PlatformImage.Orientation { @inlinable var toSwiftUI: Image.Orientation { switch self { @@ -52,7 +52,7 @@ extension PlatformImage.Orientation { } } -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension Image.Orientation { @inlinable var toPlatform: PlatformImage.Orientation { switch self { diff --git a/SDWebImageSwiftUI/Classes/ImageManager.swift b/SDWebImageSwiftUI/Classes/ImageManager.swift index dd2bb8c1..f42eccb7 100644 --- a/SDWebImageSwiftUI/Classes/ImageManager.swift +++ b/SDWebImageSwiftUI/Classes/ImageManager.swift @@ -12,7 +12,7 @@ import SDWebImage /// A Image observable object for handle image load process. This drive the Source of Truth for image loading status. /// You can use `@ObservedObject` to associate each instance of manager to your View type, which update your view's body from SwiftUI framework when image was loaded. -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public final class ImageManager : ObservableObject { /// loaded image, note when progressive loading, this will published multiple times with different partial image public var image: PlatformImage? { @@ -136,7 +136,7 @@ public final class ImageManager : ObservableObject { } // Completion Handler -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension ImageManager { /// Provide the action when image load fails. /// - Parameters: diff --git a/SDWebImageSwiftUI/Classes/ImagePlayer.swift b/SDWebImageSwiftUI/Classes/ImagePlayer.swift index 8e5820c8..1548744f 100644 --- a/SDWebImageSwiftUI/Classes/ImagePlayer.swift +++ b/SDWebImageSwiftUI/Classes/ImagePlayer.swift @@ -11,7 +11,7 @@ import Combine import SDWebImage /// A Image observable object for handle aniamted image playback. This is used to avoid `@State` update may capture the View struct type and cause memory leak. -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public final class ImagePlayer : ObservableObject { var player: SDAnimatedImagePlayer? diff --git a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift index b0d6ac35..17c5a383 100644 --- a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift +++ b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift @@ -12,7 +12,7 @@ import SDWebImage #if !os(watchOS) /// Use wrapper to solve tne `UIImageView`/`NSImageView` frame size become image size issue (SwiftUI's Bug) -@available(iOS 14.0, OSX 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 { /// The wrapped actual image view, using SDWebImage's aniamted image view public var wrapped = SDAnimatedImageView() @@ -67,7 +67,7 @@ public class AnimatedImageViewWrapper : PlatformView { } } -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension PlatformView { /// Adds constraints to this `UIView` instances `superview` object to make sure this always has the same size as the superview. /// Please note that this has no effect if its `superview` is `nil` – add this `UIView` instance as a subview before calling this. diff --git a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift index 739212e1..bf6cc6b3 100644 --- a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift +++ b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift @@ -10,7 +10,7 @@ import SwiftUI import Combine /// A type to build the indicator -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public struct Indicator where T : View { var content: (Binding, Binding) -> T @@ -25,7 +25,7 @@ public struct Indicator where T : View { } /// A observable model to report indicator loading status -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public class IndicatorStatus : ObservableObject { /// whether indicator is loading or not var isLoading: Bool = false { @@ -48,7 +48,7 @@ public class IndicatorStatus : ObservableObject { /// A implementation detail View Modifier with indicator /// SwiftUI View Modifier construced by using a internal View type which modify the `body` /// It use type system to represent the view hierarchy, and Swift `some View` syntax to hide the type detail for users -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public struct IndicatorViewModifier : ViewModifier where T : View { /// The loading status @@ -72,7 +72,7 @@ public struct IndicatorViewModifier : ViewModifier where T : View { } } -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension Indicator where T == AnyView { /// Activity Indicator public static var activity: Indicator { @@ -90,7 +90,7 @@ extension Indicator where T == AnyView { } } -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension Indicator where T == AnyView { /// Progress Indicator public static var progress: Indicator { diff --git a/SDWebImageSwiftUI/Classes/SDWebImageSwiftUI.swift b/SDWebImageSwiftUI/Classes/SDWebImageSwiftUI.swift index a16d356a..adc40690 100644 --- a/SDWebImageSwiftUI/Classes/SDWebImageSwiftUI.swift +++ b/SDWebImageSwiftUI/Classes/SDWebImageSwiftUI.swift @@ -11,53 +11,53 @@ import SwiftUI @_exported import SDWebImage // Automatically import SDWebImage #if os(macOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformImage = NSImage #else -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformImage = UIImage #endif #if os(macOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformView = NSView #endif #if os(iOS) || os(tvOS) || os(visionOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformView = UIView #endif #if os(watchOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformView = WKInterfaceObject #endif #if os(macOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformViewRepresentable = NSViewRepresentable #endif #if os(iOS) || os(tvOS) || os(visionOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformViewRepresentable = UIViewRepresentable #endif #if os(watchOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformViewRepresentable = WKInterfaceObjectRepresentable #endif #if os(macOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension NSViewRepresentable { typealias PlatformViewType = NSViewType } #endif #if os(iOS) || os(tvOS) || os(visionOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension UIViewRepresentable { typealias PlatformViewType = UIViewType } #endif #if os(watchOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension WKInterfaceObjectRepresentable { typealias PlatformViewType = WKInterfaceObjectType } diff --git a/SDWebImageSwiftUI/Classes/Transition/Transition.swift b/SDWebImageSwiftUI/Classes/Transition/Transition.swift index e42503c5..fa3ca48c 100644 --- a/SDWebImageSwiftUI/Classes/Transition/Transition.swift +++ b/SDWebImageSwiftUI/Classes/Transition/Transition.swift @@ -8,7 +8,7 @@ import SwiftUI -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension AnyTransition { /// Fade-in transition diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index 19b47798..cf1080cb 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -47,7 +47,7 @@ public enum WebImagePhase { } /// Data Binding Object, only properties in this object can support changes from user with @State and refresh -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) final class WebImageModel : ObservableObject { /// URL image @Published var url: URL? @@ -56,7 +56,7 @@ final class WebImageModel : ObservableObject { } /// Completion Handler Binding Object, supports dynamic @State changes -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) final class WebImageHandler: ObservableObject { // Completion Handler @Published var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)? @@ -65,7 +65,7 @@ final class WebImageHandler: ObservableObject { } /// Configuration Binding Object, supports dynamic @State changes -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) final class WebImageConfiguration: ObservableObject { var retryOnAppear: Bool = true var cancelOnDisappear: Bool = true @@ -79,7 +79,7 @@ final class WebImageConfiguration: ObservableObject { } /// A Image View type to load image from url. Supports static/animated image format. -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public struct WebImage : View where Content: View { var transaction: Transaction @@ -344,7 +344,7 @@ public struct WebImage : View where Content: View { } // Layout -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension WebImage { func configure(_ block: @escaping (Image) -> Image) -> WebImage { var result = self @@ -382,7 +382,7 @@ extension WebImage { } // Completion Handler -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension WebImage { /// Provide the action when image load fails. @@ -414,7 +414,7 @@ extension WebImage { } // WebImage Modifier -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension WebImage { /// Control the behavior to retry the failed loading when view become appears again /// - Parameter flag: Whether or not to retry the failed loading @@ -432,7 +432,7 @@ extension WebImage { } // Indicator -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension WebImage { /// Associate a indicator when loading image with url @@ -449,7 +449,7 @@ extension WebImage { } // Animated Image -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension WebImage { /// Total loop count for animated image rendering. Defaults to nil. @@ -517,7 +517,7 @@ extension WebImage { } #if DEBUG -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) struct WebImage_Previews : PreviewProvider { static var previews: some View { Group { From ecce423f4c32ab1fbf45b154075f04678c667a89 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 25 Jun 2024 23:57:00 +0800 Subject: [PATCH 06/19] Add Image scale support in WebImage init --- SDWebImageSwiftUI/Classes/WebImage.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index cf1080cb..7cc6fbe2 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -108,16 +108,17 @@ public struct WebImage : View where Content: View { /// Create a web image with url, placeholder, custom options and context. Optional can support animated image using Binding. /// - Parameter url: The image url + /// - Parameter scale: The scale to use for the image. The default is 1. Set a different value when loading images designed for higher resolution displays. For example, set a value of 2 for an image that you would name with the @2x suffix if stored in a file on disk. /// - Parameter options: The options to use when downloading the image. See `SDWebImageOptions` for the possible values. /// - Parameter context: A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. /// - Parameter isAnimating: The binding for animation control. The binding value should be `true` when initialized to setup the correct animated image class. If not, you must provide the `.animatedImageClass` explicitly. When the animation started, this binding can been used to start / stop the animation. - public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding = .constant(true)) where Content == Image { + public init(url: URL?, scale: CGFloat = 1, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding = .constant(true)) where Content == Image { self.init(url: url, options: options, context: context, isAnimating: isAnimating) { phase in phase.image ?? Image(platformImage: .empty) } } - public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding = .constant(true), @ViewBuilder content: @escaping (Image) -> I, @ViewBuilder placeholder: @escaping () -> P) where Content == _ConditionalContent, I: View, P: View { + public init(url: URL?, scale: CGFloat = 1, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding = .constant(true), @ViewBuilder content: @escaping (Image) -> I, @ViewBuilder placeholder: @escaping () -> P) where Content == _ConditionalContent, I: View, P: View { self.init(url: url, options: options, context: context, isAnimating: isAnimating) { phase in if let i = phase.image { content(i) @@ -127,9 +128,12 @@ public struct WebImage : View where Content: View { } } - public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding = .constant(true), transaction: Transaction = Transaction(), @ViewBuilder content: @escaping (WebImagePhase) -> Content) { + public init(url: URL?, scale: CGFloat = 1, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding = .constant(true), transaction: Transaction = Transaction(), @ViewBuilder content: @escaping (WebImagePhase) -> Content) { self._isAnimating = isAnimating var context = context ?? [:] + if context[.imageScaleFactor] == nil { + context[.imageScaleFactor] = scale + } // provide animated image class if the initialized `isAnimating` is true, user can still custom the image class if they want if isAnimating.wrappedValue { if context[.animatedImageClass] == nil { From 1edee7f01997a8131889b1d127678007d2763ccd Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 27 Jun 2024 16:37:09 +0800 Subject: [PATCH 07/19] Re-implements the aspectRatio support on AnimatedImage, fix issue like cornerRadius Use the correct way to override invalidateIntrinsicContentSize to keep aspect ratio to UIKit/SwiftUI engine --- .../SDWebImageSwiftUIDemo/ContentView.swift | 11 +++ SDWebImageSwiftUI/Classes/AnimatedImage.swift | 99 ++++--------------- .../Classes/ImageViewWrapper.swift | 18 +++- 3 files changed, 45 insertions(+), 83 deletions(-) diff --git a/Example/SDWebImageSwiftUIDemo/ContentView.swift b/Example/SDWebImageSwiftUIDemo/ContentView.swift index 6e7e74cf..e1fc7a57 100644 --- a/Example/SDWebImageSwiftUIDemo/ContentView.swift +++ b/Example/SDWebImageSwiftUIDemo/ContentView.swift @@ -17,6 +17,17 @@ class UserSettings: ObservableObject { #endif } +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)) + } +} + // Test Switching nil url struct ContentView3: View { @State var isOn = false diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift index 3070b0ab..83d0b2c9 100644 --- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift +++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift @@ -275,6 +275,9 @@ public struct AnimatedImage : PlatformViewRepresentable { self.imageModel.placeholderView?.isHidden = false self.imageHandler.failureBlock?(error ?? NSError()) } + // Finished loading + configureView(view, context: context) + layoutView(view, context: context) } } @@ -361,20 +364,7 @@ public struct AnimatedImage : PlatformViewRepresentable { break // impossible } - #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 - + // Finished loading configureView(view, context: context) layoutView(view, context: context) if let viewUpdateBlock = imageHandler.viewUpdateBlock { @@ -442,9 +432,7 @@ public struct AnimatedImage : PlatformViewRepresentable { #endif // Resizable - if let _ = imageLayout.resizingMode { - view.resizable = true - } + view.resizingMode = imageLayout.resizingMode // Animated Image does not support resizing mode and rendering mode if let image = view.wrapped.image { @@ -587,6 +575,21 @@ public struct AnimatedImage : PlatformViewRepresentable { } else { 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 +633,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 @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { diff --git a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift index 17c5a383..cfe81baf 100644 --- a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift +++ b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift @@ -8,6 +8,7 @@ import Foundation import SDWebImage +import SwiftUI #if !os(watchOS) @@ -18,7 +19,7 @@ public class AnimatedImageViewWrapper : PlatformView { public var wrapped = SDAnimatedImageView() var interpolationQuality = CGInterpolationQuality.default var shouldAntialias = false - var resizable = false + var resizingMode: Image.ResizingMode? public override func draw(_ rect: CGRect) { #if os(macOS) @@ -48,11 +49,20 @@ public class AnimatedImageViewWrapper : PlatformView { public override var intrinsicContentSize: CGSize { /// Match the behavior of SwiftUI.Image, only when image is resizable, use the super implementation to calculate size - if resizable { - return super.intrinsicContentSize + let imageSize = wrapped.intrinsicContentSize + if let _ = resizingMode { + /// Keep aspect ratio + let noIntrinsicMetric = AnimatedImageViewWrapper.noIntrinsicMetric + if (imageSize.width > 0 && imageSize.height > 0) { + let ratio = imageSize.width / imageSize.height + let size = CGSize(width: ratio, height: 1) + return size + } else { + return CGSize(width: noIntrinsicMetric, height: noIntrinsicMetric) + } } else { /// Not resizable, always use image size, like SwiftUI.Image - return wrapped.intrinsicContentSize + return imageSize } } From 3340ea4ed46b10e3a15b95ea10532e4ad2ec93b0 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 27 Jun 2024 17:53:07 +0800 Subject: [PATCH 08/19] Fix the compatibility with UIView transition Actually this is not the good design, but at least a workaround --- SDWebImageSwiftUI/Classes/AnimatedImage.swift | 25 ++++++++++++++----- .../Classes/ImageViewWrapper.swift | 20 ++++++++++----- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift index 83d0b2c9..193c9eb2 100644 --- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift +++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift @@ -275,9 +275,8 @@ public struct AnimatedImage : PlatformViewRepresentable { self.imageModel.placeholderView?.isHidden = false self.imageHandler.failureBlock?(error ?? NSError()) } - // Finished loading - configureView(view, context: context) - layoutView(view, context: context) + // Finished loading, async + finishUpdateView(view, context: context, image: image) } } @@ -310,6 +309,8 @@ public struct AnimatedImage : PlatformViewRepresentable { #endif context.coordinator.imageLoading.imageName = name view.wrapped.image = image + // Finished loading, sync + finishUpdateView(view, context: context, image: image) } private func updateViewForData(_ data: Data?, view: AnimatedImageViewWrapper, context: Context) { @@ -323,6 +324,8 @@ public struct AnimatedImage : PlatformViewRepresentable { } context.coordinator.imageLoading.imageData = data view.wrapped.image = image + // Finished loading, sync + finishUpdateView(view, context: context, image: image) } private func updateViewForURL(_ url: URL?, view: AnimatedImageViewWrapper, context: Context) { @@ -347,6 +350,8 @@ public struct AnimatedImage : PlatformViewRepresentable { setupIndicator(view, context: context) loadImage(view, context: context) } + // Finished loading, sync + finishUpdateView(view, context: context, image: view.wrapped.image) } func updateView(_ view: AnimatedImageViewWrapper, context: Context) { @@ -364,9 +369,6 @@ public struct AnimatedImage : PlatformViewRepresentable { break // impossible } - // Finished loading - configureView(view, context: context) - layoutView(view, context: context) if let viewUpdateBlock = imageHandler.viewUpdateBlock { viewUpdateBlock(view.wrapped, context) } @@ -384,6 +386,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) { // AspectRatio && ContentMode #if os(macOS) diff --git a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift index cfe81baf..e019881f 100644 --- a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift +++ b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift @@ -20,6 +20,7 @@ public class AnimatedImageViewWrapper : PlatformView { var interpolationQuality = CGInterpolationQuality.default var shouldAntialias = false var resizingMode: Image.ResizingMode? + var imageSize: CGSize? public override func draw(_ rect: CGRect) { #if os(macOS) @@ -49,20 +50,27 @@ public class AnimatedImageViewWrapper : PlatformView { public override var intrinsicContentSize: CGSize { /// Match the behavior of SwiftUI.Image, only when image is resizable, use the super implementation to calculate size - let imageSize = wrapped.intrinsicContentSize + 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 + } + } if let _ = resizingMode { /// Keep aspect ratio - let noIntrinsicMetric = AnimatedImageViewWrapper.noIntrinsicMetric - if (imageSize.width > 0 && imageSize.height > 0) { - let ratio = imageSize.width / imageSize.height + if contentSize.width > 0 && contentSize.height > 0 { + let ratio = contentSize.width / contentSize.height let size = CGSize(width: ratio, height: 1) return size } else { - return CGSize(width: noIntrinsicMetric, height: noIntrinsicMetric) + return contentSize } } else { /// Not resizable, always use image size, like SwiftUI.Image - return imageSize + return contentSize } } From c8320d4e20d00455cb6401331cf54616bceb4e4c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 27 Jun 2024 23:08:56 +0800 Subject: [PATCH 09/19] Revert the wrong changes to fix the unit test --- Example/SDWebImageSwiftUIDemo/ContentView.swift | 2 ++ SDWebImageSwiftUI/Classes/AnimatedImage.swift | 9 +++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Example/SDWebImageSwiftUIDemo/ContentView.swift b/Example/SDWebImageSwiftUIDemo/ContentView.swift index e1fc7a57..4cb0fa54 100644 --- a/Example/SDWebImageSwiftUIDemo/ContentView.swift +++ b/Example/SDWebImageSwiftUIDemo/ContentView.swift @@ -17,6 +17,7 @@ class UserSettings: ObservableObject { #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 { @@ -27,6 +28,7 @@ struct ContentView4: View { .clipShape(RoundedRectangle(cornerRadius: 50, style: .continuous)) } } +#endif // Test Switching nil url struct ContentView3: View { diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift index 193c9eb2..66543720 100644 --- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift +++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift @@ -309,8 +309,6 @@ public struct AnimatedImage : PlatformViewRepresentable { #endif context.coordinator.imageLoading.imageName = name view.wrapped.image = image - // Finished loading, sync - finishUpdateView(view, context: context, image: image) } private func updateViewForData(_ data: Data?, view: AnimatedImageViewWrapper, context: Context) { @@ -324,8 +322,6 @@ public struct AnimatedImage : PlatformViewRepresentable { } context.coordinator.imageLoading.imageData = data view.wrapped.image = image - // Finished loading, sync - finishUpdateView(view, context: context, image: image) } private func updateViewForURL(_ url: URL?, view: AnimatedImageViewWrapper, context: Context) { @@ -350,8 +346,6 @@ public struct AnimatedImage : PlatformViewRepresentable { setupIndicator(view, context: context) loadImage(view, context: context) } - // Finished loading, sync - finishUpdateView(view, context: context, image: view.wrapped.image) } func updateView(_ view: AnimatedImageViewWrapper, context: Context) { @@ -369,6 +363,9 @@ public struct AnimatedImage : PlatformViewRepresentable { break // impossible } + // Finished loading, sync + finishUpdateView(view, context: context, image: view.wrapped.image) + if let viewUpdateBlock = imageHandler.viewUpdateBlock { viewUpdateBlock(view.wrapped, context) } From 02b2579a93aaa8ac0146a46f18d9056253c03096 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 27 Jun 2024 23:28:46 +0800 Subject: [PATCH 10/19] Released v3.1.0 version --- CHANGELOG.md | 6 ++++++ SDWebImageSwiftUI.podspec | 2 +- SDWebImageSwiftUI/Module/Info.plist | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51271238..5c27bdc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.1.0] - 2024-06-27 +- Re-implements the aspectRatio support on AnimatedImage, fix issue like cornerRadius #324 +- Add Image scale support in WebImage init #323 +- Update platform names in `available` attributes #321 +- - This is source compatible but binary incompatible version + ## [3.0.4] - 2024-04-30 - Trying to move the initial state setup before onAppear to fix the watchOS switching url or any other state issue #316 - This solve a issue in history when sometimes SwiftUI does not trigger the `onAppear` and cause state error, like #312 #314 diff --git a/SDWebImageSwiftUI.podspec b/SDWebImageSwiftUI.podspec index 158dac83..9d71080a 100644 --- a/SDWebImageSwiftUI.podspec +++ b/SDWebImageSwiftUI.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageSwiftUI' - s.version = '3.0.4' + s.version = '3.1.0' s.summary = 'SwiftUI Image loading and Animation framework powered by SDWebImage' s.description = <<-DESC diff --git a/SDWebImageSwiftUI/Module/Info.plist b/SDWebImageSwiftUI/Module/Info.plist index 4f3308f9..99601856 100644 --- a/SDWebImageSwiftUI/Module/Info.plist +++ b/SDWebImageSwiftUI/Module/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 3.0.4 + 3.1.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) From 26f75715c7492841e5373906b1baca1e32a98ff4 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 1 Jul 2024 16:41:02 +0800 Subject: [PATCH 11/19] Fix the WebImage.transaction should use take effect --- SDWebImageSwiftUI/Classes/ImageManager.swift | 27 +++++++++++--------- SDWebImageSwiftUI/Classes/WebImage.swift | 4 +-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/SDWebImageSwiftUI/Classes/ImageManager.swift b/SDWebImageSwiftUI/Classes/ImageManager.swift index f42eccb7..eb5ec273 100644 --- a/SDWebImageSwiftUI/Classes/ImageManager.swift +++ b/SDWebImageSwiftUI/Classes/ImageManager.swift @@ -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)? @@ -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()) + } } } } diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index 7cc6fbe2..3dab6a3c 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -81,8 +81,6 @@ final class WebImageConfiguration: ObservableObject { /// 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, *) public struct WebImage : View where Content: View { - var transaction: Transaction - var configurations: [(Image) -> Image] = [] var content: (WebImagePhase) -> Content @@ -146,10 +144,10 @@ public struct WebImage : View where Content: View { imageModel.context = context _imageModel = ObservedObject(wrappedValue: imageModel) let imageManager = ImageManager() + imageManager.transaction = transaction _imageManager = StateObject(wrappedValue: imageManager) _indicatorStatus = ObservedObject(wrappedValue: imageManager.indicatorStatus) - self.transaction = transaction self.content = { phase in content(phase) } From d68c13a7f317adba773f302a99522c7f5732a80a Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 1 Jul 2024 17:59:27 +0800 Subject: [PATCH 12/19] Fix the transition visual jump between placeholderImage and final image for AnimatedImage --- SDWebImageSwiftUI/Classes/AnimatedImage.swift | 11 +++------ .../Classes/ImageViewWrapper.swift | 24 ++++++++++--------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift index 66543720..4e5a7a75 100644 --- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift +++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift @@ -276,7 +276,7 @@ public struct AnimatedImage : PlatformViewRepresentable { self.imageHandler.failureBlock?(error ?? NSError()) } // Finished loading, async - finishUpdateView(view, context: context, image: image) + finishUpdateView(view, context: context) } } @@ -364,7 +364,7 @@ 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) @@ -383,13 +383,8 @@ public struct AnimatedImage : PlatformViewRepresentable { } } - 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) } diff --git a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift index e019881f..ff42fdf1 100644 --- a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift +++ b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift @@ -16,11 +16,15 @@ import SwiftUI @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() + @objc dynamic public var wrapped = SDAnimatedImageView() + var observation: NSKeyValueObservation? var interpolationQuality = CGInterpolationQuality.default var shouldAntialias = false var resizingMode: Image.ResizingMode? - var imageSize: CGSize? + + deinit { + observation?.invalidate() + } public override func draw(_ rect: CGRect) { #if os(macOS) @@ -50,15 +54,7 @@ public class AnimatedImageViewWrapper : PlatformView { 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 = wrapped.intrinsicContentSize if let _ = resizingMode { /// Keep aspect ratio if contentSize.width > 0 && contentSize.height > 0 { @@ -77,11 +73,17 @@ public class AnimatedImageViewWrapper : PlatformView { public override init(frame frameRect: CGRect) { super.init(frame: frameRect) addSubview(wrapped) + observation = observe(\.wrapped.image, options: [.new]) { _, _ in + self.invalidateIntrinsicContentSize() + } } public required init?(coder: NSCoder) { super.init(coder: coder) addSubview(wrapped) + observation = observe(\.wrapped.image, options: [.new]) { _, _ in + self.invalidateIntrinsicContentSize() + } } } From 5d462f7530677ae0c2b9510c26383aa25ba48751 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 1 Jul 2024 19:47:26 +0800 Subject: [PATCH 13/19] Released v3.1.1 version --- CHANGELOG.md | 3 +++ SDWebImageSwiftUI.podspec | 2 +- SDWebImageSwiftUI/Module/Info.plist | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c27bdc1..5fa69ec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.1.1] - 2024-07-01 +- Fix the transition visual jump between placeholderImage and final image for AnimatedImage #326 + ## [3.1.0] - 2024-06-27 - Re-implements the aspectRatio support on AnimatedImage, fix issue like cornerRadius #324 - Add Image scale support in WebImage init #323 diff --git a/SDWebImageSwiftUI.podspec b/SDWebImageSwiftUI.podspec index 9d71080a..96e7cadc 100644 --- a/SDWebImageSwiftUI.podspec +++ b/SDWebImageSwiftUI.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageSwiftUI' - s.version = '3.1.0' + s.version = '3.1.1' s.summary = 'SwiftUI Image loading and Animation framework powered by SDWebImage' s.description = <<-DESC diff --git a/SDWebImageSwiftUI/Module/Info.plist b/SDWebImageSwiftUI/Module/Info.plist index 99601856..282c0ff3 100644 --- a/SDWebImageSwiftUI/Module/Info.plist +++ b/SDWebImageSwiftUI/Module/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 3.1.0 + 3.1.1 CFBundleVersion $(CURRENT_PROJECT_VERSION) From 7efdf228f68073ec9a95a2308378fae82932f10c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 28 Aug 2024 17:35:43 +0800 Subject: [PATCH 14/19] Allows easy to use WebImage with `isAnimating` default to false and change to true later Since SDAnimatedImage has fallback logic, we can apply this to WebImage by default without dynamic check --- .../SDWebImageSwiftUIDemo/ContentView.swift | 18 ++++++++++++++++++ SDWebImageSwiftUI/Classes/WebImage.swift | 12 ++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Example/SDWebImageSwiftUIDemo/ContentView.swift b/Example/SDWebImageSwiftUIDemo/ContentView.swift index 4cb0fa54..17e43e7e 100644 --- a/Example/SDWebImageSwiftUIDemo/ContentView.swift +++ b/Example/SDWebImageSwiftUIDemo/ContentView.swift @@ -17,6 +17,24 @@ class UserSettings: ObservableObject { #endif } +struct ContentView5: View { + let url: URL = URL(string: "http://assets.sbnation.com/assets/2512203/dogflops.gif")! + + @State private var isAnimating = false + + var body: some View { + ZStack { + WebImage(url: url, isAnimating: $isAnimating) + .pausable(false) + Button { + isAnimating.toggle() + } label: { + Text(isAnimating ? "Stop" : "Start") + } + } + } +} + #if !os(watchOS) struct ContentView4: View { var url = URL(string: "https://github.com/SDWebImage/SDWebImageSwiftUI/assets/97430818/72d27f90-e9d8-48d7-b144-82ada828a027")! diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index 3dab6a3c..59fe49a5 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -109,7 +109,7 @@ public struct WebImage : View where Content: View { /// - Parameter scale: The scale to use for the image. The default is 1. Set a different value when loading images designed for higher resolution displays. For example, set a value of 2 for an image that you would name with the @2x suffix if stored in a file on disk. /// - Parameter options: The options to use when downloading the image. See `SDWebImageOptions` for the possible values. /// - Parameter context: A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. - /// - Parameter isAnimating: The binding for animation control. The binding value should be `true` when initialized to setup the correct animated image class. If not, you must provide the `.animatedImageClass` explicitly. When the animation started, this binding can been used to start / stop the animation. + /// - Parameter isAnimating: The binding for animation control. When the animation started, this binding can been used to start / stop the animation. You can still customize the `.animatedImageClass` context for advanced custom animation. public init(url: URL?, scale: CGFloat = 1, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding = .constant(true)) where Content == Image { self.init(url: url, options: options, context: context, isAnimating: isAnimating) { phase in phase.image ?? Image(platformImage: .empty) @@ -132,11 +132,11 @@ public struct WebImage : View where Content: View { if context[.imageScaleFactor] == nil { context[.imageScaleFactor] = scale } - // provide animated image class if the initialized `isAnimating` is true, user can still custom the image class if they want - if isAnimating.wrappedValue { - if context[.animatedImageClass] == nil { - context[.animatedImageClass] = SDAnimatedImage.self - } + // always provide animated image class to allows dynamic control + // since most cases, SDAnimatedImage should be compatible with UIImage + // user can still custom the image class if they want + if context[.animatedImageClass] == nil { + context[.animatedImageClass] = SDAnimatedImage.self } let imageModel = WebImageModel() imageModel.url = url From 5aa947356f4ea49a0c3b9968564267f6ea5abea7 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 29 Aug 2024 16:35:40 +0800 Subject: [PATCH 15/19] Released v3.1.2 version --- CHANGELOG.md | 4 ++++ SDWebImageSwiftUI.podspec | 2 +- SDWebImageSwiftUI/Module/Info.plist | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa69ec0..0c1a3efa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.1.2] - 2024-08-29 +- Allows easy to use WebImage with isAnimating default to false and change to true later #333 +- Note: This changes WebImage's internal loaded image from `UIImage/NSImage` to `SDAnimatedImage`, which is compatible for `UIImageView/NSImageView` + ## [3.1.1] - 2024-07-01 - Fix the transition visual jump between placeholderImage and final image for AnimatedImage #326 diff --git a/SDWebImageSwiftUI.podspec b/SDWebImageSwiftUI.podspec index 96e7cadc..5f21a283 100644 --- a/SDWebImageSwiftUI.podspec +++ b/SDWebImageSwiftUI.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageSwiftUI' - s.version = '3.1.1' + s.version = '3.1.2' s.summary = 'SwiftUI Image loading and Animation framework powered by SDWebImage' s.description = <<-DESC diff --git a/SDWebImageSwiftUI/Module/Info.plist b/SDWebImageSwiftUI/Module/Info.plist index 282c0ff3..0eee3074 100644 --- a/SDWebImageSwiftUI/Module/Info.plist +++ b/SDWebImageSwiftUI/Module/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 3.1.1 + 3.1.2 CFBundleVersion $(CURRENT_PROJECT_VERSION) From 7ecc2d33f840f3c5577a27850089f96e6c630d27 Mon Sep 17 00:00:00 2001 From: Narong Aunthee Date: Tue, 5 Nov 2024 16:46:37 +0700 Subject: [PATCH 16/19] Fixed old version compiler does not support automatic self capture in Xcode 14.2 and Swift 5.7.2 --- SDWebImageSwiftUI/Classes/ImageManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDWebImageSwiftUI/Classes/ImageManager.swift b/SDWebImageSwiftUI/Classes/ImageManager.swift index eb5ec273..7da6fc9d 100644 --- a/SDWebImageSwiftUI/Classes/ImageManager.swift +++ b/SDWebImageSwiftUI/Classes/ImageManager.swift @@ -107,7 +107,7 @@ public final class ImageManager : ObservableObject { // So previous View struct call `onDisappear` and cancel the currentOperation return } - withTransaction(transaction) { + withTransaction(self.transaction) { self.image = image self.error = error self.isIncremental = !finished From 46407f925dee7346cbb617fa4657dda9433f1f15 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 6 Nov 2024 14:27:51 +0800 Subject: [PATCH 17/19] Fix the data race because progress block is called in non-main queue This match the behavior of `progress indicator`, which only update on main queue Note: This is different behavior compared to SDWebIamge on UIKit (progress updated in global queue) --- SDWebImageSwiftUI/Classes/ImageManager.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SDWebImageSwiftUI/Classes/ImageManager.swift b/SDWebImageSwiftUI/Classes/ImageManager.swift index 7da6fc9d..045bfa37 100644 --- a/SDWebImageSwiftUI/Classes/ImageManager.swift +++ b/SDWebImageSwiftUI/Classes/ImageManager.swift @@ -85,6 +85,7 @@ public final class ImageManager : ObservableObject { self.indicatorStatus.isLoading = true self.indicatorStatus.progress = 0 currentOperation = manager.loadImage(with: url, options: options, context: context, progress: { [weak self] (receivedSize, expectedSize, _) in + // This block may be called in non-main thread guard let self = self else { return } @@ -95,7 +96,11 @@ public final class ImageManager : ObservableObject { progress = 0 } self.indicatorStatus.progress = progress - self.progressBlock?(receivedSize, expectedSize) + if let progressBlock = self.progressBlock { + DispatchQueue.main.async { + progressBlock(receivedSize, expectedSize) + } + } }) { [weak self] (image, data, error, cacheType, finished, _) in guard let self = self else { return From 451c6dfd5ecec2cf626d1d9ca81c2d4a60355172 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 6 Nov 2024 14:46:42 +0800 Subject: [PATCH 18/19] Released v3.1.3 version --- CHANGELOG.md | 4 ++++ SDWebImageSwiftUI.podspec | 2 +- SDWebImageSwiftUI/Module/Info.plist | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c1a3efa..05a05072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.1.3] - 2024-11-06 +- Fixed old version compiler does not support automatic self capture in Xcode 14.2 and Swift 5.7.2 #340 +- Fix the data race because progress block is called in non-main queue #341 + ## [3.1.2] - 2024-08-29 - Allows easy to use WebImage with isAnimating default to false and change to true later #333 - Note: This changes WebImage's internal loaded image from `UIImage/NSImage` to `SDAnimatedImage`, which is compatible for `UIImageView/NSImageView` diff --git a/SDWebImageSwiftUI.podspec b/SDWebImageSwiftUI.podspec index 5f21a283..2c26725e 100644 --- a/SDWebImageSwiftUI.podspec +++ b/SDWebImageSwiftUI.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageSwiftUI' - s.version = '3.1.2' + s.version = '3.1.3' s.summary = 'SwiftUI Image loading and Animation framework powered by SDWebImage' s.description = <<-DESC diff --git a/SDWebImageSwiftUI/Module/Info.plist b/SDWebImageSwiftUI/Module/Info.plist index 0eee3074..df2e4bee 100644 --- a/SDWebImageSwiftUI/Module/Info.plist +++ b/SDWebImageSwiftUI/Module/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 3.1.2 + 3.1.3 CFBundleVersion $(CURRENT_PROJECT_VERSION) From 765aea04e0b94442578a7932f0edbed2b6f748af Mon Sep 17 00:00:00 2001 From: wuqi Date: Thu, 22 May 2025 17:26:45 +0800 Subject: [PATCH 19/19] fix: memoryleak --- SDWebImageSwiftUI/Classes/ImageViewWrapper.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift index ff42fdf1..49ac173b 100644 --- a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift +++ b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift @@ -73,7 +73,10 @@ public class AnimatedImageViewWrapper : PlatformView { public override init(frame frameRect: CGRect) { super.init(frame: frameRect) addSubview(wrapped) - observation = observe(\.wrapped.image, options: [.new]) { _, _ in + observation = observe(\.wrapped.image, options: [.new]) { [weak self] _, _ in + guard let self = self else { + return + } self.invalidateIntrinsicContentSize() } } @@ -81,7 +84,10 @@ public class AnimatedImageViewWrapper : PlatformView { public required init?(coder: NSCoder) { super.init(coder: coder) addSubview(wrapped) - observation = observe(\.wrapped.image, options: [.new]) { _, _ in + observation = observe(\.wrapped.image, options: [.new]) { [weak self] _, _ in + guard let self = self else { + return + } self.invalidateIntrinsicContentSize() } }