diff --git a/.travis.yml b/.travis.yml
index 659b32a9..d987e2e4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -33,4 +33,5 @@ script:
- pod install --project-directory=Example
- xcodebuild build -workspace Example/SDWebImageSwiftUI.xcworkspace -scheme SDWebImageSwiftUIDemo -sdk iphonesimulator -destination 'name=iPhone 8' ONLY_ACTIVE_ARCH=NO | xcpretty -c
- - swift build
\ No newline at end of file
+ - carthage update --platform iOS
+ - xcodebuild build -project SDWebImageSwiftUI.xcodeproj -scheme 'SDWebImageSwiftUI' -sdk iphonesimulator -configuration Debug | xcpretty -c
\ No newline at end of file
diff --git a/Cartfile.resolved b/Cartfile.resolved
index 98fdac58..b3575d5a 100644
--- a/Cartfile.resolved
+++ b/Cartfile.resolved
@@ -1 +1 @@
-github "SDWebImage/SDWebImage" "5.1.0"
+github "SDWebImage/SDWebImage" "5.2.3"
diff --git a/Example/Podfile.lock b/Example/Podfile.lock
index d9a8d3e8..1f1ba4f2 100644
--- a/Example/Podfile.lock
+++ b/Example/Podfile.lock
@@ -11,7 +11,7 @@ PODS:
- SDWebImage (5.2.3):
- SDWebImage/Core (= 5.2.3)
- SDWebImage/Core (5.2.3)
- - SDWebImageSwiftUI (0.3.2):
+ - SDWebImageSwiftUI (0.3.3):
- SDWebImage (~> 5.1)
- SDWebImageWebPCoder (0.2.5):
- libwebp (~> 1.0)
@@ -34,7 +34,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
libwebp: 057912d6d0abfb6357d8bb05c0ea470301f5d61e
SDWebImage: 46a7f73228f84ce80990c786e4372cf4db5875ce
- SDWebImageSwiftUI: a8a03ef596dde2e9668a76794f6c59d194289bb0
+ SDWebImageSwiftUI: 2284857313ca5085ab7b5310d372420d23c0817f
SDWebImageWebPCoder: 947093edd1349d820c40afbd9f42acb6cdecd987
PODFILE CHECKSUM: 3fb06a5173225e197f3a4bf2be7e5586a693257a
diff --git a/Example/SDWebImageSwiftUI.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/SDWebImageSwiftUI.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/Example/SDWebImageSwiftUI.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Example/SDWebImageSwiftUIDemo/ContentView.swift b/Example/SDWebImageSwiftUIDemo/ContentView.swift
index 41fc69ad..021ba49f 100644
--- a/Example/SDWebImageSwiftUIDemo/ContentView.swift
+++ b/Example/SDWebImageSwiftUIDemo/ContentView.swift
@@ -67,6 +67,9 @@ struct ContentView: View {
Button(action: { self.reloadCache() }) {
Text("Reload")
}
+ Button(action: { self.switchView() }) {
+ Text("Switch")
+ }
}
#endif
}
@@ -76,7 +79,6 @@ struct ContentView: View {
ForEach(imageURLs) { url in
NavigationLink(destination: DetailView(url: url, animated: self.animated)) {
HStack {
- #if os(iOS) || os(tvOS) || os(macOS)
if self.animated {
AnimatedImage(url: URL(string:url))
.resizable()
@@ -88,12 +90,6 @@ struct ContentView: View {
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
}
- #else
- WebImage(url: URL(string:url))
- .resizable()
- .scaledToFit()
- .frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
- #endif
Text((url as NSString).lastPathComponent)
}
}
diff --git a/Example/SDWebImageSwiftUIDemo/DetailView.swift b/Example/SDWebImageSwiftUIDemo/DetailView.swift
index 02b82f80..ebcd3cee 100644
--- a/Example/SDWebImageSwiftUIDemo/DetailView.swift
+++ b/Example/SDWebImageSwiftUIDemo/DetailView.swift
@@ -33,7 +33,7 @@ struct DetailView: View {
contentView()
}
#endif
- #if os(macOS)
+ #if os(macOS) || os(watchOS)
if animated {
contentView()
.contextMenu {
@@ -45,16 +45,12 @@ struct DetailView: View {
contentView()
}
#endif
- #if os(watchOS)
- contentView()
- #endif
Spacer()
}
}
func contentView() -> some View {
HStack {
- #if os(iOS) || os(tvOS) || os(macOS)
if animated {
AnimatedImage(url: URL(string:url), options: [.progressiveLoad], isAnimating: $isAnimating)
.onProgress(perform: { (receivedSize, expectedSize) in
@@ -79,18 +75,6 @@ struct DetailView: View {
.resizable()
.scaledToFit()
}
- #else
- WebImage(url: URL(string:url), options: [.progressiveLoad])
- .onProgress(perform: { (receivedSize, expectedSize) in
- if (expectedSize >= 0) {
- self.progress = CGFloat(receivedSize) / CGFloat(expectedSize)
- } else {
- self.progress = 1
- }
- })
- .resizable()
- .scaledToFit()
- #endif
}
}
}
diff --git a/Package.swift b/Package.swift
index 9c5c3885..1acb937b 100644
--- a/Package.swift
+++ b/Package.swift
@@ -24,7 +24,16 @@ let package = Package(
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "SDWebImageSwiftUI",
+ dependencies: ["SDWebImage", "SDWebImageSwiftUIObjC"],
+ path: "SDWebImageSwiftUI/Classes",
+ exclude: ["ObjC"]
+ ),
+ // This is implementation detail because SwiftPM does not support mixed Objective-C/Swift code, don't dependent this target
+ .target(
+ name: "SDWebImageSwiftUIObjC",
dependencies: ["SDWebImage"],
- path: "SDWebImageSwiftUI/Classes"),
+ path: "SDWebImageSwiftUI/Classes/ObjC",
+ publicHeadersPath: "."
+ )
]
)
diff --git a/SDWebImageSwiftUI.podspec b/SDWebImageSwiftUI.podspec
index 8ec2d2e2..c9b9c373 100644
--- a/SDWebImageSwiftUI.podspec
+++ b/SDWebImageSwiftUI.podspec
@@ -26,7 +26,7 @@ Which aims to provide a better support for SwiftUI users.
s.tvos.deployment_target = '13.0'
s.watchos.deployment_target = '6.0'
- s.source_files = 'SDWebImageSwiftUI/Classes/**/*'
+ s.source_files = 'SDWebImageSwiftUI/Classes/**/*', 'SDWebImageSwiftUI/Module/*.h'
s.frameworks = 'SwiftUI'
s.dependency 'SDWebImage', '~> 5.1'
diff --git a/SDWebImageSwiftUI.xcodeproj/project.pbxproj b/SDWebImageSwiftUI.xcodeproj/project.pbxproj
index 3ec68bb0..b539c52c 100644
--- a/SDWebImageSwiftUI.xcodeproj/project.pbxproj
+++ b/SDWebImageSwiftUI.xcodeproj/project.pbxproj
@@ -7,6 +7,14 @@
objects = {
/* Begin PBXBuildFile section */
+ 324F61C7235E07EC003973B8 /* SDAnimatedImageInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 324F61C5235E07EC003973B8 /* SDAnimatedImageInterface.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 324F61C8235E07EC003973B8 /* SDAnimatedImageInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 324F61C5235E07EC003973B8 /* SDAnimatedImageInterface.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 324F61C9235E07EC003973B8 /* SDAnimatedImageInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 324F61C5235E07EC003973B8 /* SDAnimatedImageInterface.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 324F61CA235E07EC003973B8 /* SDAnimatedImageInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 324F61C5235E07EC003973B8 /* SDAnimatedImageInterface.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 324F61CB235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */; };
+ 324F61CC235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */; };
+ 324F61CD235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */; };
+ 324F61CE235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */; };
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
@@ -89,6 +97,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 324F61C5235E07EC003973B8 /* SDAnimatedImageInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDAnimatedImageInterface.h; sourceTree = ""; };
+ 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDAnimatedImageInterface.m; sourceTree = ""; };
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewWrapper.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 = ""; };
@@ -142,6 +152,15 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 324F61C4235E07EC003973B8 /* ObjC */ = {
+ isa = PBXGroup;
+ children = (
+ 324F61C5235E07EC003973B8 /* SDAnimatedImageInterface.h */,
+ 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */,
+ );
+ path = ObjC;
+ sourceTree = "";
+ };
32C43DC222FD540D00BE87F5 = {
isa = PBXGroup;
children = (
@@ -175,6 +194,7 @@
32C43DDB22FD54C600BE87F5 /* Classes */ = {
isa = PBXGroup;
children = (
+ 324F61C4235E07EC003973B8 /* ObjC */,
32C43DDC22FD54C600BE87F5 /* ImageManager.swift */,
32C43DDE22FD54C600BE87F5 /* WebImage.swift */,
32C43DDF22FD54C600BE87F5 /* AnimatedImage.swift */,
@@ -202,6 +222,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
+ 324F61C7235E07EC003973B8 /* SDAnimatedImageInterface.h in Headers */,
32C43DE622FD54CD00BE87F5 /* SDWebImageSwiftUI.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -210,6 +231,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
+ 324F61C8235E07EC003973B8 /* SDAnimatedImageInterface.h in Headers */,
32C43E2222FD583A00BE87F5 /* SDWebImageSwiftUI.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -218,6 +240,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
+ 324F61C9235E07EC003973B8 /* SDAnimatedImageInterface.h in Headers */,
32C43E2322FD583B00BE87F5 /* SDWebImageSwiftUI.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -226,6 +249,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
+ 324F61CA235E07EC003973B8 /* SDAnimatedImageInterface.h in Headers */,
32C43E2422FD583C00BE87F5 /* SDWebImageSwiftUI.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -320,15 +344,19 @@
TargetAttributes = {
32C43DCB22FD540D00BE87F5 = {
CreatedOnToolsVersion = 11.0;
+ LastSwiftMigration = 1100;
};
32C43DF322FD57FD00BE87F5 = {
CreatedOnToolsVersion = 11.0;
+ LastSwiftMigration = 1100;
};
32C43E0022FD581400BE87F5 = {
CreatedOnToolsVersion = 11.0;
+ LastSwiftMigration = 1100;
};
32C43E0D22FD581C00BE87F5 = {
CreatedOnToolsVersion = 11.0;
+ LastSwiftMigration = 1100;
};
};
};
@@ -394,6 +422,7 @@
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32C43E1622FD583700BE87F5 /* ImageManager.swift in Sources */,
32C43E1822FD583700BE87F5 /* AnimatedImage.swift in Sources */,
+ 324F61CB235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -406,6 +435,7 @@
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32C43E1922FD583700BE87F5 /* ImageManager.swift in Sources */,
32C43E1B22FD583700BE87F5 /* AnimatedImage.swift in Sources */,
+ 324F61CC235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -418,6 +448,7 @@
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32C43E1C22FD583800BE87F5 /* ImageManager.swift in Sources */,
32C43E1E22FD583800BE87F5 /* AnimatedImage.swift in Sources */,
+ 324F61CD235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -430,6 +461,7 @@
326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
32C43E1F22FD583800BE87F5 /* ImageManager.swift in Sources */,
32C43E2122FD583800BE87F5 /* AnimatedImage.swift in Sources */,
+ 324F61CE235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -565,6 +597,7 @@
32C43DD522FD540D00BE87F5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -585,6 +618,7 @@
PRODUCT_NAME = SDWebImageSwiftUI;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@@ -593,6 +627,7 @@
32C43DD622FD540D00BE87F5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -621,6 +656,7 @@
32C43DFA22FD57FD00BE87F5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
@@ -643,6 +679,7 @@
PRODUCT_NAME = SDWebImageSwiftUI;
SDKROOT = macosx;
SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
@@ -650,6 +687,7 @@
32C43DFB22FD57FD00BE87F5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
@@ -679,6 +717,7 @@
32C43E0722FD581400BE87F5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -699,6 +738,7 @@
PRODUCT_NAME = SDWebImageSwiftUI;
SDKROOT = appletvos;
SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 3;
};
@@ -707,6 +747,7 @@
32C43E0822FD581400BE87F5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -736,6 +777,7 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -756,6 +798,7 @@
PRODUCT_NAME = SDWebImageSwiftUI;
SDKROOT = watchos;
SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
};
@@ -765,6 +808,7 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift
index a7675d72..da63db90 100644
--- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift
+++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift
@@ -8,13 +8,13 @@
import SwiftUI
import SDWebImage
-
-#if !os(watchOS)
+#if canImport(SDWebImageSwiftUIObjC)
+import SDWebImageSwiftUIObjC
+#endif
// Data Binding Object
final class AnimatedImageModel : ObservableObject {
@Published var image: PlatformImage?
- @Published var url: URL?
@Published var successBlock: ((PlatformImage, SDImageCacheType) -> Void)?
@Published var failureBlock: ((Error) -> Void)?
@Published var progressBlock: ((Int, Int) -> Void)?
@@ -38,12 +38,23 @@ final class AnimatedImageConfiguration: ObservableObject {
@Published var customLoopCount: Int?
}
+// Convenient
+#if os(watchOS)
+public typealias AnimatedImageViewWrapper = SDAnimatedImageInterface
+extension SDAnimatedImageInterface {
+ var wrapped: SDAnimatedImageInterface {
+ return self
+ }
+}
+#endif
+
// View
public struct AnimatedImage : PlatformViewRepresentable {
@ObservedObject var imageModel = AnimatedImageModel()
@ObservedObject var imageLayout = AnimatedImageLayout()
@ObservedObject var imageConfiguration = AnimatedImageConfiguration()
+ var url: URL?
var placeholder: PlatformImage?
var webOptions: SDWebImageOptions = []
var webContext: [SDWebImageContextOption : Any]? = nil
@@ -72,7 +83,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
self.placeholder = placeholder
self.webOptions = options
self.webContext = context
- self.imageModel.url = url
+ self.url = url
}
/// Create an animated image with name and bundle.
@@ -90,7 +101,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
/// - Parameter isAnimating: The binding for animation control
public init(name: String, bundle: Bundle? = nil, isAnimating: Binding) {
self._isAnimating = isAnimating
- #if os(macOS)
+ #if os(macOS) || os(watchOS)
let image = SDAnimatedImage(named: name, in: bundle)
#else
let image = SDAnimatedImage(named: name, in: bundle, compatibleWith: nil)
@@ -117,8 +128,10 @@ public struct AnimatedImage : PlatformViewRepresentable {
#if os(macOS)
public typealias NSViewType = AnimatedImageViewWrapper
- #else
+ #elseif os(iOS) || os(tvOS)
public typealias UIViewType = AnimatedImageViewWrapper
+ #elseif os(watchOS)
+ public typealias WKInterfaceObjectType = AnimatedImageViewWrapper
#endif
#if os(macOS)
@@ -133,7 +146,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
public static func dismantleNSView(_ nsView: AnimatedImageViewWrapper, coordinator: ()) {
dismantleView(nsView, coordinator: coordinator)
}
- #else
+ #elseif os(iOS) || os(tvOS)
public func makeUIView(context: UIViewRepresentableContext) -> AnimatedImageViewWrapper {
makeView(context: context)
}
@@ -145,6 +158,18 @@ public struct AnimatedImage : PlatformViewRepresentable {
public static func dismantleUIView(_ uiView: AnimatedImageViewWrapper, coordinator: ()) {
dismantleView(uiView, coordinator: coordinator)
}
+ #elseif os(watchOS)
+ public func makeWKInterfaceObject(context: WKInterfaceObjectRepresentableContext) -> AnimatedImageViewWrapper {
+ makeView(context: context)
+ }
+
+ public func updateWKInterfaceObject(_ wkInterfaceObject: AnimatedImageViewWrapper, context: WKInterfaceObjectRepresentableContext) {
+ updateView(wkInterfaceObject, context: context)
+ }
+
+ public static func dismantleWKInterfaceObject(_ wkInterfaceObject: AnimatedImageViewWrapper, coordinator: ()) {
+ dismantleView(wkInterfaceObject, coordinator: coordinator)
+ }
#endif
func makeView(context: PlatformViewRepresentableContext) -> AnimatedImageViewWrapper {
@@ -152,15 +177,23 @@ public struct AnimatedImage : PlatformViewRepresentable {
}
func updateView(_ view: AnimatedImageViewWrapper, context: PlatformViewRepresentableContext) {
- view.wrapped.image = imageModel.image
- if let url = imageModel.url {
- view.wrapped.sd_setImage(with: url, placeholderImage: placeholder, options: webOptions, context: webContext, progress: { (receivedSize, expectedSize, _) in
- self.imageModel.progressBlock?(receivedSize, expectedSize)
- }) { (image, error, cacheType, _) in
- if let image = image {
- self.imageModel.successBlock?(image, cacheType)
- } else {
- self.imageModel.failureBlock?(error ?? NSError())
+ if let image = imageModel.image {
+ #if os(watchOS)
+ view.wrapped.setImage(image)
+ #else
+ view.wrapped.image = image
+ #endif
+ } else {
+ if let url = url {
+ view.wrapped.sd_setImage(with: url, placeholderImage: placeholder, options: webOptions, context: webContext, progress: { (receivedSize, expectedSize, _) in
+ self.imageModel.progressBlock?(receivedSize, expectedSize)
+ }) { (image, error, cacheType, _) in
+ self.imageModel.image = image
+ if let image = image {
+ self.imageModel.successBlock?(image, cacheType)
+ } else {
+ self.imageModel.failureBlock?(error ?? NSError())
+ }
}
}
}
@@ -169,7 +202,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
if self.isAnimating != view.wrapped.animates {
view.wrapped.animates = self.isAnimating
}
- #else
+ #elseif os(iOS) || os(tvOS)
if self.isAnimating != view.wrapped.isAnimating {
if self.isAnimating {
view.wrapped.startAnimating()
@@ -177,6 +210,12 @@ public struct AnimatedImage : PlatformViewRepresentable {
view.wrapped.stopAnimating()
}
}
+ #elseif os(watchOS)
+ if self.isAnimating {
+ view.wrapped.startAnimating()
+ } else {
+ view.wrapped.stopAnimating()
+ }
#endif
configureView(view, context: context)
@@ -202,19 +241,24 @@ public struct AnimatedImage : PlatformViewRepresentable {
case .fit:
#if os(macOS)
view.wrapped.imageScaling = .scaleProportionallyUpOrDown
- #else
+ #elseif os(iOS) || os(tvOS)
view.wrapped.contentMode = .scaleAspectFit
+ #elseif os(watchOS)
+ view.wrapped.setContentMode(.aspectFit)
#endif
case .fill:
#if os(macOS)
view.wrapped.imageScaling = .scaleAxesIndependently
- #else
+ #elseif os(iOS) || os(tvOS)
view.wrapped.contentMode = .scaleToFill
+ #elseif os(watchOS)
+ view.wrapped.setContentMode(.fill)
#endif
}
// Animated Image does not support resizing mode and rendering mode
- if let image = view.wrapped.image, !image.sd_isAnimated, !image.conforms(to: SDAnimatedImageProtocol.self) {
+ if let image = imageModel.image, !image.sd_isAnimated, !image.conforms(to: SDAnimatedImageProtocol.self) {
+ var image = image
// ResizingMode
if let resizingMode = imageLayout.resizingMode {
#if os(macOS)
@@ -228,14 +272,24 @@ public struct AnimatedImage : PlatformViewRepresentable {
view.wrapped.image?.resizingMode = .stretch
view.wrapped.image?.capInsets = capInsets
#else
- view.wrapped.image = view.wrapped.image?.resizableImage(withCapInsets: capInsets, resizingMode: .stretch)
+ image = image.resizableImage(withCapInsets: capInsets, resizingMode: .stretch)
+ #if os(iOS) || os(tvOS)
+ view.wrapped.image = image
+ #elseif os(watchOS)
+ view.wrapped.setImage(image)
+ #endif
#endif
case .tile:
#if os(macOS)
view.wrapped.image?.resizingMode = .tile
view.wrapped.image?.capInsets = capInsets
#else
- view.wrapped.image = view.wrapped.image?.resizableImage(withCapInsets: capInsets, resizingMode: .tile)
+ image = image.resizableImage(withCapInsets: capInsets, resizingMode: .tile)
+ #if os(iOS) || os(tvOS)
+ view.wrapped.image = image
+ #elseif os(watchOS)
+ view.wrapped.setImage(image)
+ #endif
#endif
@unknown default:
// Future cases, not implements
@@ -250,13 +304,23 @@ public struct AnimatedImage : PlatformViewRepresentable {
#if os(macOS)
view.wrapped.image?.isTemplate = true
#else
- view.wrapped.image = view.wrapped.image?.withRenderingMode(.alwaysTemplate)
+ image = image.withRenderingMode(.alwaysTemplate)
+ #if os(iOS) || os(tvOS)
+ view.wrapped.image = image
+ #elseif os(watchOS)
+ view.wrapped.setImage(image)
+ #endif
#endif
case .original:
#if os(macOS)
view.wrapped.image?.isTemplate = false
#else
- view.wrapped.image = view.wrapped.image?.withRenderingMode(.alwaysOriginal)
+ image = image.withRenderingMode(.alwaysOriginal)
+ #if os(iOS) || os(tvOS)
+ view.wrapped.image = image
+ #elseif os(watchOS)
+ view.wrapped.setImage(image)
+ #endif
#endif
@unknown default:
// Future cases, not implements
@@ -265,6 +329,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
}
}
+ #if os(macOS) || os(iOS) || os(tvOS)
// Interpolation
if let interpolation = imageLayout.interpolation {
switch interpolation {
@@ -295,9 +360,11 @@ public struct AnimatedImage : PlatformViewRepresentable {
view.setNeedsLayout()
view.setNeedsDisplay()
#endif
+ #endif
}
func configureView(_ view: AnimatedImageViewWrapper, context: PlatformViewRepresentableContext) {
+ #if os(macOS) || os(iOS) || os(tvOS)
// IncrementalLoad
if let incrementalLoad = imageConfiguration.incrementalLoad {
view.wrapped.shouldIncrementalLoad = incrementalLoad
@@ -319,6 +386,14 @@ public struct AnimatedImage : PlatformViewRepresentable {
// disable custom loop count
view.wrapped.shouldCustomLoopCount = false
}
+ #elseif os(watchOS)
+ if let customLoopCount = imageConfiguration.customLoopCount {
+ view.wrapped.setAnimationRepeatCount(customLoopCount as NSNumber)
+ } else {
+ // disable custom loop count
+ view.wrapped.setAnimationRepeatCount(nil)
+ }
+ #endif
}
}
@@ -417,9 +492,11 @@ extension AnimatedImage {
}
/// Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is nil.
- // `0` or nil means automatically adjust by calculating current memory usage.
- // `1` means without any buffer cache, each of frames will be decoded and then be freed after rendering. (Lowest Memory and Highest CPU)
- // `UInt.max` means cache all the buffer. (Lowest CPU and Highest Memory)
+ ///
+ /// `0` or nil means automatically adjust by calculating current memory usage.
+ /// `1` means without any buffer cache, each of frames will be decoded and then be freed after rendering. (Lowest Memory and Highest CPU)
+ /// `UInt.max` means cache all the buffer. (Lowest CPU and Highest Memory)
+ /// - Warning: watchOS does not implementes.
/// - Parameter bufferSize: The max buffer size
public func maxBufferSize(_ bufferSize: UInt?) -> AnimatedImage {
imageConfiguration.maxBufferSize = bufferSize
@@ -429,6 +506,7 @@ extension AnimatedImage {
/// Whehter or not to enable incremental image load for animated image. See `SDAnimatedImageView` for detailed explanation for this.
/// - Note: If you are confused about this description, open Chrome browser to view some large GIF images with low network speed to see the animation behavior.
/// Default is true. Set to false to only render the static poster for incremental animated image.
+ /// - Warning: watchOS does not implementes.
/// - Parameter incrementalLoad: Whether or not to incremental load
public func incrementalLoad(_ incrementalLoad: Bool) -> AnimatedImage {
imageConfiguration.incrementalLoad = incrementalLoad
@@ -479,5 +557,3 @@ struct AnimatedImage_Previews : PreviewProvider {
}
}
#endif
-
-#endif
diff --git a/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.h b/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.h
new file mode 100644
index 00000000..08e0b7a9
--- /dev/null
+++ b/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.h
@@ -0,0 +1,24 @@
+/*
+* 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 SDWebImage;
+
+#if SD_WATCH
+NS_ASSUME_NONNULL_BEGIN
+
+/// Do not use this class directly in WatchKit or Storyboard. This class is implementation detail and will be removed in the future.
+@interface SDAnimatedImageInterface : WKInterfaceImage
+
+- (instancetype)init WK_AVAILABLE_WATCHOS_ONLY(6.0);
+- (void)setContentMode:(SDImageScaleMode)contentMode;
+- (void)setAnimationRepeatCount:(nullable NSNumber *)repeatCount;
+
+@end
+
+NS_ASSUME_NONNULL_END
+#endif
diff --git a/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.m b/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.m
new file mode 100644
index 00000000..2fabf582
--- /dev/null
+++ b/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.m
@@ -0,0 +1,272 @@
+/*
+* 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 "SDAnimatedImageInterface.h"
+#if SD_WATCH
+// ImageIO.modulemap does not contains this public header
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wincomplete-umbrella"
+#import
+#pragma clang diagnostic pop
+
+#pragma mark - SPI
+
+@protocol CALayerProtocol
+@property (nullable, strong) id contents;
+@property CGFloat contentsScale;
+@end
+
+@protocol UIViewProtocol
+@property (nonatomic, strong, readonly) id layer;
+@property (nonatomic, assign) SDImageScaleMode contentMode;
+@end
+
+@interface WKInterfaceObject ()
+
+// This is needed for dynamic created WKInterfaceObject, like `WKInterfaceMap`
+- (instancetype)_initForDynamicCreationWithInterfaceProperty:(NSString *)property;
+// This is remote UIView
+@property (nonatomic, strong, readonly) id _interfaceView;
+
+@end
+
+@interface SDAnimatedImageStatus : NSObject
+
+@property (nonatomic, assign) BOOL shouldAnimate;
+@property (nonatomic, assign) CGImageAnimationStatus animationStatus;
+
+@end
+
+@implementation SDAnimatedImageStatus
+
+@end
+
+@interface SDAnimatedImageInterface () {
+ UIImage *_image;
+}
+
+@property (nonatomic, strong, readwrite) UIImage *currentFrame;
+@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
+@property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
+@property (nonatomic, assign) NSUInteger totalFrameCount;
+@property (nonatomic, assign) NSUInteger totalLoopCount;
+@property (nonatomic, strong) UIImage *animatedImage;
+@property (nonatomic, assign) CGFloat animatedImageScale;
+@property (nonatomic, strong) SDAnimatedImageStatus *currentStatus;
+@property (nonatomic, strong) NSNumber *animationRepeatCount;
+
+@end
+
+@implementation SDAnimatedImageInterface
+
+- (instancetype)init {
+ Class cls = [self class];
+ NSString *UUID = [NSUUID UUID].UUIDString;
+ NSString *property = [NSString stringWithFormat:@"%@_%@", cls, UUID];
+ self = [self _initForDynamicCreationWithInterfaceProperty:property];
+ return self;
+}
+
+- (NSDictionary *)interfaceDescriptionForDynamicCreation {
+ // This is called by WatchKit
+ return @{
+ @"type" : @"image",
+ @"property" : self.interfaceProperty,
+ @"image" : [self.class sharedEmptyImage]
+ };
+}
+
++ (UIImage *)sharedEmptyImage {
+ // This is used for placeholder on `WKInterfaceImage`
+ // Do not using `[UIImage new]` because WatchKit will ignore it
+ static dispatch_once_t onceToken;
+ static UIImage *image;
+ dispatch_once(&onceToken, ^{
+ UIColor *color = UIColor.clearColor;
+ CGRect rect = CGRectMake(0, 0, 1, 1);
+ UIGraphicsBeginImageContext(rect.size);
+ CGContextRef context = UIGraphicsGetCurrentContext();
+ CGContextSetFillColorWithColor(context, [color CGColor]);
+ CGContextFillRect(context, rect);
+ image = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+ });
+ return image;
+}
+
+- (void)setImage:(UIImage *)image {
+ if (_image == image) {
+ return;
+ }
+ _image = image;
+
+ // Reset all value
+ [self resetAnimatedImage];
+
+ [super setImage:image];
+ if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) {
+ UIImage *animatedImage = (UIImage *)image;
+ NSUInteger animatedImageFrameCount = animatedImage.animatedImageFrameCount;
+ // Check the frame count
+ if (animatedImageFrameCount <= 1) {
+ return;
+ }
+ self.animatedImage = animatedImage;
+ self.totalFrameCount = animatedImageFrameCount;
+ // Get the current frame and loop count.
+ self.totalLoopCount = self.animatedImage.animatedImageLoopCount;
+ // Get the scale
+ self.animatedImageScale = image.scale;
+
+ NSData *animatedImageData = animatedImage.animatedImageData;
+ SDImageFormat format = [NSData sd_imageFormatForImageData:animatedImageData];
+ if (format == SDImageFormatGIF || format == SDImageFormatPNG) {
+ [self startBuiltInAnimationWithImage:animatedImage];
+ }
+
+ // Update should animate
+ [self updateShouldAnimate];
+ }
+}
+
+- (void)startBuiltInAnimationWithImage:(UIImage *)animatedImage {
+ NSData *animatedImageData = animatedImage.animatedImageData;
+ NSUInteger maxLoopCount;
+ if (self.animationRepeatCount != nil) {
+ maxLoopCount = self.animationRepeatCount.unsignedIntegerValue;
+ } else {
+ maxLoopCount = animatedImage.animatedImageLoopCount;
+ }
+ if (maxLoopCount == 0) {
+ // The documentation says `kCFNumberPositiveInfinity may be used`, but it actually treat as 1 loop count
+ // 0 was treated as 1 loop count as well, not the same as Image/IO or UIKit
+ maxLoopCount = ((__bridge NSNumber *)kCFNumberPositiveInfinity).unsignedIntegerValue - 1;
+ }
+ NSDictionary *options = @{(__bridge NSString *)kCGImageAnimationLoopCount : @(maxLoopCount)};
+ SDAnimatedImageStatus *status = [SDAnimatedImageStatus new];
+ status.shouldAnimate = YES;
+ __weak typeof(self) wself = self;
+ status.animationStatus = CGAnimateImageDataWithBlock((__bridge CFDataRef)animatedImageData, (__bridge CFDictionaryRef)options, ^(size_t index, CGImageRef _Nonnull imageRef, bool * _Nonnull stop) {
+ __strong typeof(wself) self = wself;
+ if (!self) {
+ *stop = YES;
+ return;
+ }
+ if (!status.shouldAnimate) {
+ *stop = YES;
+ return;
+ }
+ // The CGImageRef provided by this API is GET only, should not call CGImageRelease
+ self.currentFrame = [[UIImage alloc] initWithCGImage:imageRef scale:self.animatedImageScale orientation:UIImageOrientationUp];
+ self.currentFrameIndex = index;
+ // Render the frame
+ [self displayLayer];
+ });
+
+ self.currentStatus = status;
+}
+
+- (void)displayLayer {
+ if (self.currentFrame) {
+ id layer = [self _interfaceView].layer;
+ layer.contentsScale = self.animatedImageScale;
+ layer.contents = (__bridge id)self.currentFrame.CGImage;
+ }
+}
+
+- (void)resetAnimatedImage
+{
+ self.animatedImage = nil;
+ self.totalFrameCount = 0;
+ self.totalLoopCount = 0;
+ // reset current state
+ self.currentStatus.shouldAnimate = NO;
+ self.currentStatus = nil;
+ [self resetCurrentFrameIndex];
+ self.animatedImageScale = 1;
+}
+
+- (void)resetCurrentFrameIndex
+{
+ self.currentFrame = nil;
+ self.currentFrameIndex = 0;
+ self.currentLoopCount = 0;
+}
+
+- (void)updateShouldAnimate
+{
+ self.currentStatus.shouldAnimate = self.animatedImage && self.totalFrameCount > 1;
+}
+
+- (void)startAnimating {
+ if (self.animatedImage) {
+ self.currentStatus.shouldAnimate = YES;
+ } else if (_image.images.count > 0) {
+ [super startAnimating];
+ }
+}
+
+- (void)startAnimatingWithImagesInRange:(NSRange)imageRange duration:(NSTimeInterval)duration repeatCount:(NSInteger)repeatCount {
+ if (self.animatedImage) {
+ self.currentStatus.shouldAnimate = YES;
+ } else if (_image.images.count > 0) {
+ [super startAnimatingWithImagesInRange:imageRange duration:duration repeatCount:repeatCount];
+ }
+}
+
+- (void)stopAnimating {
+ if (self.animatedImage) {
+ self.currentStatus.shouldAnimate = NO;
+ } else if (_image.images.count > 0) {
+ [super stopAnimating];
+ }
+}
+
+- (void)setContentMode:(SDImageScaleMode)contentMode {
+ [self _interfaceView].contentMode = contentMode;
+}
+
+@end
+
+#pragma mark - Web Cache
+
+@interface SDAnimatedImageInterface (WebCache)
+
+@end
+
+@implementation SDAnimatedImageInterface (WebCache)
+
+- (void)sd_setImageWithURL:(nullable NSURL *)url
+ placeholderImage:(nullable UIImage *)placeholder
+ options:(SDWebImageOptions)options
+ context:(nullable SDWebImageContext *)context
+ progress:(nullable SDImageLoaderProgressBlock)progressBlock
+ completed:(nullable SDExternalCompletionBlock)completedBlock {
+ Class animatedImageClass = [SDAnimatedImage class];
+ SDWebImageMutableContext *mutableContext;
+ if (context) {
+ mutableContext = [context mutableCopy];
+ } else {
+ mutableContext = [NSMutableDictionary dictionary];
+ }
+ mutableContext[SDWebImageContextAnimatedImageClass] = animatedImageClass;
+ [self sd_internalSetImageWithURL:url
+ placeholderImage:placeholder
+ options:options
+ context:mutableContext
+ setImageBlock:nil
+ progress:progressBlock
+ completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
+ if (completedBlock) {
+ completedBlock(image, error, cacheType, imageURL);
+ }
+ }];
+}
+
+@end
+#endif
diff --git a/SDWebImageSwiftUI/Module/SDWebImageSwiftUI.h b/SDWebImageSwiftUI/Module/SDWebImageSwiftUI.h
index fa5a9c45..958fe3ec 100644
--- a/SDWebImageSwiftUI/Module/SDWebImageSwiftUI.h
+++ b/SDWebImageSwiftUI/Module/SDWebImageSwiftUI.h
@@ -7,6 +7,7 @@
*/
#import
+#import
//! Project version number for SDWebImageSwiftUI.
FOUNDATION_EXPORT double SDWebImageSwiftUIVersionNumber;