Преглед на файлове

Support fade transition

onevcat преди 6 години
родител
ревизия
254b040179

+ 5 - 2
Demo/Demo/Kingfisher-Demo/ViewControllers/SwiftUIScreens/SwiftUIList.swift

@@ -38,7 +38,8 @@ struct SwiftUIList : View {
                 Spacer()
                 Spacer()
 
 
                 KFImage(
                 KFImage(
-                    URL(string: "https://github.com/onevcat/Flower-Data-Set/raw/master/rose/rose-\(i).jpg")!
+                    URL(string: "https://github.com/onevcat/Flower-Data-Set/raw/master/rose/rose-\(i).jpg")!,
+                    options: [.transition(.fade(0.4))]
                 )
                 )
                 .resizable()
                 .resizable()
                 .onSuccess { r in
                 .onSuccess { r in
@@ -57,7 +58,9 @@ struct SwiftUIList : View {
                             .frame(width: 80, height: 80)
                             .frame(width: 80, height: 80)
                             .padding(20)
                             .padding(20)
                         Text("Loading...").font(.largeTitle)
                         Text("Loading...").font(.largeTitle)
-                    }.foregroundColor(.gray)
+                    }
+                    .foregroundColor(.gray)
+                    .opacity(0.3)
 
 
                 }
                 }
                 .cancelOnDisappear(true)
                 .cancelOnDisappear(true)

+ 16 - 16
Demo/Kingfisher-Demo.xcodeproj/project.pbxproj

@@ -491,30 +491,30 @@
 				TargetAttributes = {
 				TargetAttributes = {
 					4B2944541C3D03880088C3E7 = {
 					4B2944541C3D03880088C3E7 = {
 						CreatedOnToolsVersion = 7.2;
 						CreatedOnToolsVersion = 7.2;
-						DevelopmentTeam = T499X543T7;
+						DevelopmentTeam = A4YJ9MRZ66;
 						LastSwiftMigration = 0900;
 						LastSwiftMigration = 0900;
 						ProvisioningStyle = Automatic;
 						ProvisioningStyle = Automatic;
 					};
 					};
 					D13F49C11BEDA53F00CE335D = {
 					D13F49C11BEDA53F00CE335D = {
 						CreatedOnToolsVersion = 7.1;
 						CreatedOnToolsVersion = 7.1;
-						DevelopmentTeam = T499X543T7;
+						DevelopmentTeam = A4YJ9MRZ66;
 						LastSwiftMigration = 0900;
 						LastSwiftMigration = 0900;
 						ProvisioningStyle = Automatic;
 						ProvisioningStyle = Automatic;
 					};
 					};
 					D1679A381C4E78B20020FD12 = {
 					D1679A381C4E78B20020FD12 = {
 						CreatedOnToolsVersion = 7.2;
 						CreatedOnToolsVersion = 7.2;
-						DevelopmentTeam = 683UGRW72Z;
+						DevelopmentTeam = A4YJ9MRZ66;
 						LastSwiftMigration = 0900;
 						LastSwiftMigration = 0900;
 						ProvisioningStyle = Automatic;
 						ProvisioningStyle = Automatic;
 					};
 					};
 					D1679A441C4E78B20020FD12 = {
 					D1679A441C4E78B20020FD12 = {
 						CreatedOnToolsVersion = 7.2;
 						CreatedOnToolsVersion = 7.2;
-						DevelopmentTeam = 683UGRW72Z;
+						DevelopmentTeam = A4YJ9MRZ66;
 						LastSwiftMigration = 0920;
 						LastSwiftMigration = 0920;
 					};
 					};
 					D1ED2D0A1AD2CFA600CFC3EB = {
 					D1ED2D0A1AD2CFA600CFC3EB = {
 						CreatedOnToolsVersion = 6.2;
 						CreatedOnToolsVersion = 6.2;
-						DevelopmentTeam = T499X543T7;
+						DevelopmentTeam = A4YJ9MRZ66;
 						LastSwiftMigration = 0900;
 						LastSwiftMigration = 0900;
 					};
 					};
 				};
 				};
@@ -716,7 +716,7 @@
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEBUG_INFORMATION_FORMAT = dwarf;
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = A4YJ9MRZ66;
 				ENABLE_HARDENED_RUNTIME = YES;
 				ENABLE_HARDENED_RUNTIME = YES;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-macOS-Demo/Info.plist";
 				INFOPLIST_FILE = "Demo/Kingfisher-macOS-Demo/Info.plist";
