From 2909b0027aa83fb2a458aeb8943a4ad5cb93f68f Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 26 Dec 2022 15:56:31 +0800 Subject: [PATCH 1/7] Drop iOS 13/macOS 10.15/tvOS 13/watchOS 6 support Changes: 1. Backport sources are removed 2. Availability is changed 3. Use StateObject instead --- Package.swift | 4 +- README.md | 24 ++- SDWebImageSwiftUI.podspec | 10 +- SDWebImageSwiftUI.xcodeproj/project.pbxproj | 66 +------- SDWebImageSwiftUI/Classes/AnimatedImage.swift | 30 ++-- .../Classes/Backports/Backport.swift | 59 ------- .../Classes/Backports/OnChange.swift | 52 ------ .../Classes/Backports/Overlay.swift | 124 -------------- .../Classes/Backports/StateObject.swift | 151 ------------------ SDWebImageSwiftUI/Classes/Image.swift | 8 +- SDWebImageSwiftUI/Classes/ImageManager.swift | 4 +- SDWebImageSwiftUI/Classes/ImagePlayer.swift | 2 +- .../Classes/ImageViewWrapper.swift | 6 +- .../Classes/Indicator/ActivityIndicator.swift | 4 +- .../Classes/Indicator/Indicator.swift | 25 +-- .../Classes/Indicator/ProgressIndicator.swift | 4 +- .../Classes/SDWebImageSwiftUI.swift | 22 +-- .../Classes/SwiftUICompatibility.swift | 6 +- .../Classes/Transition/Transition.swift | 2 +- SDWebImageSwiftUI/Classes/WebImage.swift | 28 ++-- 20 files changed, 96 insertions(+), 535 deletions(-) delete mode 100644 SDWebImageSwiftUI/Classes/Backports/Backport.swift delete mode 100644 SDWebImageSwiftUI/Classes/Backports/OnChange.swift delete mode 100644 SDWebImageSwiftUI/Classes/Backports/Overlay.swift delete mode 100644 SDWebImageSwiftUI/Classes/Backports/StateObject.swift diff --git a/Package.swift b/Package.swift index e7c307f..7e53f13 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.2 +// swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "SDWebImageSwiftUI", platforms: [ - .macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6) + .macOS(.v11), .iOS(.v14), .tvOS(.v14), .watchOS(.v7) ], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. diff --git a/README.md b/README.md index d64f3c3..4f40528 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,10 @@ All issue reports, feature requests, contributions, and GitHub stars are welcome ## Requirements + Xcode 12+ -+ iOS 13+ (14+ Recommended) -+ macOS 10.15+ (11+ Recommended) -+ tvOS 13+ (14+ Recommended) -+ watchOS 6+ (7+ Recommended) ++ iOS 14+ ++ macOS 11+ ++ tvOS 14+ ++ watchOS 7+ ## SwiftUI 2.0 Compatibility @@ -74,9 +74,7 @@ var body: some View { } ``` -Note: However, many differences behavior between iOS 13/14's is hard to fixup. And we may break some APIs (which are not designed to be public) to fixup it. - -Due to maintain issue, in the future release, we will drop the iOS 13 supports and always match SwiftUI 2.0's behavior. And **v2.x** may be the last version support iOS 13. +Note: However, many differences behavior between iOS 13/14 is hard to fixup. Due to maintain issue, from SDWebImageSwiftUI v3.0, iOS 13 is no longer supported. We always match SwiftUI 2.0's behavior. ## Installation @@ -514,7 +512,7 @@ For caches, you actually don't need to worry about anything. It just works after #### Using for backward deployment and weak linking SwiftUI -SDWebImageSwiftUI supports to use when your App Target has a deployment target version less than iOS 13/macOS 10.15/tvOS 13/watchOS 6. Which will weak linking of SwiftUI(Combine) to allows writing code with available check at runtime. +SDWebImageSwiftUI supports to use when your App Target has a deployment target version less than iOS 14/macOS 11/tvOS 14/watchOS 7. Which will weak linking of SwiftUI(Combine) to allows writing code with available check at runtime. To use backward deployment, you have to do the follow things: @@ -528,7 +526,7 @@ You should notice that all the third party SwiftUI frameworks should have this b For deployment target version below iOS 12.2 (The first version which Swift 5 Runtime bundled in iOS system), you have to change the min deployment target version of SDWebImageSwiftUI. This may take some side effect on compiler's optimization and trigger massive warnings for some frameworks. -However, for iOS 12.2+, you can still keep the min deployment target version to iOS 13, no extra warnings or performance slow down for iOS 13 client. +However, for iOS 12.2+, you can still keep the min deployment target version to iOS 14, no extra warnings or performance slow down for iOS 14 client. Because Swift use the min deployment target version to detect whether to link the App bundled Swift runtime, or the System built-in one (`/usr/lib/swift/libswiftCore.dylib`). @@ -555,7 +553,7 @@ end + For CocoaPods user, you can skip the platform version validation in Podfile with: ```ruby -platform :ios, '13.0' # This does not effect your App Target's deployment target version, just a hint for CocoaPods +platform :ios, '14.0' # This does not effect your App Target's deployment target version, just a hint for CocoaPods ``` + For SwiftPM user, SwiftPM does not support weak linking nor Library Evolution, so it can not deployment to iOS 12+ user without changing the min deployment target. @@ -568,7 +566,7 @@ Add **all the SwiftUI code** with the available annotation and runtime check, li // AppDelegate.swift func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // ... - if #available(iOS 13, *) { + if #available(iOS 14, *) { window.rootViewController = UIHostingController(rootView: ContentView()) } else { window.rootViewController = ViewController() @@ -590,11 +588,11 @@ class ViewController: UIViewController { } // ContentView.swift -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) struct ContentView : View { var body: some View { Group { - Text("Hello World iOS 13!") + Text("Hello World iOS 14!") WebImage(url: URL(string: "https://i.loli.net/2019/09/24/rX2RkVWeGKIuJvc.jpg")) } } diff --git a/SDWebImageSwiftUI.podspec b/SDWebImageSwiftUI.podspec index 1562601..38566a0 100644 --- a/SDWebImageSwiftUI.podspec +++ b/SDWebImageSwiftUI.podspec @@ -21,10 +21,10 @@ It brings all your favorite features from SDWebImage, like async image loading, s.author = { 'DreamPiggy' => 'lizhuoli1126@126.com' } s.source = { :git => 'https://github.com/SDWebImage/SDWebImageSwiftUI.git', :tag => s.version.to_s } - s.ios.deployment_target = '13.0' - s.osx.deployment_target = '10.15' - s.tvos.deployment_target = '13.0' - s.watchos.deployment_target = '6.0' + s.ios.deployment_target = '14.0' + s.osx.deployment_target = '11.0' + s.tvos.deployment_target = '14.0' + s.watchos.deployment_target = '7.0' s.source_files = 'SDWebImageSwiftUI/Classes/**/*', 'SDWebImageSwiftUI/Module/*.h' s.pod_target_xcconfig = { @@ -35,5 +35,5 @@ It brings all your favorite features from SDWebImage, like async image loading, s.weak_frameworks = 'SwiftUI', 'Combine' s.dependency 'SDWebImage', '~> 5.10' - s.swift_version = '5.2' + s.swift_version = '5.3' end diff --git a/SDWebImageSwiftUI.xcodeproj/project.pbxproj b/SDWebImageSwiftUI.xcodeproj/project.pbxproj index 1fbde81..5d05341 100644 --- a/SDWebImageSwiftUI.xcodeproj/project.pbxproj +++ b/SDWebImageSwiftUI.xcodeproj/project.pbxproj @@ -31,22 +31,6 @@ 32B933E623659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; }; 32B933E723659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; }; 32B933E823659A1900BB7CAD /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B933E423659A1900BB7CAD /* Transition.swift */; }; - 32BC087328D23D35002451BD /* StateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC086F28D23D35002451BD /* StateObject.swift */; }; - 32BC087428D23D35002451BD /* StateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC086F28D23D35002451BD /* StateObject.swift */; }; - 32BC087528D23D35002451BD /* StateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC086F28D23D35002451BD /* StateObject.swift */; }; - 32BC087628D23D35002451BD /* StateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC086F28D23D35002451BD /* StateObject.swift */; }; - 32BC087728D23D35002451BD /* OnChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087028D23D35002451BD /* OnChange.swift */; }; - 32BC087828D23D35002451BD /* OnChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087028D23D35002451BD /* OnChange.swift */; }; - 32BC087928D23D35002451BD /* OnChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087028D23D35002451BD /* OnChange.swift */; }; - 32BC087A28D23D35002451BD /* OnChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087028D23D35002451BD /* OnChange.swift */; }; - 32BC087B28D23D35002451BD /* Overlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087128D23D35002451BD /* Overlay.swift */; }; - 32BC087C28D23D35002451BD /* Overlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087128D23D35002451BD /* Overlay.swift */; }; - 32BC087D28D23D35002451BD /* Overlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087128D23D35002451BD /* Overlay.swift */; }; - 32BC087E28D23D35002451BD /* Overlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087128D23D35002451BD /* Overlay.swift */; }; - 32BC087F28D23D35002451BD /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087228D23D35002451BD /* Backport.swift */; }; - 32BC088028D23D35002451BD /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087228D23D35002451BD /* Backport.swift */; }; - 32BC088128D23D35002451BD /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087228D23D35002451BD /* Backport.swift */; }; - 32BC088228D23D35002451BD /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC087228D23D35002451BD /* Backport.swift */; }; 32C43DE622FD54CD00BE87F5 /* SDWebImageSwiftUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 32C43DE422FD54CD00BE87F5 /* SDWebImageSwiftUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32C43DEA22FD577300BE87F5 /* SDWebImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32C43DE922FD577300BE87F5 /* SDWebImage.framework */; }; 32C43E1622FD583700BE87F5 /* ImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C43DDC22FD54C600BE87F5 /* ImageManager.swift */; }; @@ -93,10 +77,6 @@ 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewWrapper.swift; 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 = ""; }; - 32BC086F28D23D35002451BD /* StateObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateObject.swift; sourceTree = ""; }; - 32BC087028D23D35002451BD /* OnChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnChange.swift; sourceTree = ""; }; - 32BC087128D23D35002451BD /* Overlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Overlay.swift; sourceTree = ""; }; - 32BC087228D23D35002451BD /* Backport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = ""; }; 32BD9C4623E03B08008D5F6A /* IndicatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndicatorTests.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 = ""; }; @@ -185,17 +165,6 @@ path = Transition; sourceTree = ""; }; - 32BC086E28D23D35002451BD /* Backports */ = { - isa = PBXGroup; - children = ( - 32BC086F28D23D35002451BD /* StateObject.swift */, - 32BC087028D23D35002451BD /* OnChange.swift */, - 32BC087128D23D35002451BD /* Overlay.swift */, - 32BC087228D23D35002451BD /* Backport.swift */, - ); - path = Backports; - sourceTree = ""; - }; 32C43DC222FD540D00BE87F5 = { isa = PBXGroup; children = ( @@ -232,7 +201,6 @@ children = ( 32B933E323659A0700BB7CAD /* Transition */, 326099472362E09E006EBB22 /* Indicator */, - 32BC086E28D23D35002451BD /* Backports */, 32C43DDC22FD54C600BE87F5 /* ImageManager.swift */, 32CBA77E25E4D7D800C6A8DC /* ImagePlayer.swift */, 32C43DDE22FD54C600BE87F5 /* WebImage.swift */, @@ -453,11 +421,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 32BC087328D23D35002451BD /* StateObject.swift in Sources */, - 32BC087728D23D35002451BD /* OnChange.swift in Sources */, 32B933E523659A1900BB7CAD /* Transition.swift in Sources */, 32CBA78025E4D7D800C6A8DC /* ImagePlayer.swift in Sources */, - 32BC087B28D23D35002451BD /* Overlay.swift in Sources */, 32C43E1722FD583700BE87F5 /* WebImage.swift in Sources */, 326B848C236335400011BDFB /* ProgressIndicator.swift in Sources */, 326B84822363350C0011BDFB /* Indicator.swift in Sources */, @@ -468,7 +433,6 @@ 32C43E1622FD583700BE87F5 /* ImageManager.swift in Sources */, 32C43E1822FD583700BE87F5 /* AnimatedImage.swift in Sources */, 32D26A022446B546005905DA /* Image.swift in Sources */, - 32BC087F28D23D35002451BD /* Backport.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -476,11 +440,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 32BC087428D23D35002451BD /* StateObject.swift in Sources */, - 32BC087828D23D35002451BD /* OnChange.swift in Sources */, 32B933E623659A1900BB7CAD /* Transition.swift in Sources */, 32CBA78125E4D7D800C6A8DC /* ImagePlayer.swift in Sources */, - 32BC087C28D23D35002451BD /* Overlay.swift in Sources */, 32C43E1A22FD583700BE87F5 /* WebImage.swift in Sources */, 326B848D236335400011BDFB /* ProgressIndicator.swift in Sources */, 326B84832363350C0011BDFB /* Indicator.swift in Sources */, @@ -491,7 +452,6 @@ 32C43E1922FD583700BE87F5 /* ImageManager.swift in Sources */, 32C43E1B22FD583700BE87F5 /* AnimatedImage.swift in Sources */, 32D26A032446B546005905DA /* Image.swift in Sources */, - 32BC088028D23D35002451BD /* Backport.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -499,11 +459,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 32BC087528D23D35002451BD /* StateObject.swift in Sources */, - 32BC087928D23D35002451BD /* OnChange.swift in Sources */, 32B933E723659A1900BB7CAD /* Transition.swift in Sources */, 32CBA78225E4D7D800C6A8DC /* ImagePlayer.swift in Sources */, - 32BC087D28D23D35002451BD /* Overlay.swift in Sources */, 32C43E1D22FD583800BE87F5 /* WebImage.swift in Sources */, 326B848E236335400011BDFB /* ProgressIndicator.swift in Sources */, 326B84842363350C0011BDFB /* Indicator.swift in Sources */, @@ -514,7 +471,6 @@ 32C43E1C22FD583800BE87F5 /* ImageManager.swift in Sources */, 32C43E1E22FD583800BE87F5 /* AnimatedImage.swift in Sources */, 32D26A042446B546005905DA /* Image.swift in Sources */, - 32BC088128D23D35002451BD /* Backport.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -522,11 +478,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 32BC087628D23D35002451BD /* StateObject.swift in Sources */, - 32BC087A28D23D35002451BD /* OnChange.swift in Sources */, 32B933E823659A1900BB7CAD /* Transition.swift in Sources */, 32CBA78325E4D7D800C6A8DC /* ImagePlayer.swift in Sources */, - 32BC087E28D23D35002451BD /* Overlay.swift in Sources */, 32C43E2022FD583800BE87F5 /* WebImage.swift in Sources */, 326B848F236335400011BDFB /* ProgressIndicator.swift in Sources */, 326B84852363350C0011BDFB /* Indicator.swift in Sources */, @@ -537,7 +490,6 @@ 32C43E1F22FD583800BE87F5 /* ImageManager.swift in Sources */, 32C43E2122FD583800BE87F5 /* AnimatedImage.swift in Sources */, 32D26A052446B546005905DA /* Image.swift in Sources */, - 32BC088228D23D35002451BD /* Backport.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -596,8 +548,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MACOSX_DEPLOYMENT_TARGET = 10.15; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -610,10 +562,10 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TVOS_DEPLOYMENT_TARGET = 13.0; + TVOS_DEPLOYMENT_TARGET = 14.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 6.0; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Debug; }; @@ -663,8 +615,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MACOSX_DEPLOYMENT_TARGET = 10.15; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( @@ -676,11 +628,11 @@ SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - TVOS_DEPLOYMENT_TARGET = 13.0; + TVOS_DEPLOYMENT_TARGET = 14.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 6.0; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Release; }; @@ -764,7 +716,6 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = "com.dreampiggy.SDWebImageSwiftUI-macOS"; PRODUCT_NAME = SDWebImageSwiftUI; SDKROOT = macosx; @@ -795,7 +746,6 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = "com.dreampiggy.SDWebImageSwiftUI-macOS"; PRODUCT_NAME = SDWebImageSwiftUI; SDKROOT = macosx; diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift index e454432..2835775 100644 --- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift +++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift @@ -12,7 +12,7 @@ import SDWebImage #if os(iOS) || os(tvOS) || os(macOS) /// A coordinator object used for `AnimatedImage`native view bridge for UIKit/AppKit. -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) final class AnimatedImageModel : ObservableObject { /// URL image @Published var url: URL? @@ -40,7 +40,7 @@ final class AnimatedImageModel : ObservableObject { } /// Loading Binding Object, only properties in this object can support changes from user with @State and refresh -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 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 @@ -53,7 +53,7 @@ final class AnimatedLoadingModel : ObservableObject { } /// Completion Handler Binding Object, supports dynamic @State changes -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) final class AnimatedImageHandler: ObservableObject { // Completion Handler @Published var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)? @@ -65,7 +65,7 @@ final class AnimatedImageHandler: ObservableObject { } /// Layout Binding Object, supports dynamic @State changes -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) final class AnimatedImageLayout : ObservableObject { var contentMode: ContentMode? var aspectRatio: CGFloat? @@ -77,7 +77,7 @@ final class AnimatedImageLayout : ObservableObject { } /// Configuration Binding Object, supports dynamic @State changes -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) final class AnimatedImageConfiguration: ObservableObject { var incrementalLoad: Bool? var maxBufferSize: UInt? @@ -99,7 +99,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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public struct AnimatedImage : PlatformViewRepresentable { @ObservedObject var imageModel: AnimatedImageModel @ObservedObject var imageHandler = AnimatedImageHandler() @@ -566,7 +566,7 @@ public struct AnimatedImage : PlatformViewRepresentable { } // Layout -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Configurate this view's image with the specified cap insets and options. @@ -606,7 +606,7 @@ extension AnimatedImage { } // Aspect Ratio -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Constrains this view's dimensions to the specified aspect ratio. /// - Parameters: @@ -659,7 +659,7 @@ extension AnimatedImage { } // AnimatedImage Modifier -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Total loop count for animated image rendering. Defaults to nil. @@ -736,7 +736,7 @@ extension AnimatedImage { } // Completion Handler -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Provide the action when image load fails. @@ -768,7 +768,7 @@ extension AnimatedImage { } // View Coordinator Handler -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Provide the action when view representable create the native view. @@ -796,7 +796,7 @@ extension AnimatedImage { } // Web Image convenience, based on UIKit/AppKit API -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Associate a placeholder when loading image with url @@ -837,7 +837,7 @@ extension AnimatedImage { } // Indicator -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension AnimatedImage { /// Associate a indicator when loading image with url @@ -854,7 +854,7 @@ extension AnimatedImage { } #if DEBUG -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) struct AnimatedImage_Previews : PreviewProvider { static var previews: some View { Group { diff --git a/SDWebImageSwiftUI/Classes/Backports/Backport.swift b/SDWebImageSwiftUI/Classes/Backports/Backport.swift deleted file mode 100644 index 5b0c5a4..0000000 --- a/SDWebImageSwiftUI/Classes/Backports/Backport.swift +++ /dev/null @@ -1,59 +0,0 @@ -import SwiftUI -import ObjectiveC - -/// Provides a convenient method for backporting API, -/// including types, functions, properties, property wrappers and more. -/// -/// To backport a SwiftUI Label for example, you could apply the -/// following extension: -/// -/// extension Backport where Content == Any { -/// public struct Label { } -/// } -/// -/// Now if we want to provide further extensions to our backport type, -/// we need to ensure we retain the `Content == Any` generic requirement: -/// -/// extension Backport.Label where Content == Any, Title == Text, Icon == Image { -/// public init(_ title: S, systemName: String) { } -/// } -/// -/// In addition to types, we can also provide backports for properties -/// and methods: -/// -/// extension Backport.Label where Content: View { -/// func onChange(of value: Value, perform action: (Value) -> Void) -> some View { -/// // `content` provides access to the extended type -/// content.modifier(OnChangeModifier(value, action)) -/// } -/// } -/// -public struct Backport { - - /// The underlying content this backport represents. - public let content: Wrapped - - /// Initializes a new Backport for the specified content. - /// - Parameter content: The content (type) that's being backported - public init(_ content: Wrapped) { - self.content = content - } - -} - -public extension View { - /// Wraps a SwiftUI `View` that can be extended to provide backport functionality. - var backport: Backport { .init(self) } -} - -public extension NSObjectProtocol { - /// Wraps an `NSObject` that can be extended to provide backport functionality. - var backport: Backport { .init(self) } -} - -public extension AnyTransition { - /// Wraps an `AnyTransition` that can be extended to provide backport functionality. - static var backport: Backport{ - Backport(.identity) - } -} diff --git a/SDWebImageSwiftUI/Classes/Backports/OnChange.swift b/SDWebImageSwiftUI/Classes/Backports/OnChange.swift deleted file mode 100644 index c53eaf7..0000000 --- a/SDWebImageSwiftUI/Classes/Backports/OnChange.swift +++ /dev/null @@ -1,52 +0,0 @@ -import SwiftUI -import Combine - -@available(iOS, deprecated: 14.0) -@available(macOS, deprecated: 11.0) -@available(tvOS, deprecated: 14.0) -@available(watchOS, deprecated: 7.0) -public extension Backport where Wrapped: View { - - /// Adds a modifier for this view that fires an action when a specific - /// value changes. - /// - /// `onChange` is called on the main thread. Avoid performing long-running - /// tasks on the main thread. If you need to perform a long-running task in - /// response to `value` changing, you should dispatch to a background queue. - /// - /// The new value is passed into the closure. - /// - /// - Parameters: - /// - value: The value to observe for changes - /// - action: A closure to run when the value changes. - /// - newValue: The new value that changed - /// - /// - Returns: A view that fires an action when the specified value changes. - @ViewBuilder - func onChange(of value: Value, perform action: @escaping (Value) -> Void) -> some View { - content.modifier(ChangeModifier(value: value, action: action)) - } - -} - -private struct ChangeModifier: ViewModifier { - let value: Value - let action: (Value) -> Void - - @State var oldValue: Value? - - init(value: Value, action: @escaping (Value) -> Void) { - self.value = value - self.action = action - _oldValue = .init(initialValue: value) - } - - func body(content: Content) -> some View { - content - .onReceive(Just(value)) { newValue in - guard newValue != oldValue else { return } - action(newValue) - oldValue = newValue - } - } -} diff --git a/SDWebImageSwiftUI/Classes/Backports/Overlay.swift b/SDWebImageSwiftUI/Classes/Backports/Overlay.swift deleted file mode 100644 index a6f1a1f..0000000 --- a/SDWebImageSwiftUI/Classes/Backports/Overlay.swift +++ /dev/null @@ -1,124 +0,0 @@ -import SwiftUI - -public extension Backport where Wrapped: View { - - /// Layers the views that you specify in front of this view. - /// - /// Use this modifier to place one or more views in front of another view. - /// For example, you can place a group of stars on a ``RoundedRectangle``: - /// - /// RoundedRectangle(cornerRadius: 8) - /// .frame(width: 200, height: 100) - /// .overlay(alignment: .topLeading) { Star(color: .red) } - /// .overlay(alignment: .topTrailing) { Star(color: .yellow) } - /// .overlay(alignment: .bottomLeading) { Star(color: .green) } - /// .overlay(alignment: .bottomTrailing) { Star(color: .blue) } - /// - /// The example above assumes that you've defined a `Star` view with a - /// parameterized color: - /// - /// struct Star: View { - /// var color = Color.yellow - /// - /// var body: some View { - /// Image(systemName: "star.fill") - /// .foregroundStyle(color) - /// } - /// } - /// - /// By setting different `alignment` values for each modifier, you make the - /// stars appear in different places on the rectangle: - /// - /// ![A screenshot of a rounded rectangle with a star in each corner. The - /// star in the upper-left is red; the start in the upper-right is yellow; - /// the star in the lower-left is green; the star the lower-right is - /// blue.](View-overlay-2) - /// - /// If you specify more than one view in the `content` closure, the modifier - /// collects all of the views in the closure into an implicit ``ZStack``, - /// taking them in order from back to front. For example, you can place a - /// star and a ``Circle`` on a field of ``ShapeStyle/blue``: - /// - /// Color.blue - /// .frame(width: 200, height: 200) - /// .overlay { - /// Circle() - /// .frame(width: 100, height: 100) - /// Star() - /// } - /// - /// Both the overlay modifier and the implicit ``ZStack`` composed from the - /// overlay content --- the circle and the star --- use a default - /// ``Alignment/center`` alignment. The star appears centered on the circle, - /// and both appear as a composite view centered in front of the square: - /// - /// ![A screenshot of a star centered on a circle, which is - /// centered on a square.](View-overlay-3) - /// - /// If you specify an alignment for the overlay, it applies to the implicit - /// stack rather than to the individual views in the closure. You can see - /// this if you add the ``Alignment/bottom`` alignment: - /// - /// Color.blue - /// .frame(width: 200, height: 200) - /// .overlay(alignment: .bottom) { - /// Circle() - /// .frame(width: 100, height: 100) - /// Star() - /// } - /// - /// The circle and the star move down as a unit to align the stack's bottom - /// edge with the bottom edge of the square, while the star remains - /// centered on the circle: - /// - /// ![A screenshot of a star centered on a circle, which is on a square. - /// The circle's bottom edge is aligned with the square's bottom - /// edge.](View-overlay-3a) - /// - /// To control the placement of individual items inside the `content` - /// closure, either use a different overlay modifier for each item, as the - /// earlier example of stars in the corners of a rectangle demonstrates, or - /// add an explicit ``ZStack`` inside the content closure with its own - /// alignment: - /// - /// Color.blue - /// .frame(width: 200, height: 200) - /// .overlay(alignment: .bottom) { - /// ZStack(alignment: .bottom) { - /// Circle() - /// .frame(width: 100, height: 100) - /// Star() - /// } - /// } - /// - /// The stack alignment ensures that the star's bottom edge aligns with the - /// circle's, while the overlay aligns the composite view with the square: - /// - /// ![A screenshot of a star, a circle, and a square with all their - /// bottom edges aligned.](View-overlay-4) - /// - /// You can achieve layering without an overlay modifier by putting both the - /// modified view and the overlay content into a ``ZStack``. This can - /// produce a simpler view hierarchy, but changes the layout priority that - /// SwiftUI applies to the views. Use the overlay modifier when you want the - /// modified view to dominate the layout. - /// - /// If you want to specify a ``ShapeStyle`` like a ``Color`` or a - /// ``Material`` as the overlay, use - /// ``View/overlay(_:ignoresSafeAreaEdges:)`` instead. To specify a - /// ``Shape``, use ``View/overlay(_:in:fillStyle:)``. - /// - /// - Parameters: - /// - alignment: The alignment that the modifier uses to position the - /// implicit ``ZStack`` that groups the foreground views. The default - /// is ``Alignment/center``. - /// - content: A ``ViewBuilder`` that you use to declare the views to - /// draw in front of this view, stacked in the order that you list them. - /// The last view that you list appears at the front of the stack. - /// - /// - Returns: A view that uses the specified content as a foreground. - func overlay(alignment: Alignment = .center, @ViewBuilder _ content: () -> Content) -> some View { - self.content.overlay(content(), alignment: alignment) - } - -} diff --git a/SDWebImageSwiftUI/Classes/Backports/StateObject.swift b/SDWebImageSwiftUI/Classes/Backports/StateObject.swift deleted file mode 100644 index 5d47b2b..0000000 --- a/SDWebImageSwiftUI/Classes/Backports/StateObject.swift +++ /dev/null @@ -1,151 +0,0 @@ -import Combine -import SwiftUI - -@available(iOS, deprecated: 14.0) -@available(macOS, deprecated: 11.0) -@available(tvOS, deprecated: 14.0) -@available(watchOS, deprecated: 7.0) -public extension Backport where Wrapped: ObservableObject { - - /// A property wrapper type that instantiates an observable object. - /// - /// Create a state object in a ``SwiftUI/View``, ``SwiftUI/App``, or - /// ``SwiftUI/Scene`` by applying the `@Backport.StateObject` attribute to a property - /// declaration and providing an initial value that conforms to the - /// - /// protocol: - /// - /// @Backport.StateObject var model = DataModel() - /// - /// SwiftUI creates a new instance of the object only once for each instance of - /// the structure that declares the object. When published properties of the - /// observable object change, SwiftUI updates the parts of any view that depend - /// on those properties: - /// - /// Text(model.title) // Updates the view any time `title` changes. - /// - /// You can pass the state object into a property that has the - /// ``SwiftUI/ObservedObject`` attribute. You can alternatively add the object - /// to the environment of a view hierarchy by applying the - /// ``SwiftUI/View/environmentObject(_:)`` modifier: - /// - /// ContentView() - /// .environmentObject(model) - /// - /// If you create an environment object as shown in the code above, you can - /// read the object inside `ContentView` or any of its descendants - /// using the ``SwiftUI/EnvironmentObject`` attribute: - /// - /// @EnvironmentObject var model: DataModel - /// - /// Get a ``SwiftUI/Binding`` to one of the state object's properties using the - /// `$` operator. Use a binding when you want to create a two-way connection to - /// one of the object's properties. For example, you can let a - /// ``SwiftUI/Toggle`` control a Boolean value called `isEnabled` stored in the - /// model: - /// - /// Toggle("Enabled", isOn: $model.isEnabled) - @propertyWrapper struct StateObject: DynamicProperty { - private final class Wrapper: ObservableObject { - private var subject = PassthroughSubject() - - var value: Wrapped? { - didSet { - cancellable = nil - cancellable = value?.objectWillChange - .sink { [subject] _ in subject.send() } - } - } - - private var cancellable: AnyCancellable? - - var objectWillChange: AnyPublisher { - subject.eraseToAnyPublisher() - } - } - - @State private var state = Wrapper() - - @ObservedObject private var observedObject = Wrapper() - - private var thunk: () -> Wrapped - - /// The underlying value referenced by the state object. - /// - /// The wrapped value property provides primary access to the value's data. - /// However, you don't access `wrappedValue` directly. Instead, use the - /// property variable created with the `@Backport.StateObject` attribute: - /// - /// @Backport.StateObject var contact = Contact() - /// - /// var body: some View { - /// Text(contact.name) // Accesses contact's wrapped value. - /// } - /// - /// When you change a property of the wrapped value, you can access the new - /// value immediately. However, SwiftUI updates views displaying the value - /// asynchronously, so the user interface might not update immediately. - public var wrappedValue: Wrapped { - if let object = state.value { - return object - } else { - let object = thunk() - state.value = object - return object - } - } - - /// A projection of the state object that creates bindings to its - /// properties. - /// - /// Use the projected value to pass a binding value down a view hierarchy. - /// To get the projected value, prefix the property variable with `$`. For - /// example, you can get a binding to a model's `isEnabled` Boolean so that - /// a ``SwiftUI/Toggle`` view can control the value: - /// - /// struct MyView: View { - /// @Backport.StateObject var model = DataModel() - /// - /// var body: some View { - /// Toggle("Enabled", isOn: $model.isEnabled) - /// } - /// } - public var projectedValue: ObservedObject.Wrapper { - ObservedObject(wrappedValue: wrappedValue).projectedValue - } - - /// Creates a new state object with an initial wrapped value. - /// - /// You don’t call this initializer directly. Instead, declare a property - /// with the `@Backport.StateObject` attribute in a ``SwiftUI/View``, - /// ``SwiftUI/App``, or ``SwiftUI/Scene``, and provide an initial value: - /// - /// struct MyView: View { - /// @Backport.StateObject var model = DataModel() - /// - /// // ... - /// } - /// - /// SwiftUI creates only one instance of the state object for each - /// container instance that you declare. In the code above, SwiftUI - /// creates `model` only the first time it initializes a particular instance - /// of `MyView`. On the other hand, each different instance of `MyView` - /// receives a distinct copy of the data model. - /// - /// - Parameter thunk: An initial value for the state object. - public init(wrappedValue thunk: @autoclosure @escaping () -> Wrapped) { - self.thunk = thunk - } - - public mutating func update() { - if state.value == nil { - state.value = thunk() - } - if observedObject.value !== state.value { - observedObject.value = state.value - } - } - } - -} - diff --git a/SDWebImageSwiftUI/Classes/Image.swift b/SDWebImageSwiftUI/Classes/Image.swift index 28ba969..4dd60ad 100644 --- a/SDWebImageSwiftUI/Classes/Image.swift +++ b/SDWebImageSwiftUI/Classes/Image.swift @@ -9,7 +9,7 @@ import Foundation import SwiftUI -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension PlatformImage { static var empty = PlatformImage() } #if os(iOS) || os(tvOS) || os(watchOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 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 d9bce8f..2c134ce 100644 --- a/SDWebImageSwiftUI/Classes/ImageManager.swift +++ b/SDWebImageSwiftUI/Classes/ImageManager.swift @@ -11,7 +11,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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@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? @@ -107,7 +107,7 @@ public final class ImageManager : ObservableObject { } // Completion Handler -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 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 4fdc405..4f0f1a8 100644 --- a/SDWebImageSwiftUI/Classes/ImagePlayer.swift +++ b/SDWebImageSwiftUI/Classes/ImagePlayer.swift @@ -10,7 +10,7 @@ import SwiftUI 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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 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 80f936e..e13e92a 100644 --- a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift +++ b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift @@ -12,7 +12,7 @@ import SDWebImage #if os(iOS) || os(tvOS) || os(macOS) /// Use wrapper to solve tne `UIImageView`/`NSImageView` frame size become image size issue (SwiftUI's Bug) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public class AnimatedImageViewWrapper : PlatformView { var wrapped = SDAnimatedImageView() var interpolationQuality = CGInterpolationQuality.default @@ -67,7 +67,7 @@ public class AnimatedImageViewWrapper : PlatformView { } /// Use wrapper to solve the `UIProgressView`/`NSProgressIndicator` frame origin NaN crash (SwiftUI's bug) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public class ProgressIndicatorWrapper : PlatformView { #if os(macOS) var wrapped = NSProgressIndicator() @@ -98,7 +98,7 @@ public class ProgressIndicatorWrapper : PlatformView { } } -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 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/ActivityIndicator.swift b/SDWebImageSwiftUI/Classes/Indicator/ActivityIndicator.swift index 7103c77..2592ebd 100644 --- a/SDWebImageSwiftUI/Classes/Indicator/ActivityIndicator.swift +++ b/SDWebImageSwiftUI/Classes/Indicator/ActivityIndicator.swift @@ -10,7 +10,7 @@ import SwiftUI #if os(macOS) || os(iOS) || os(tvOS) /// An activity indicator (system style) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public struct ActivityIndicator: PlatformViewRepresentable { @Binding var isAnimating: Bool var style: Style @@ -72,7 +72,7 @@ public struct ActivityIndicator: PlatformViewRepresentable { #endif } -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension ActivityIndicator { public enum Style { case medium diff --git a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift index 37eb903..69fbb4d 100644 --- a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift +++ b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift @@ -10,7 +10,7 @@ import Foundation import SwiftUI /// A type to build the indicator -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@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 @@ -36,7 +36,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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public struct IndicatorViewModifier : ViewModifier where T : View { /// The loading status @@ -45,21 +45,22 @@ public struct IndicatorViewModifier : ViewModifier where T : View { /// The indicator public var indicator: Indicator + @ViewBuilder + private var overlay: some View { + if status.isLoading { + indicator.content($status.isLoading, $status.progress) + } + } + public func body(content: Content) -> some View { ZStack { - content - .backport - .overlay { - if status.isLoading { - indicator.content($status.isLoading, $status.progress) - } - } + content.overlay(overlay, alignment: .center) } } } #if os(macOS) || os(iOS) || os(tvOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension Indicator where T == ActivityIndicator { /// Activity Indicator public static var activity: Indicator { @@ -77,7 +78,7 @@ extension Indicator where T == ActivityIndicator { } } -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension Indicator where T == ProgressIndicator { /// Progress Indicator public static var progress: Indicator { diff --git a/SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift b/SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift index 1256ee8..a017066 100644 --- a/SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift +++ b/SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift @@ -10,7 +10,7 @@ import SwiftUI #if os(macOS) || os(iOS) || os(tvOS) /// A progress bar indicator (system style) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public struct ProgressIndicator: PlatformViewRepresentable { @Binding var isAnimating: Bool @Binding var progress: Double @@ -102,7 +102,7 @@ public struct ProgressIndicator: PlatformViewRepresentable { #endif } -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension ProgressIndicator { public enum Style { case `default` diff --git a/SDWebImageSwiftUI/Classes/SDWebImageSwiftUI.swift b/SDWebImageSwiftUI/Classes/SDWebImageSwiftUI.swift index 9c0e71a..45ae3c2 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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformImage = NSImage #else -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformImage = UIImage #endif #if os(macOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformView = NSView #endif #if os(iOS) || os(tvOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformView = UIView #endif #if os(watchOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformView = WKInterfaceObject #endif #if os(macOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformViewRepresentable = NSViewRepresentable #endif #if os(iOS) || os(tvOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformViewRepresentable = UIViewRepresentable #endif #if os(watchOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public typealias PlatformViewRepresentable = WKInterfaceObjectRepresentable #endif #if os(macOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension NSViewRepresentable { typealias PlatformViewType = NSViewType } #endif #if os(iOS) || os(tvOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension UIViewRepresentable { typealias PlatformViewType = UIViewType } #endif #if os(watchOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension WKInterfaceObjectRepresentable { typealias PlatformViewType = WKInterfaceObjectType } diff --git a/SDWebImageSwiftUI/Classes/SwiftUICompatibility.swift b/SDWebImageSwiftUI/Classes/SwiftUICompatibility.swift index e7a4639..1e9f66c 100644 --- a/SDWebImageSwiftUI/Classes/SwiftUICompatibility.swift +++ b/SDWebImageSwiftUI/Classes/SwiftUICompatibility.swift @@ -11,7 +11,7 @@ import SwiftUI #if os(iOS) || os(tvOS) || os(macOS) -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) struct PlatformAppear: PlatformViewRepresentable { let appearAction: () -> Void let disappearAction: () -> Void @@ -38,7 +38,7 @@ struct PlatformAppear: PlatformViewRepresentable { #endif } -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) class PlatformAppearView: PlatformView { var appearAction: () -> Void = {} var disappearAction: () -> Void = {} @@ -74,7 +74,7 @@ class PlatformAppearView: PlatformView { #endif -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@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. diff --git a/SDWebImageSwiftUI/Classes/Transition/Transition.swift b/SDWebImageSwiftUI/Classes/Transition/Transition.swift index c4a908f..e42503c 100644 --- a/SDWebImageSwiftUI/Classes/Transition/Transition.swift +++ b/SDWebImageSwiftUI/Classes/Transition/Transition.swift @@ -8,7 +8,7 @@ import SwiftUI -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 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 2290182..faaa423 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -10,7 +10,7 @@ import SwiftUI import SDWebImage /// Data Binding Object, only properties in this object can support changes from user with @State and refresh -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) final class WebImageModel : ObservableObject { /// URL image @Published var url: URL? @@ -19,7 +19,7 @@ final class WebImageModel : ObservableObject { } /// Completion Handler Binding Object, supports dynamic @State changes -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) final class WebImageHandler: ObservableObject { // Completion Handler @Published var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)? @@ -28,7 +28,7 @@ final class WebImageHandler: ObservableObject { } /// Configuration Binding Object, supports dynamic @State changes -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) final class WebImageConfiguration: ObservableObject { var retryOnAppear: Bool = true var cancelOnDisappear: Bool = true @@ -42,7 +42,7 @@ final class WebImageConfiguration: ObservableObject { } /// A Image View type to load image from url. Supports static/animated image format. -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) public struct WebImage : View { var configurations: [(Image) -> Image] = [] @@ -63,11 +63,9 @@ public struct WebImage : View { @ObservedObject var indicatorStatus : IndicatorStatus - // FIXME: Use SwiftUI StateObject and remove onPlatformAppear once drop iOS 13 support - @Backport.StateObject var imagePlayer = ImagePlayer() + @StateObject var imagePlayer = ImagePlayer() - // FIXME: Use SwiftUI StateObject and remove onPlatformAppear once drop iOS 13 support - @Backport.StateObject var imageManager : ImageManager + @StateObject var imageManager : ImageManager /// Create a web image with url, placeholder, custom options and context. Optional can support animated image using Binding. /// - Parameter url: The image url @@ -89,7 +87,7 @@ public struct WebImage : View { imageModel.context = context _imageModel = ObservedObject(wrappedValue: imageModel) let imageManager = ImageManager() - _imageManager = Backport.StateObject(wrappedValue: imageManager) + _imageManager = StateObject(wrappedValue: imageManager) _indicatorStatus = ObservedObject(wrappedValue: imageManager.indicatorStatus) } @@ -292,7 +290,7 @@ public struct WebImage : View { } // Layout -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension WebImage { func configure(_ block: @escaping (Image) -> Image) -> WebImage { var result = self @@ -330,7 +328,7 @@ extension WebImage { } // Completion Handler -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension WebImage { /// Provide the action when image load fails. @@ -362,7 +360,7 @@ extension WebImage { } // WebImage Modifier -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension WebImage { /// Associate a placeholder when loading image with url @@ -401,7 +399,7 @@ extension WebImage { } // Indicator -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension WebImage { /// Associate a indicator when loading image with url @@ -418,7 +416,7 @@ extension WebImage { } // Animated Image -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) extension WebImage { /// Total loop count for animated image rendering. Defaults to nil. @@ -486,7 +484,7 @@ extension WebImage { } #if DEBUG -@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) +@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) struct WebImage_Previews : PreviewProvider { static var previews: some View { Group { From 63e1aebbf6dc3d435fc345460c70e816fd2016da Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 26 Dec 2022 17:46:33 +0800 Subject: [PATCH 2/7] Remove the legacy ActivityIndicator/ProgressIndicator, use ProrgessView --- ...eSwiftUIDemo-watchOS WatchKit App.xcscheme | 25 +--- .../SDWebImageSwiftUIDemo/ContentView.swift | 17 --- .../Classes/Indicator/ActivityIndicator.swift | 82 ------------- .../Classes/Indicator/Indicator.swift | 25 ++-- .../Classes/Indicator/ProgressIndicator.swift | 114 ------------------ 5 files changed, 18 insertions(+), 245 deletions(-) delete mode 100644 SDWebImageSwiftUI/Classes/Indicator/ActivityIndicator.swift delete mode 100644 SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift diff --git a/Example/SDWebImageSwiftUI.xcodeproj/xcshareddata/xcschemes/SDWebImageSwiftUIDemo-watchOS WatchKit App.xcscheme b/Example/SDWebImageSwiftUI.xcodeproj/xcshareddata/xcschemes/SDWebImageSwiftUIDemo-watchOS WatchKit App.xcscheme index 3d1f081..65df20b 100644 --- a/Example/SDWebImageSwiftUI.xcodeproj/xcshareddata/xcschemes/SDWebImageSwiftUIDemo-watchOS WatchKit App.xcscheme +++ b/Example/SDWebImageSwiftUI.xcodeproj/xcshareddata/xcschemes/SDWebImageSwiftUIDemo-watchOS WatchKit App.xcscheme @@ -54,10 +54,8 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + - + - + - - - - - + diff --git a/Example/SDWebImageSwiftUIDemo/ContentView.swift b/Example/SDWebImageSwiftUIDemo/ContentView.swift index a3619c9..418bad7 100644 --- a/Example/SDWebImageSwiftUIDemo/ContentView.swift +++ b/Example/SDWebImageSwiftUIDemo/ContentView.swift @@ -17,23 +17,6 @@ class UserSettings: ObservableObject { #endif } -#if os(watchOS) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) -extension Indicator where T == ProgressView { - static var activity: Indicator { - Indicator { isAnimating, progress in - ProgressView() - } - } - - static var progress: Indicator { - Indicator { isAnimating, progress in - ProgressView(value: progress.wrappedValue) - } - } -} -#endif - // Test Switching url using @State struct ContentView2: View { @State var imageURLs = [ diff --git a/SDWebImageSwiftUI/Classes/Indicator/ActivityIndicator.swift b/SDWebImageSwiftUI/Classes/Indicator/ActivityIndicator.swift deleted file mode 100644 index 2592ebd..0000000 --- a/SDWebImageSwiftUI/Classes/Indicator/ActivityIndicator.swift +++ /dev/null @@ -1,82 +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 SwiftUI - -#if os(macOS) || os(iOS) || os(tvOS) -/// An activity indicator (system style) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) -public struct ActivityIndicator: PlatformViewRepresentable { - @Binding var isAnimating: Bool - var style: Style - - /// Create the indicator with animation binding and style - /// - Parameters: - /// - isAnimating: The binding to control the animation - /// - style: The indicator style - public init(_ isAnimating: Binding, style: Style = .medium) { - self._isAnimating = isAnimating - self.style = style - } - - #if os(macOS) - public typealias NSViewType = NSProgressIndicator - #elseif os(iOS) || os(tvOS) - public typealias UIViewType = UIActivityIndicatorView - #endif - - #if os(iOS) || os(tvOS) - public func makeUIView(context: UIViewRepresentableContext) -> UIActivityIndicatorView { - let activityStyle: UIActivityIndicatorView.Style - switch style { - case .medium: - activityStyle = .medium - case .large: - activityStyle = .large - } - let indicator = UIActivityIndicatorView(style: activityStyle) - indicator.hidesWhenStopped = true - return indicator - } - - public func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext) { - isAnimating ? uiView.startAnimating() : uiView.stopAnimating() - } - #endif - - #if os(macOS) - public func makeNSView(context: NSViewRepresentableContext) -> NSProgressIndicator { - let controlSize: NSControl.ControlSize - switch style { - case .medium: - controlSize = .small - case .large: - controlSize = .regular - } - let indicator = NSProgressIndicator() - indicator.style = .spinning - indicator.controlSize = controlSize - indicator.isDisplayedWhenStopped = false - return indicator - } - - public func updateNSView(_ nsView: NSProgressIndicator, context: NSViewRepresentableContext) { - isAnimating ? nsView.startAnimation(nil) : nsView.stopAnimation(nil) - } - - #endif -} - -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) -extension ActivityIndicator { - public enum Style { - case medium - case large - } -} -#endif diff --git a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift index 69fbb4d..499aafd 100644 --- a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift +++ b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift @@ -54,45 +54,44 @@ public struct IndicatorViewModifier : ViewModifier where T : View { public func body(content: Content) -> some View { ZStack { - content.overlay(overlay, alignment: .center) + content + overlay } } } -#if os(macOS) || os(iOS) || os(tvOS) @available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) -extension Indicator where T == ActivityIndicator { +extension Indicator where T == AnyView { /// Activity Indicator - public static var activity: Indicator { + public static var activity: Indicator { Indicator { isAnimating, _ in - ActivityIndicator(isAnimating) + AnyView(ProgressView().opacity(isAnimating.wrappedValue ? 1 : 0)) } } /// Activity Indicator with style /// - Parameter style: style - public static func activity(style: ActivityIndicator.Style) -> Indicator { + public static func activity(style: any ProgressViewStyle) -> Indicator { Indicator { isAnimating, _ in - ActivityIndicator(isAnimating, style: style) + AnyView(ProgressView().progressViewStyle(style).opacity(isAnimating.wrappedValue ? 1 : 0)) } } } @available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) -extension Indicator where T == ProgressIndicator { +extension Indicator where T == AnyView { /// Progress Indicator - public static var progress: Indicator { + public static var progress: Indicator { Indicator { isAnimating, progress in - ProgressIndicator(isAnimating, progress: progress) + AnyView(ProgressView(value: progress.wrappedValue).opacity(isAnimating.wrappedValue ? 1 : 0)) } } /// Progress Indicator with style /// - Parameter style: style - public static func progress(style: ProgressIndicator.Style) -> Indicator { + public static func progress(style: any ProgressViewStyle) -> Indicator { Indicator { isAnimating, progress in - ProgressIndicator(isAnimating, progress: progress, style: style) + AnyView(ProgressView(value: progress.wrappedValue).progressViewStyle(style).opacity(isAnimating.wrappedValue ? 1 : 0)) } } } -#endif diff --git a/SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift b/SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift deleted file mode 100644 index a017066..0000000 --- a/SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift +++ /dev/null @@ -1,114 +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 SwiftUI - -#if os(macOS) || os(iOS) || os(tvOS) -/// A progress bar indicator (system style) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) -public struct ProgressIndicator: PlatformViewRepresentable { - @Binding var isAnimating: Bool - @Binding var progress: Double - var style: Style - - /// Create indicator with animation binding, progress binding and the style - /// - Parameters: - /// - isAnimating: The binding to control the animation - /// - progress: The binding to update the progress - /// - style: The indicator style - public init(_ isAnimating: Binding, progress: Binding, style: Style = .default) { - self._isAnimating = isAnimating - self._progress = progress - self.style = style - } - - #if os(macOS) - public typealias NSViewType = ProgressIndicatorWrapper - #elseif os(iOS) || os(tvOS) - public typealias UIViewType = ProgressIndicatorWrapper - #endif - - #if os(iOS) || os(tvOS) - public func makeUIView(context: UIViewRepresentableContext) -> ProgressIndicatorWrapper { - let progressStyle: UIProgressView.Style - switch style { - #if os(iOS) - case .bar: - progressStyle = .bar - #endif - case .default: - progressStyle = .default - } - let uiView = ProgressIndicatorWrapper() - let view = uiView.wrapped - view.progressViewStyle = progressStyle - return uiView - } - - public func updateUIView(_ uiView: ProgressIndicatorWrapper, context: UIViewRepresentableContext) { - let view = uiView.wrapped - if isAnimating { - view.setProgress(Float(progress), animated: true) - } else { - if progress == 0 { - view.isHidden = false - view.progress = 0 - } else { - view.isHidden = true - view.progress = 1 - } - } - } - #endif - - #if os(macOS) - public func makeNSView(context: NSViewRepresentableContext) -> ProgressIndicatorWrapper { - let nsView = ProgressIndicatorWrapper() - let view = nsView.wrapped - view.style = .bar - view.isDisplayedWhenStopped = false - view.controlSize = .small - view.frame = CGRect(x: 0, y: 0, width: 160, height: 0) // Width from `UIProgressView` default width - view.sizeToFit() - view.autoresizingMask = [.maxXMargin, .minXMargin, .maxYMargin, .minYMargin] - return nsView - } - - public func updateNSView(_ nsView: ProgressIndicatorWrapper, context: NSViewRepresentableContext) { - let view = nsView.wrapped - if isAnimating { - view.isIndeterminate = false - view.doubleValue = Double(progress) * 100 - view.startAnimation(nil) - } else { - if progress == 0 { - view.isHidden = false - view.isIndeterminate = true - view.doubleValue = 0 - view.stopAnimation(nil) - } else { - view.isHidden = true - view.isIndeterminate = false - view.doubleValue = 100 - view.stopAnimation(nil) - } - } - } - #endif -} - -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) -extension ProgressIndicator { - public enum Style { - case `default` - #if os(iOS) - case bar - #endif - } -} -#endif From f2a7b990841c44bd88f8bb01c1de5ff6556ecb64 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 26 Dec 2022 18:04:02 +0800 Subject: [PATCH 3/7] Use the generic signature for ProgressiveStyle This match Apple's API --- Example/SDWebImageSwiftUIDemo/DetailView.swift | 2 +- SDWebImageSwiftUI/Classes/Indicator/Indicator.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Example/SDWebImageSwiftUIDemo/DetailView.swift b/Example/SDWebImageSwiftUIDemo/DetailView.swift index f9e7dea..1c2b723 100644 --- a/Example/SDWebImageSwiftUIDemo/DetailView.swift +++ b/Example/SDWebImageSwiftUIDemo/DetailView.swift @@ -111,7 +111,7 @@ struct DetailView: View { WebImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder], isAnimating: $isAnimating) .resizable() .placeholder(.wifiExclamationmark) - .indicator(.progress) + .indicator(.progress(style: .circular)) .scaledToFit() } } diff --git a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift index 499aafd..76113ce 100644 --- a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift +++ b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift @@ -71,7 +71,7 @@ extension Indicator where T == AnyView { /// Activity Indicator with style /// - Parameter style: style - public static func activity(style: any ProgressViewStyle) -> Indicator { + public static func activity(style: S) -> Indicator where S: ProgressViewStyle { Indicator { isAnimating, _ in AnyView(ProgressView().progressViewStyle(style).opacity(isAnimating.wrappedValue ? 1 : 0)) } @@ -89,7 +89,7 @@ extension Indicator where T == AnyView { /// Progress Indicator with style /// - Parameter style: style - public static func progress(style: any ProgressViewStyle) -> Indicator { + public static func progress(style: S) -> Indicator where S: ProgressViewStyle { Indicator { isAnimating, progress in AnyView(ProgressView(value: progress.wrappedValue).progressViewStyle(style).opacity(isAnimating.wrappedValue ? 1 : 0)) } From 2b2ee4f671d17470b8fedff99e5ca2431a133349 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 26 Dec 2022 18:07:45 +0800 Subject: [PATCH 4/7] Remove the unused ProgressIndicatorWrapper --- .../Classes/ImageViewWrapper.swift | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift index e13e92a..88a3549 100644 --- a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift +++ b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift @@ -66,38 +66,6 @@ public class AnimatedImageViewWrapper : PlatformView { } } -/// Use wrapper to solve the `UIProgressView`/`NSProgressIndicator` frame origin NaN crash (SwiftUI's bug) -@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *) -public class ProgressIndicatorWrapper : PlatformView { - #if os(macOS) - var wrapped = NSProgressIndicator() - #else - var wrapped = UIProgressView(progressViewStyle: .default) - #endif - - #if os(macOS) - public override func layout() { - super.layout() - wrapped.setFrameOrigin(CGPoint(x: round(self.bounds.width - wrapped.frame.width) / 2, y: round(self.bounds.height - wrapped.frame.height) / 2)) - } - #else - public override func layoutSubviews() { - super.layoutSubviews() - wrapped.center = self.center - } - #endif - - public override init(frame frameRect: CGRect) { - super.init(frame: frameRect) - addSubview(wrapped) - } - - public required init?(coder: NSCoder) { - super.init(coder: coder) - addSubview(wrapped) - } -} - @available(iOS 14.0, OSX 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. From 8192aecee6ba3e11919687a8c318850455a66fa8 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 26 Dec 2022 18:25:16 +0800 Subject: [PATCH 5/7] Remove the unused HostingView hack --- SDWebImageSwiftUI/Classes/AnimatedImage.swift | 15 --------------- SDWebImageSwiftUI/Classes/ImageViewWrapper.swift | 14 -------------- 2 files changed, 29 deletions(-) diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift index 2835775..73aa223 100644 --- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift +++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift @@ -261,21 +261,6 @@ public struct AnimatedImage : PlatformViewRepresentable { } self.imageHandler.progressBlock?(receivedSize, expectedSize) }) { (image, data, error, cacheType, finished, _) in - if #available(iOS 14.0, macOS 11.0, watchOS 7.0, tvOS 14.0, *) { - // Do nothing. on iOS 14's SwiftUI, the @Published will always trigger another `updateUIView` call with new UIView instance. - } else { - // This is a hack because of iOS 13's SwiftUI bug, the @Published does not trigger another `updateUIView` call - // Here I have to use UIKit/AppKit API to triger the same effect (the window change implicitly cause re-render) - if let hostingView = view.findHostingView() { - if let _ = hostingView.window { - #if os(macOS) - hostingView.viewDidMoveToWindow() - #else - hostingView.didMoveToWindow() - #endif - } - } - } context.coordinator.imageLoading.image = image context.coordinator.imageLoading.isLoading = false context.coordinator.imageLoading.progress = 1 diff --git a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift index 88a3549..f49aa1a 100644 --- a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift +++ b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift @@ -82,20 +82,6 @@ extension PlatformView { self.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 0).isActive = true self.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: 0).isActive = true } - - /// Finding the HostingView for UIKit/AppKit View. - /// - Parameter entry: The entry platform view - /// - Returns: The hosting view. - func findHostingView() -> PlatformView? { - var superview = self.superview - while let s = superview { - if NSStringFromClass(type(of: s)).contains("HostingView") { - return s - } - superview = s.superview - } - return nil - } } #endif From 84e792704bb3c8e71086ae40f84401609cee7145 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 26 Dec 2022 19:17:20 +0800 Subject: [PATCH 6/7] Removed the unused IndicatorTests --- .../project.pbxproj | 8 -- Tests/IndicatorTests.swift | 84 ------------------- 2 files changed, 92 deletions(-) delete mode 100644 Tests/IndicatorTests.swift diff --git a/Example/SDWebImageSwiftUI.xcodeproj/project.pbxproj b/Example/SDWebImageSwiftUI.xcodeproj/project.pbxproj index c370771..044416f 100644 --- a/Example/SDWebImageSwiftUI.xcodeproj/project.pbxproj +++ b/Example/SDWebImageSwiftUI.xcodeproj/project.pbxproj @@ -15,19 +15,16 @@ 320CDC3222FADB45007CF858 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 320CDC3122FADB45007CF858 /* Assets.xcassets */; }; 320CDC3522FADB45007CF858 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 320CDC3422FADB45007CF858 /* Preview Assets.xcassets */; }; 320CDC3822FADB45007CF858 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 320CDC3622FADB45007CF858 /* LaunchScreen.storyboard */; }; - 322E0DF728D331A20003A55F /* IndicatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF028D331A20003A55F /* IndicatorTests.swift */; }; 322E0DF828D331A20003A55F /* WebImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF128D331A20003A55F /* WebImageTests.swift */; }; 322E0DF928D331A20003A55F /* Images.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 322E0DF228D331A20003A55F /* Images.bundle */; }; 322E0DFA28D331A20003A55F /* ImageManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF328D331A20003A55F /* ImageManagerTests.swift */; }; 322E0DFB28D331A20003A55F /* AnimatedImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF428D331A20003A55F /* AnimatedImageTests.swift */; }; 322E0DFD28D331A20003A55F /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF628D331A20003A55F /* TestUtils.swift */; }; 322E0E1828D3320D0003A55F /* ImageManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF328D331A20003A55F /* ImageManagerTests.swift */; }; - 322E0E1928D3320D0003A55F /* IndicatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF028D331A20003A55F /* IndicatorTests.swift */; }; 322E0E1A28D3320D0003A55F /* WebImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF128D331A20003A55F /* WebImageTests.swift */; }; 322E0E1B28D3320D0003A55F /* AnimatedImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF428D331A20003A55F /* AnimatedImageTests.swift */; }; 322E0E1C28D3320D0003A55F /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF628D331A20003A55F /* TestUtils.swift */; }; 322E0E1D28D3320D0003A55F /* ImageManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF328D331A20003A55F /* ImageManagerTests.swift */; }; - 322E0E1E28D3320D0003A55F /* IndicatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF028D331A20003A55F /* IndicatorTests.swift */; }; 322E0E1F28D3320D0003A55F /* WebImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF128D331A20003A55F /* WebImageTests.swift */; }; 322E0E2028D3320D0003A55F /* AnimatedImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF428D331A20003A55F /* AnimatedImageTests.swift */; }; 322E0E2128D3320D0003A55F /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322E0DF628D331A20003A55F /* TestUtils.swift */; }; @@ -142,7 +139,6 @@ 320CDC3722FADB45007CF858 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 320CDC3922FADB45007CF858 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 322E0DE628D3318B0003A55F /* SDWebImageSwiftUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SDWebImageSwiftUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 322E0DF028D331A20003A55F /* IndicatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndicatorTests.swift; sourceTree = ""; }; 322E0DF128D331A20003A55F /* WebImageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebImageTests.swift; sourceTree = ""; }; 322E0DF228D331A20003A55F /* Images.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Images.bundle; sourceTree = ""; }; 322E0DF328D331A20003A55F /* ImageManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageManagerTests.swift; sourceTree = ""; }; @@ -323,7 +319,6 @@ 322E0DEF28D331A20003A55F /* Tests */ = { isa = PBXGroup; children = ( - 322E0DF028D331A20003A55F /* IndicatorTests.swift */, 322E0DF128D331A20003A55F /* WebImageTests.swift */, 322E0DF228D331A20003A55F /* Images.bundle */, 322E0DF328D331A20003A55F /* ImageManagerTests.swift */, @@ -1159,7 +1154,6 @@ 322E0DF828D331A20003A55F /* WebImageTests.swift in Sources */, 322E0DFD28D331A20003A55F /* TestUtils.swift in Sources */, 322E0DFB28D331A20003A55F /* AnimatedImageTests.swift in Sources */, - 322E0DF728D331A20003A55F /* IndicatorTests.swift in Sources */, 322E0DFA28D331A20003A55F /* ImageManagerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1172,7 +1166,6 @@ 322E0E1A28D3320D0003A55F /* WebImageTests.swift in Sources */, 322E0E1828D3320D0003A55F /* ImageManagerTests.swift in Sources */, 322E0E1C28D3320D0003A55F /* TestUtils.swift in Sources */, - 322E0E1928D3320D0003A55F /* IndicatorTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1184,7 +1177,6 @@ 322E0E1F28D3320D0003A55F /* WebImageTests.swift in Sources */, 322E0E1D28D3320D0003A55F /* ImageManagerTests.swift in Sources */, 322E0E2128D3320D0003A55F /* TestUtils.swift in Sources */, - 322E0E1E28D3320D0003A55F /* IndicatorTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/IndicatorTests.swift b/Tests/IndicatorTests.swift deleted file mode 100644 index cef4ae4..0000000 --- a/Tests/IndicatorTests.swift +++ /dev/null @@ -1,84 +0,0 @@ -import XCTest -import SwiftUI -import ViewInspector -@testable import SDWebImageSwiftUI - -extension ActivityIndicator : Inspectable {} -extension ProgressIndicator : Inspectable {} - -#if os(iOS) || os(tvOS) -typealias ActivityIndicatorViewType = UIActivityIndicatorView -typealias ProgressIndicatorViewType = UIProgressView -#else -typealias ActivityIndicatorViewType = NSProgressIndicator -typealias ProgressIndicatorViewType = NSProgressIndicator -#endif - -class IndicatorTests: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - SDImageCache.shared.clear(with: .all) - } - - func testActivityIndicator() throws { - let expectation = self.expectation(description: "Activity indicator") - let binding = Binding(wrappedValue: true) - let indicator = ActivityIndicator(binding, style: .medium) - ViewHosting.host(view: indicator) - let indicatorView = try indicator.inspect().actualView().platformView() - #if os(iOS) || os(tvOS) - XCTAssertTrue(indicatorView.isAnimating) - #endif - binding.wrappedValue = false - XCTAssertFalse(binding.wrappedValue) - XCTAssertFalse(indicator.isAnimating) - #if os(iOS) || os(tvOS) - indicatorView.stopAnimating() - #else - indicatorView.stopAnimation(nil) - #endif - #if os(iOS) || os(tvOS) - XCTAssertFalse(indicatorView.isAnimating) - #endif - expectation.fulfill() - self.waitForExpectations(timeout: 5, handler: nil) - ViewHosting.expel() - } - - func testProgressIndicator() throws { - let expectation = self.expectation(description: "Progress indicator") - let binding = Binding(wrappedValue: true) - let progress = Binding(wrappedValue: 0) - let indicator = ProgressIndicator(binding, progress: progress) - ViewHosting.host(view: indicator) - let indicatorView = try indicator.inspect().actualView().platformView().wrapped - #if os(iOS) || os(tvOS) - XCTAssertEqual(indicatorView.progress, 0.0) - #else - XCTAssertEqual(indicatorView.doubleValue, 0.0) - #endif - progress.wrappedValue = 1.0 - XCTAssertEqual(indicator.progress, 1.0) - #if os(iOS) || os(tvOS) - indicatorView.setProgress(1.0, animated: true) - #else - indicatorView.increment(by: 1.0) - #endif - #if os(iOS) || os(tvOS) - XCTAssertEqual(indicatorView.progress, 1.0) - #else - XCTAssertEqual(indicatorView.doubleValue, 1.0) - #endif - expectation.fulfill() - self.waitForExpectations(timeout: 5, handler: nil) - ViewHosting.expel() - } - -} From 7db63eda0ace5ed15f7f0f5d66cd89f61078968a Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 26 Dec 2022 19:20:05 +0800 Subject: [PATCH 7/7] Update README --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4f40528..4fbeeba 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,14 @@ var body: some View { } ``` -Note: For indicator, you can custom your own as well. For example, iOS 14/watchOS 7 introduce the new `ProgressView`, which can replace our built-in `ProgressIndicator/ActivityIndicator` (where watchOS does not provide). +Note: For indicator, you can custom your own as well. For example, iOS 14/watchOS 7 introduce the new `ProgressView`, which can be easily used via: + +```swift +WebImage(url: url) +.indicator(.activity) +``` + +or you can just write like: ```swift WebImage(url: url) @@ -628,7 +635,7 @@ SDWebImageSwiftUI has Unit Test to increase code quality. For SwiftUI, there are However, since SwiftUI is State-Based and Attributed-Implemented layout system, there are open source projects who provide the solution: -+ [ViewInspector](https://github.com/nalexn/ViewInspector): Inspect View's runtime attribute value (like `.frame` modifier, `.image` value). We use this to test `AnimatedImage` and `WebImage`. It also allows the inspect to native UIView/NSView, which we use to test `ActivityIndicator` and `ProgressIndicator`. ++ [ViewInspector](https://github.com/nalexn/ViewInspector): Inspect View's runtime attribute value (like `.frame` modifier, `.image` value). We use this to test `AnimatedImage` and `WebImage`. It also allows the inspect to native UIView/NSView. To run the test: