Merge pull request #250 from SDWebImage/drop_ios_13

Drop iOS 13/macOS 10.15/tvOS 13/watchOS 6 support
This commit is contained in:
DreamPiggy 2023-09-02 21:37:44 +08:00 committed by GitHub
commit 5622af5387
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 117 additions and 929 deletions

View File

@ -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 = "<group>"; };
320CDC3922FADB45007CF858 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; };
322E0DF128D331A20003A55F /* WebImageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebImageTests.swift; sourceTree = "<group>"; };
322E0DF228D331A20003A55F /* Images.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Images.bundle; sourceTree = "<group>"; };
322E0DF328D331A20003A55F /* ImageManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageManagerTests.swift; sourceTree = "<group>"; };
@ -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;
};

View File

@ -54,10 +54,8 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/SDWebImageSwiftUIDemo-watchOS WatchKit App">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "32E529362348A0DD00EA46FF"
@ -65,7 +63,7 @@
BlueprintName = "SDWebImageSwiftUIDemo-watchOS WatchKit App"
ReferencedContainer = "container:SDWebImageSwiftUI.xcodeproj">
</BuildableReference>
</RemoteRunnable>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@ -73,10 +71,8 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/SDWebImageSwiftUIDemo-watchOS WatchKit App">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "32E529362348A0DD00EA46FF"
@ -84,16 +80,7 @@
BlueprintName = "SDWebImageSwiftUIDemo-watchOS WatchKit App"
ReferencedContainer = "container:SDWebImageSwiftUI.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "32E529362348A0DD00EA46FF"
BuildableName = "SDWebImageSwiftUIDemo-watchOS WatchKit App.app"
BlueprintName = "SDWebImageSwiftUIDemo-watchOS WatchKit App"
ReferencedContainer = "container:SDWebImageSwiftUI.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@ -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<EmptyView, EmptyView> {
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 = [

View File

@ -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()
}
}

View File

@ -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.

View File

@ -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
@ -166,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)
@ -514,7 +519,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 +533,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 +560,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 +573,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 +595,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"))
}
}
@ -630,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:

View File

@ -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 = {
@ -34,5 +34,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

View File

@ -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 = "<group>"; };
32B79C9428DB40430088C432 /* SwiftUICompatibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUICompatibility.swift; sourceTree = "<group>"; };
32B933E423659A1900BB7CAD /* Transition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transition.swift; sourceTree = "<group>"; };
32BC086F28D23D35002451BD /* StateObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateObject.swift; sourceTree = "<group>"; };
32BC087028D23D35002451BD /* OnChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnChange.swift; sourceTree = "<group>"; };
32BC087128D23D35002451BD /* Overlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Overlay.swift; sourceTree = "<group>"; };
32BC087228D23D35002451BD /* Backport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
32BD9C4623E03B08008D5F6A /* IndicatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndicatorTests.swift; sourceTree = "<group>"; };
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 = "<group>"; };
@ -185,17 +165,6 @@
path = Transition;
sourceTree = "<group>";
};
32BC086E28D23D35002451BD /* Backports */ = {
isa = PBXGroup;
children = (
32BC086F28D23D35002451BD /* StateObject.swift */,
32BC087028D23D35002451BD /* OnChange.swift */,
32BC087128D23D35002451BD /* Overlay.swift */,
32BC087228D23D35002451BD /* Backport.swift */,
);
path = Backports;
sourceTree = "<group>";
};
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;

View File

@ -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()
@ -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
@ -566,7 +551,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 +591,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 +644,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 +721,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 +753,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 +781,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 +822,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 +839,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 {

View File

@ -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<Title, Icon> { }
/// }
///
/// 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<S: StringProtocol>(_ 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<Value: Equatable>(of value: Value, perform action: (Value) -> Void) -> some View {
/// // `content` provides access to the extended type
/// content.modifier(OnChangeModifier(value, action))
/// }
/// }
///
public struct Backport<Wrapped> {
/// 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<Self> { .init(self) }
}
public extension NSObjectProtocol {
/// Wraps an `NSObject` that can be extended to provide backport functionality.
var backport: Backport<Self> { .init(self) }
}
public extension AnyTransition {
/// Wraps an `AnyTransition` that can be extended to provide backport functionality.
static var backport: Backport<AnyTransition>{
Backport(.identity)
}
}

View File

@ -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<Value: Equatable>(of value: Value, perform action: @escaping (Value) -> Void) -> some View {
content.modifier(ChangeModifier(value: value, action: action))
}
}
private struct ChangeModifier<Value: Equatable>: 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
}
}
}