@@ -736,7 +736,7 @@
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = A4YJ9MRZ66;
 				ENABLE_HARDENED_RUNTIME = YES;
 				ENABLE_HARDENED_RUNTIME = YES;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-macOS-Demo/Info.plist";
 				INFOPLIST_FILE = "Demo/Kingfisher-macOS-Demo/Info.plist";
@@ -757,7 +757,7 @@
 				"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
 				"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEBUG_INFORMATION_FORMAT = dwarf;
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = A4YJ9MRZ66;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-tvOS-Demo/Info.plist";
 				INFOPLIST_FILE = "Demo/Kingfisher-tvOS-Demo/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -777,7 +777,7 @@
 				"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
 				"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = A4YJ9MRZ66;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-tvOS-Demo/Info.plist";
 				INFOPLIST_FILE = "Demo/Kingfisher-tvOS-Demo/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -795,7 +795,7 @@
 			buildSettings = {
 			buildSettings = {
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEBUG_INFORMATION_FORMAT = dwarf;
-				DEVELOPMENT_TEAM = 683UGRW72Z;
+				DEVELOPMENT_TEAM = A4YJ9MRZ66;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo Extension/Info.plist";
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo Extension/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
@@ -813,7 +813,7 @@
 			buildSettings = {
 			buildSettings = {
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				DEVELOPMENT_TEAM = 683UGRW72Z;
+				DEVELOPMENT_TEAM = A4YJ9MRZ66;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_NO_COMMON_BLOCKS = YES;
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo Extension/Info.plist";
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo Extension/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
@@ -835,7 +835,7 @@
 				"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
 				"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEBUG_INFORMATION_FORMAT = dwarf;
-				DEVELOPMENT_TEAM = 683UGRW72Z;
+				DEVELOPMENT_TEAM = A4YJ9MRZ66;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_NO_COMMON_BLOCKS = YES;
 				IBSC_MODULE = Kingfisher_watchOS_Demo_Extension;
 				IBSC_MODULE = Kingfisher_watchOS_Demo_Extension;
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo/Info.plist";
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo/Info.plist";
@@ -853,11 +853,11 @@
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				CODE_SIGN_IDENTITY = "iPhone Developer";
+				CODE_SIGN_IDENTITY = "Apple Development";
 				"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
 				"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				DEVELOPMENT_TEAM = 683UGRW72Z;
+				DEVELOPMENT_TEAM = A4YJ9MRZ66;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_NO_COMMON_BLOCKS = YES;
 				IBSC_MODULE = Kingfisher_watchOS_Demo_Extension;
 				IBSC_MODULE = Kingfisher_watchOS_Demo_Extension;
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo/Info.plist";
 				INFOPLIST_FILE = "Demo/Kingfisher-watchOS-Demo/Info.plist";
@@ -990,7 +990,7 @@
 			buildSettings = {
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_IDENTITY = "iPhone Developer";
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = A4YJ9MRZ66;
 				INFOPLIST_FILE = "Demo/Kingfisher-Demo/Info.plist";
 				INFOPLIST_FILE = "Demo/Kingfisher-Demo/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				PRODUCT_BUNDLE_IDENTIFIER = "com.onevcat.$(PRODUCT_NAME:rfc1034identifier)";
 				PRODUCT_BUNDLE_IDENTIFIER = "com.onevcat.$(PRODUCT_NAME:rfc1034identifier)";
@@ -1003,7 +1003,7 @@
 			buildSettings = {
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_IDENTITY = "iPhone Developer";
-				DEVELOPMENT_TEAM = T499X543T7;
+				DEVELOPMENT_TEAM = A4YJ9MRZ66;
 				INFOPLIST_FILE = "Demo/Kingfisher-Demo/Info.plist";
 				INFOPLIST_FILE = "Demo/Kingfisher-Demo/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				PRODUCT_BUNDLE_IDENTIFIER = "com.onevcat.$(PRODUCT_NAME:rfc1034identifier)";
 				PRODUCT_BUNDLE_IDENTIFIER = "com.onevcat.$(PRODUCT_NAME:rfc1034identifier)";

+ 19 - 1
Sources/SwiftUI/ImageBinder.swift

@@ -46,6 +46,22 @@ extension KFImage {
             didSet { didChange.send() }
             didSet { didChange.send() }
         }
         }
 
 
+        var fadeTransitionAnimation: Animation? {
+            #if os(iOS) || os(tvOS)
+            guard let options = (options.map { KingfisherParsedOptionsInfo($0) }) else {
+                return nil
+            }
+            switch options.transition {
+            case .fade(let duration):
+                return .basic(duration: duration, curve: .linear)
+            default:
+                return nil
+            }
+            #else
+            return nil
+            #endif
+        }
+
         init(source: Source, options: KingfisherOptionsInfo?) {
         init(source: Source, options: KingfisherOptionsInfo?) {
             self.source = source
             self.source = source
             self.options = options
             self.options = options
@@ -67,7 +83,9 @@ extension KFImage {
                         switch result {
                         switch result {
                         case .success(let value):
                         case .success(let value):
                             self.image = value.image
                             self.image = value.image
-                            self.onSuccessDelegate.call(value)
+                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+                                self.onSuccessDelegate.call(value)
+                            }
                         case .failure(let error):
                         case .failure(let error):
                             self.onFailureDelegate.call(error)
                             self.onFailureDelegate.call(error)
                         }
                         }

+ 32 - 18
Sources/SwiftUI/KFImage.swift

@@ -44,45 +44,59 @@ extension View {
     func eraseToAnyView() -> AnyView { .init(self) }
     func eraseToAnyView() -> AnyView { .init(self) }
 }
 }
 
 
+
+/// A Kingfisher compatible SwiftUI `View` to load an image from a `Source`.
+/// Declaring a `KFImage` in a `View`'s body to trigger loading from the given `Source`.
 @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
 @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
 public struct KFImage: View {
 public struct KFImage: View {
 
 
+    /// An image binder that manages loading and cancelling image related task.
     @ObjectBinding public private(set) var binder: ImageBinder
     @ObjectBinding public private(set) var binder: ImageBinder
 
 
+    // Acts as a placeholder when loading an image.
     var placeholder: AnyView?
     var placeholder: AnyView?
+
+    // Whether the download task should be cancelled when the view disappears.
     var cancelOnDisappear: Bool = false
     var cancelOnDisappear: Bool = false
-    
+
+    // Configurations should be performed on the image.
     var configs: [(Image) -> Image]
     var configs: [(Image) -> Image]
 
 
+    /// Creates a Kingfisher compatible image view to load image from the given `Source`.
+    /// - Parameter source: The image `Source` defining where to load the target image.
+    /// - Parameter options: The options should be applied when loading the image.
+    ///                      Some UIKit related options (such as `ImageTransition.flip`) are not supported.
     public init(_ source: Source, options: KingfisherOptionsInfo? = nil) {
     public init(_ source: Source, options: KingfisherOptionsInfo? = nil) {
         binder = ImageBinder(source: source, options: options)
         binder = ImageBinder(source: source, options: options)
         configs = []
         configs = []
     }
     }
 
 
+    /// Creates a Kingfisher compatible image view to load image from the given `Source`.
+    /// - Parameter url: The image URL from where to load the target image.
+    /// - Parameter options: The options should be applied when loading the image.
+    ///                      Some UIKit related options (such as `ImageTransition.flip`) are not supported.
     public init(_ url: URL, options: KingfisherOptionsInfo? = nil) {
     public init(_ url: URL, options: KingfisherOptionsInfo? = nil) {
         self.init(.network(url), options: options)
         self.init(.network(url), options: options)
     }
     }
 
 
+    /// Declares the content and behavior of this view.
     public var body: some View {
     public var body: some View {
-        if let image = binder.image {
-            return configs.reduce(Image(crossPlatformImage: image)) {
-                current, config in config(current)
-            }.eraseToAnyView()
-        } else {
-            let result = (placeholder ?? Image(crossPlatformImage: .init()).eraseToAnyView())
-                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
-            
-            let onAppear = result.onAppear { [unowned binder] in
-                binder.start()
-            }
-
-            if cancelOnDisappear {
-                return onAppear.onDisappear { [unowned binder] in
-                    binder.cancel()
-                }.eraseToAnyView()
+        ZStack {
+            if binder.image != nil {
+                configs
+                    .reduce(Image(crossPlatformImage: binder.image!)) {
+                        current, config in config(current)
+                    }
+                    .animation(binder.fadeTransitionAnimation)
             } else {
             } else {
-                return onAppear.eraseToAnyView()
+                (placeholder ?? Image(crossPlatformImage: .init()).eraseToAnyView())
+                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
+                    .onDisappear { [unowned binder = self.binder] in
+                        if self.cancelOnDisappear { binder.cancel() }
+                    }
             }
             }
+        }.onAppear { [unowned binder] in
+            binder.start()
         }
         }
     }
     }
 }
 }