View File

@ -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<Content: View>(alignment: Alignment = .center, @ViewBuilder _ content: () -> Content) -> some View {
self.content.overlay(content(), alignment: alignment)
}
}

View File

@ -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
/// <doc://com.apple.documentation/documentation/Combine/ObservableObject>
/// 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<Void, Never>()
var value: Wrapped? {
didSet {
cancellable = nil
cancellable = value?.objectWillChange
.sink { [subject] _ in subject.send() }
}
}
private var cancellable: AnyCancellable?
var objectWillChange: AnyPublisher<Void, Never> {
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<Wrapped>.Wrapper {
ObservedObject(wrappedValue: wrappedValue).projectedValue
}
/// Creates a new state object with an initial wrapped value.
///
/// You dont 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
}
}
}
}

View File

@ -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 {

View File

@ -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:

View File

@ -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?

View File

@ -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
@ -66,39 +66,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, *)
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 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.
@ -114,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

View File

@ -1,82 +0,0 @@
/*
* This file is part of the SDWebImage package.
* (c) DreamPiggy <lizhuoli1126@126.com>
*
* 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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.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<Bool>, 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<ActivityIndicator>) -> 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<ActivityIndicator>) {
isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
}
#endif
#if os(macOS)
public func makeNSView(context: NSViewRepresentableContext<ActivityIndicator>) -> 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<ActivityIndicator>) {
isAnimating ? nsView.startAnimation(nil) : nsView.stopAnimation(nil)
}
#endif
}
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ActivityIndicator {
public enum Style {
case medium
case large
}
}
#endif

View File

@ -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<T> where T : View {
var content: (Binding<Bool>, Binding<Double>) -> T
@ -25,7 +25,7 @@ public struct Indicator<T> 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<T> : ViewModifier where T : View {
/// The loading status
@ -45,53 +45,53 @@ public struct IndicatorViewModifier<T> : ViewModifier where T : View {
/// The indicator
public var indicator: Indicator<T>
public func body(content: Content) -> some View {
ZStack {
content
.backport
.overlay {
@ViewBuilder
private var overlay: some View {
if status.isLoading {
indicator.content($status.isLoading, $status.progress)
}
}
public func body(content: Content) -> some View {
ZStack {
content
overlay
}
}
}
#if os(macOS) || os(iOS) || os(tvOS)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension Indicator where T == ActivityIndicator {
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
extension Indicator where T == AnyView {
/// Activity Indicator
public static var activity: Indicator {
public static var activity: Indicator<T> {
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<S>(style: S) -> Indicator<T> where S: ProgressViewStyle {
Indicator { isAnimating, _ in
ActivityIndicator(isAnimating, style: style)
AnyView(ProgressView().progressViewStyle(style).opacity(isAnimating.wrappedValue ? 1 : 0))
}
}
}
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension Indicator where T == ProgressIndicator {
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
extension Indicator where T == AnyView {
/// Progress Indicator
public static var progress: Indicator {
public static var progress: Indicator<T> {
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<S>(style: S) -> Indicator<T> where S: ProgressViewStyle {
Indicator { isAnimating, progress in
ProgressIndicator(isAnimating, progress: progress, style: style)
AnyView(ProgressView(value: progress.wrappedValue).progressViewStyle(style).opacity(isAnimating.wrappedValue ? 1 : 0))
}
}
}
#endif

View File

@ -1,114 +0,0 @@
/*
* This file is part of the SDWebImage package.
* (c) DreamPiggy <lizhuoli1126@126.com>
*
* 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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.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<Bool>, progress: Binding<Double>, 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<ProgressIndicator>) -> 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<ProgressIndicator>) {
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<ProgressIndicator>) -> 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<ProgressIndicator>) {
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 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ProgressIndicator {
public enum Style {
case `default`
#if os(iOS)
case bar
#endif
}
}
#endif

View File

@ -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
}

View File

@ -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.

View File

@ -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

View File

@ -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)
}
@ -301,7 +299,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
@ -339,7 +337,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.
@ -371,7 +369,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
@ -410,7 +408,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
@ -427,7 +425,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.
@ -495,7 +493,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 {

View File

@ -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<Bool>(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<Bool>(wrappedValue: true)
let progress = Binding<Double>(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()
}
}