From a0a4257516852a5a94a73b7732484fa2e6fe94bf Mon Sep 17 00:00:00 2001 From: Reda Lemeden Date: Fri, 17 Jan 2025 19:36:09 +0100 Subject: [PATCH 1/9] Add Swift 6 support --- Demo/Demo.xcodeproj/project.pbxproj | 8 +++--- Package@swift-6.0.swift | 26 +++++++++++++++++++ Sources/Gifu/Helpers/ImageSourceHelpers.swift | 2 +- 3 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 Package@swift-6.0.swift diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index b6f71af..a8f9840 100755 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -347,7 +347,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 13.2; + TVOS_DEPLOYMENT_TARGET = 18.0; }; name = Debug; }; @@ -376,7 +376,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 13.2; + TVOS_DEPLOYMENT_TARGET = 18.0; }; name = Release; }; @@ -493,7 +493,7 @@ FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/Demo-iOS/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Gifu; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = net.4rays.Gifu.Demo; @@ -513,7 +513,7 @@ FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/Demo-iOS/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Gifu; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = net.4rays.Gifu.Demo; diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift new file mode 100644 index 0000000..44d947c --- /dev/null +++ b/Package@swift-6.0.swift @@ -0,0 +1,26 @@ +// swift-tools-version:6.0 + +import PackageDescription + +let package = Package( + name: "Gifu", + platforms: [ + .iOS(.v16), + .tvOS(.v16), + .visionOS(.v1), + ], + products: [ + .library( + name: "Gifu", + targets: ["Gifu"] + ) + ], + dependencies: [], + targets: [ + .target( + name: "Gifu", + dependencies: [] + ) + ], + swiftLanguageModes: [.v6] +) diff --git a/Sources/Gifu/Helpers/ImageSourceHelpers.swift b/Sources/Gifu/Helpers/ImageSourceHelpers.swift index 6c9b0e1..16a1dfa 100755 --- a/Sources/Gifu/Helpers/ImageSourceHelpers.swift +++ b/Sources/Gifu/Helpers/ImageSourceHelpers.swift @@ -16,7 +16,7 @@ private let defaultFrameDuration: Double = 1 / defaultFrameRate /// Threshold used in `capDuration` for a FrameDuration private let capDurationThreshold: Double = 0.02 - Double.ulpOfOne -/// Frameduration used, if a frame-duration is below `capDurationThreshold` +/// Frame duration used, if a frame-duration is below `capDurationThreshold` private let minFrameDuration: Double = 0.1 /// Returns the duration of a frame at a specific index using an image source (an `CGImageSource` instance). From 40cbf21ce93e3e4f7b16dfd56d394b2f637c6af2 Mon Sep 17 00:00:00 2001 From: Reda Lemeden Date: Fri, 17 Jan 2025 19:36:24 +0100 Subject: [PATCH 2/9] Update schemes --- Demo/Demo.xcodeproj/project.pbxproj | 37 ++++++++++++++----- .../xcshareddata/xcschemes/Demo-iOS.xcscheme | 2 +- .../xcshareddata/xcschemes/Gifu.xcscheme | 2 +- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index a8f9840..968c87a 100755 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -217,8 +217,9 @@ 9D98822F19BC69CA00B790C6 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1120; - LastUpgradeCheck = 1200; + LastUpgradeCheck = 1620; ORGANIZATIONNAME = "Kaishin & Co"; TargetAttributes = { 0007E16523809E9C000FED9F = { @@ -338,7 +339,10 @@ GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(SRCROOT)/Demo-tvOS/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Gifu; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.redalemeden.Gifu-tvOSDemo"; @@ -368,12 +372,16 @@ GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(SRCROOT)/Demo-tvOS/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Gifu; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.redalemeden.Gifu-tvOSDemo"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 18.0; @@ -413,6 +421,7 @@ COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -428,7 +437,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -469,6 +478,7 @@ COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -477,7 +487,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; @@ -494,7 +504,10 @@ INFOPLIST_FILE = "$(SRCROOT)/Demo-iOS/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Gifu; IPHONEOS_DEPLOYMENT_TARGET = 18.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = net.4rays.Gifu.Demo; PRODUCT_NAME = "Gifu-iOSDemo"; @@ -514,12 +527,16 @@ INFOPLIST_FILE = "$(SRCROOT)/Demo-iOS/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Gifu; IPHONEOS_DEPLOYMENT_TARGET = 18.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = net.4rays.Gifu.Demo; PRODUCT_NAME = "Gifu-iOSDemo"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo-iOS.xcscheme b/Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo-iOS.xcscheme index 9749901..33f0f25 100644 --- a/Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo-iOS.xcscheme +++ b/Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo-iOS.xcscheme @@ -1,6 +1,6 @@ Date: Sat, 18 Jan 2025 17:10:19 +0100 Subject: [PATCH 3/9] Support Swift 6 language mode - Bump minimum iOS version to 14.0 --- Gifu.xcodeproj/project.pbxproj | 14 +- Package.swift | 4 +- Package@swift-6.0.swift | 7 + Sources/Gifu/Classes/Animator.swift | 57 +++++--- Sources/Gifu/Classes/FrameStore.swift | 9 +- Sources/Gifu/Classes/GIFAnimatable.swift | 136 ++++++++++++------ Sources/Gifu/Classes/GIFImageView.swift | 2 +- Sources/Gifu/Helpers/ImageSourceHelpers.swift | 4 +- 8 files changed, 155 insertions(+), 78 deletions(-) diff --git a/Gifu.xcodeproj/project.pbxproj b/Gifu.xcodeproj/project.pbxproj index 960bee3..7a0d47b 100644 --- a/Gifu.xcodeproj/project.pbxproj +++ b/Gifu.xcodeproj/project.pbxproj @@ -233,7 +233,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1540; + LastUpgradeCheck = 1620; ORGANIZATIONNAME = "Kaishin & Co"; TargetAttributes = { 009BD1351BBC7F6500FC982B = { @@ -335,7 +335,7 @@ DEVELOPMENT_TEAM = 5G38N4D8G2; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = Tests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -363,7 +363,7 @@ DEVELOPMENT_TEAM = 5G38N4D8G2; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = Tests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -386,6 +386,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -447,6 +448,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -511,7 +513,7 @@ FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -527,6 +529,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 15.6; }; name = Debug; }; @@ -545,7 +548,7 @@ FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -561,6 +564,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 15.6; }; name = Release; }; diff --git a/Package.swift b/Package.swift index 324436c..9fa3522 100644 --- a/Package.swift +++ b/Package.swift @@ -5,8 +5,8 @@ import PackageDescription let package = Package( name: "Gifu", platforms: [ - .iOS(.v12), - .tvOS(.v12), + .iOS(.v14), + .tvOS(.v14), .visionOS(.v1), ], products: [ diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 44d947c..28d0d27 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -24,3 +24,10 @@ let package = Package( ], swiftLanguageModes: [.v6] ) + +for target in package.targets { + target.swiftSettings = target.swiftSettings ?? [] + target.swiftSettings?.append(contentsOf: [ + .enableUpcomingFeature("ExistentialAny") + ]) +} diff --git a/Sources/Gifu/Classes/Animator.swift b/Sources/Gifu/Classes/Animator.swift index 3a8d032..761343f 100644 --- a/Sources/Gifu/Classes/Animator.swift +++ b/Sources/Gifu/Classes/Animator.swift @@ -1,6 +1,7 @@ import UIKit /// Responsible for parsing GIF data and decoding the individual frames. +@MainActor public class Animator { /// Total duration of one animation loop var loopDuration: TimeInterval { @@ -20,7 +21,7 @@ public class Animator { private var displayLinkInitialized: Bool = false /// A delegate responsible for displaying the GIF frames. - private weak var delegate: (any GIFAnimatable)! + private weak var delegate: (any GIFAnimatable)? /// Callback for when all the loops of the animation are done (never called for infinite loops) private var animationBlock: (() -> Void)? = nil @@ -31,6 +32,7 @@ public class Animator { /// Responsible for starting and stopping the animation. private lazy var displayLink: CADisplayLink = { [unowned self] in self.displayLinkInitialized = true + let display = CADisplayLink( target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.onScreenUpdate) @@ -71,7 +73,7 @@ public class Animator { store.shouldChangeFrame(with: displayLink.duration) { if $0 { - delegate.animatorHasNewFrame() + delegate?.animatorHasNewFrame() if store.isLoopFinished, let loopBlock { loopBlock() } @@ -88,8 +90,12 @@ public class Animator { /// - parameter loopCount: Desired number of loops, <= 0 for infinite loop. /// - parameter completionHandler: Completion callback function func prepareForAnimation( - withGIFNamed imageName: String, inBundle bundle: Bundle = .main, size: CGSize, - contentMode: UIView.ContentMode, loopCount: Int = 0, completionHandler: (() -> Void)? = nil + withGIFNamed imageName: String, + inBundle bundle: Bundle = .main, + size: CGSize, + contentMode: UIView.ContentMode, + loopCount: Int = 0, + completionHandler: (@Sendable () -> Void)? = nil ) { guard let extensionRemoved = imageName.components(separatedBy: ".")[safe: 0], let imagePath = bundle.url(forResource: extensionRemoved, withExtension: "gif"), @@ -101,7 +107,8 @@ public class Animator { size: size, contentMode: contentMode, loopCount: loopCount, - completionHandler: completionHandler) + completionHandler: completionHandler + ) } /// Prepares the animator instance for animation. @@ -116,7 +123,7 @@ public class Animator { size: CGSize, contentMode: UIView.ContentMode, loopCount: Int = 0, - completionHandler: (() -> Void)? = nil + completionHandler: (@Sendable () -> Void)? = nil ) { frameStore = FrameStore( data: imageData, @@ -136,12 +143,18 @@ public class Animator { displayLink.add(to: .main, forMode: RunLoop.Mode.common) } - deinit { - if displayLinkInitialized { + private func invalidateDisplayLink() { + Task { [displayLink] in displayLink.invalidate() } } + deinit { + MainActor.assumeIsolated { + invalidateDisplayLink() + } + } + /// Start animating. func startAnimating() { if frameStore?.isAnimatable ?? false { @@ -164,9 +177,13 @@ public class Animator { /// - parameter animationBlock: Callback for when all the loops of the animation are done (never called for infinite loops) /// - parameter loopBlock: Callback for when a loop is done (at the end of each loop) func animate( - withGIFNamed imageName: String, size: CGSize, contentMode: UIView.ContentMode, - loopCount: Int = 0, preparationBlock: (() -> Void)? = nil, animationBlock: (() -> Void)? = nil, - loopBlock: (() -> Void)? = nil + withGIFNamed imageName: String, + size: CGSize, + contentMode: UIView.ContentMode, + loopCount: Int = 0, + preparationBlock: (@Sendable () -> Void)? = nil, + animationBlock: (@Sendable () -> Void)? = nil, + loopBlock: (@Sendable () -> Void)? = nil ) { self.animationBlock = animationBlock self.loopBlock = loopBlock @@ -175,7 +192,8 @@ public class Animator { size: size, contentMode: contentMode, loopCount: loopCount, - completionHandler: preparationBlock) + completionHandler: preparationBlock + ) startAnimating() } @@ -189,9 +207,13 @@ public class Animator { /// - parameter animationBlock: Callback for when all the loops of the animation are done (never called for infinite loops) /// - parameter loopBlock: Callback for when a loop is done (at the end of each loop) func animate( - withGIFData imageData: Data, size: CGSize, contentMode: UIView.ContentMode, loopCount: Int = 0, - preparationBlock: (() -> Void)? = nil, animationBlock: (() -> Void)? = nil, - loopBlock: (() -> Void)? = nil + withGIFData imageData: Data, + size: CGSize, + contentMode: UIView.ContentMode, + loopCount: Int = 0, + preparationBlock: (@Sendable () -> Void)? = nil, + animationBlock: (@Sendable () -> Void)? = nil, + loopBlock: (@Sendable () -> Void)? = nil ) { self.animationBlock = animationBlock self.loopBlock = loopBlock @@ -200,7 +222,8 @@ public class Animator { size: size, contentMode: contentMode, loopCount: loopCount, - completionHandler: preparationBlock) + completionHandler: preparationBlock + ) startAnimating() } @@ -232,5 +255,5 @@ private class DisplayLinkProxy { init(target: Animator) { self.target = target } /// Lets the target update the frame if needed. - @objc func onScreenUpdate() { target?.updateFrameIfNeeded() } + @MainActor @objc func onScreenUpdate() { target?.updateFrameIfNeeded() } } diff --git a/Sources/Gifu/Classes/FrameStore.swift b/Sources/Gifu/Classes/FrameStore.swift index bfe3900..1d5a16e 100644 --- a/Sources/Gifu/Classes/FrameStore.swift +++ b/Sources/Gifu/Classes/FrameStore.swift @@ -2,9 +2,9 @@ import ImageIO import UIKit /// Responsible for storing and updating the frames of a single GIF. -class FrameStore { +final class FrameStore: @unchecked Sendable { /// The strategy to use for frame cache. - enum FrameCachingStrategy: Equatable { + enum FrameCachingStrategy: Equatable, Sendable { // Cache only a given number of upcoming frames. case cacheUpcoming(Int) @@ -152,7 +152,7 @@ class FrameStore { // MARK: - Frames /// Loads the frames from an image source, resizes them, then caches them in `animatedFrames`. - func prepareFrames(_ completionHandler: (() -> Void)? = nil) { + func prepareFrames(_ completionHandler: (@Sendable () -> Void)? = nil) { frameCount = Int(CGImageSourceGetCount(imageSource)) lock.lock() animatedFrames.reserveCapacity(frameCount) @@ -228,8 +228,7 @@ extension FrameStore { /// Updates the frames by preloading new ones and replacing the previous frame with a placeholder. private func updateFrameCache() { if case let .cacheUpcoming(size) = cachingStrategy, - size < frameCount - 1 - { + size < frameCount - 1 { deleteCachedFrame(at: previousFrameIndex) } diff --git a/Sources/Gifu/Classes/GIFAnimatable.swift b/Sources/Gifu/Classes/GIFAnimatable.swift index 3426cd1..162383d 100644 --- a/Sources/Gifu/Classes/GIFAnimatable.swift +++ b/Sources/Gifu/Classes/GIFAnimatable.swift @@ -2,7 +2,8 @@ import Foundation import UIKit /// The protocol that view classes need to conform to to enable animated GIF support. -public protocol GIFAnimatable: AnyObject { +@MainActor +public protocol GIFAnimatable: AnyObject, Sendable { /// Responsible for managing the animation frames. var animator: Animator? { get set } @@ -16,11 +17,10 @@ public protocol GIFAnimatable: AnyObject { var contentMode: UIView.ContentMode { get set } } - /// A single-property protocol that animatable classes can optionally conform to. public protocol ImageContainer { /// Used for displaying the animation frames. - var image: UIImage? { get set } + @MainActor var image: UIImage? { get set } } extension GIFAnimatable where Self: ImageContainer { @@ -58,14 +58,22 @@ extension GIFAnimatable { /// - parameter preparationBlock: Callback for when preparation is done /// - parameter animationBlock: Callback for when all the loops of the animation are done (never called for infinite loops) /// - parameter loopBlock: Callback for when a loop is done (at the end of each loop) - public func animate(withGIFNamed imageName: String, loopCount: Int = 0, preparationBlock: (() -> Void)? = nil, animationBlock: (() -> Void)? = nil, loopBlock: (() -> Void)? = nil) { - animator?.animate(withGIFNamed: imageName, - size: frame.size, - contentMode: contentMode, - loopCount: loopCount, - preparationBlock: preparationBlock, - animationBlock: animationBlock, - loopBlock: loopBlock) + public func animate( + withGIFNamed imageName: String, + loopCount: Int = 0, + preparationBlock: (@Sendable () -> Void)? = nil, + animationBlock: (@Sendable () -> Void)? = nil, + loopBlock: (@Sendable () -> Void)? = nil + ) { + animator?.animate( + withGIFNamed: imageName, + size: frame.size, + contentMode: contentMode, + loopCount: loopCount, + preparationBlock: preparationBlock, + animationBlock: animationBlock, + loopBlock: loopBlock + ) } /// Prepare for animation and start animating immediately. @@ -75,14 +83,22 @@ extension GIFAnimatable { /// - parameter preparationBlock: Callback for when preparation is done /// - parameter animationBlock: Callback for when all the loops of the animation are done (never called for infinite loops) /// - parameter loopBlock: Callback for when a loop is done (at the end of each loop) - public func animate(withGIFData imageData: Data, loopCount: Int = 0, preparationBlock: (() -> Void)? = nil, animationBlock: (() -> Void)? = nil, loopBlock: (() -> Void)? = nil) { - animator?.animate(withGIFData: imageData, - size: frame.size, - contentMode: contentMode, - loopCount: loopCount, - preparationBlock: preparationBlock, - animationBlock: animationBlock, - loopBlock: loopBlock) + public func animate( + withGIFData imageData: Data, + loopCount: Int = 0, + preparationBlock: (@Sendable () -> Void)? = nil, + animationBlock: (@Sendable () -> Void)? = nil, + loopBlock: (@Sendable () -> Void)? = nil + ) { + animator?.animate( + withGIFData: imageData, + size: frame.size, + contentMode: contentMode, + loopCount: loopCount, + preparationBlock: preparationBlock, + animationBlock: animationBlock, + loopBlock: loopBlock + ) } /// Prepare for animation and start animating immediately. @@ -92,16 +108,29 @@ extension GIFAnimatable { /// - parameter preparationBlock: Callback for when preparation is done /// - parameter animationBlock: Callback for when all the loops of the animation are done (never called for infinite loops) /// - parameter loopBlock: Callback for when a loop is done (at the end of each loop) - public func animate(withGIFURL imageURL: URL, loopCount: Int = 0, preparationBlock: (() -> Void)? = nil, animationBlock: (() -> Void)? = nil, loopBlock: (() -> Void)? = nil) { + public func animate( + withGIFURL imageURL: URL, + loopCount: Int = 0, + preparationBlock: (@Sendable () -> Void)? = nil, + animationBlock: (@Sendable () -> Void)? = nil, + loopBlock: (@Sendable () -> Void)? = nil + ) { let session = URLSession.shared let task = session.dataTask(with: imageURL) { (data, response, error) in switch (data, response, error) { case (.none, _, let error?): - print("Error downloading gif:", error.localizedDescription, "at url:", imageURL.absoluteString) + print( + "Error downloading gif:", error.localizedDescription, "at url:", imageURL.absoluteString) case (let data?, _, _): DispatchQueue.main.async { - self.animate(withGIFData: data, loopCount: loopCount, preparationBlock: preparationBlock, animationBlock: animationBlock, loopBlock: loopBlock) + self.animate( + withGIFData: data, + loopCount: loopCount, + preparationBlock: preparationBlock, + animationBlock: animationBlock, + loopBlock: loopBlock + ) } default: () } @@ -115,14 +144,18 @@ extension GIFAnimatable { /// - parameter imageName: The file name of the GIF in the main bundle. /// - parameter loopCount: Desired number of loops, <= 0 for infinite loop. /// - parameter completionHandler: Callback for when preparation is done - public func prepareForAnimation(withGIFNamed imageName: String, - loopCount: Int = 0, - completionHandler: (() -> Void)? = nil) { - animator?.prepareForAnimation(withGIFNamed: imageName, - size: frame.size, - contentMode: contentMode, - loopCount: loopCount, - completionHandler: completionHandler) + public func prepareForAnimation( + withGIFNamed imageName: String, + loopCount: Int = 0, + completionHandler: (@Sendable () -> Void)? = nil + ) { + animator?.prepareForAnimation( + withGIFNamed: imageName, + size: frame.size, + contentMode: contentMode, + loopCount: loopCount, + completionHandler: completionHandler + ) } /// Prepares the animator instance for animation. @@ -130,18 +163,22 @@ extension GIFAnimatable { /// - parameter imageData: GIF image data. /// - parameter loopCount: Desired number of loops, <= 0 for infinite loop. /// - parameter completionHandler: Callback for when preparation is done - public func prepareForAnimation(withGIFData imageData: Data, - loopCount: Int = 0, - completionHandler: (() -> Void)? = nil) { - if var imageContainer = self as? any ImageContainer { + public func prepareForAnimation( + withGIFData imageData: Data, + loopCount: Int = 0, + completionHandler: (@Sendable () -> Void)? = nil + ) { + if var imageContainer = self as? (any ImageContainer) { imageContainer.image = UIImage(data: imageData) } - animator?.prepareForAnimation(withGIFData: imageData, - size: frame.size, - contentMode: contentMode, - loopCount: loopCount, - completionHandler: completionHandler) + animator?.prepareForAnimation( + withGIFData: imageData, + size: frame.size, + contentMode: contentMode, + loopCount: loopCount, + completionHandler: completionHandler + ) } /// Prepares the animator instance for animation. @@ -149,19 +186,24 @@ extension GIFAnimatable { /// - parameter imageURL: GIF image url. /// - parameter loopCount: Desired number of loops, <= 0 for infinite loop. /// - parameter completionHandler: Callback for when preparation is done - public func prepareForAnimation(withGIFURL imageURL: URL, - loopCount: Int = 0, - completionHandler: (() -> Void)? = nil) { + public func prepareForAnimation( + withGIFURL imageURL: URL, + loopCount: Int = 0, + completionHandler: (@Sendable () -> Void)? = nil + ) { let session = URLSession.shared let task = session.dataTask(with: imageURL) { (data, response, error) in switch (data, response, error) { case (.none, _, let error?): - print("Error downloading gif:", error.localizedDescription, "at url:", imageURL.absoluteString) + print( + "Error downloading gif:", error.localizedDescription, "at url:", imageURL.absoluteString) case (let data?, _, _): DispatchQueue.main.async { - self.prepareForAnimation(withGIFData: data, - loopCount: loopCount, - completionHandler: completionHandler) + self.prepareForAnimation( + withGIFData: data, + loopCount: loopCount, + completionHandler: completionHandler + ) } default: () } @@ -209,7 +251,7 @@ extension GIFAnimatable { /// Updates the image with a new frame if necessary. public func updateImageIfNeeded() { - if var imageContainer = self as? any ImageContainer { + if var imageContainer = self as? (any ImageContainer) { let container = imageContainer imageContainer.image = activeFrame ?? container.image } else { diff --git a/Sources/Gifu/Classes/GIFImageView.swift b/Sources/Gifu/Classes/GIFImageView.swift index 683b702..bf09926 100644 --- a/Sources/Gifu/Classes/GIFImageView.swift +++ b/Sources/Gifu/Classes/GIFImageView.swift @@ -4,7 +4,7 @@ import UIKit public class GIFImageView: UIImageView, GIFAnimatable { /// A lazy animator. public lazy var animator: Animator? = { - return Animator(withDelegate: self) + Animator(withDelegate: self) }() /// Layer delegate method called periodically by the layer. **Should not** be called manually. diff --git a/Sources/Gifu/Helpers/ImageSourceHelpers.swift b/Sources/Gifu/Helpers/ImageSourceHelpers.swift index 16a1dfa..5f79637 100755 --- a/Sources/Gifu/Helpers/ImageSourceHelpers.swift +++ b/Sources/Gifu/Helpers/ImageSourceHelpers.swift @@ -1,6 +1,7 @@ import ImageIO import MobileCoreServices import UIKit +import UniformTypeIdentifiers typealias GIFProperties = [String: Double] @@ -69,7 +70,8 @@ extension CGImageSource { /// /// - returns: A boolean value that is `true` if the image source contains animated GIF data. var isAnimatedGIF: Bool { - let isTypeGIF = UTTypeConformsTo(CGImageSourceGetType(self) ?? "" as CFString, kUTTypeGIF) + let type = (CGImageSourceGetType(self) as? String) ?? "" + let isTypeGIF = UTType(type)?.conforms(to: .gif) let imageCount = CGImageSourceGetCount(self) return isTypeGIF != false && imageCount > 1 } From 3a86a2f9a69fd7253de477800ea8bba0aef1bcf9 Mon Sep 17 00:00:00 2001 From: Reda Lemeden Date: Sun, 19 Jan 2025 14:44:50 +0100 Subject: [PATCH 4/9] Update Makefile and GitHub Actions for iOS and tvOS testing - Parameterize iOS device in Makefile for flexibility. - Update GitHub Actions workflow to use Xcode 15.4 and 16.2. - Add separate build jobs for Xcode 15 and Xcode 16 with respective device configurations for iOS tests. - Ensure tvOS tests are run in both environments. --- .github/workflows/test.yml | 26 ++++++++++++++++++++++---- Makefile | 4 +++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 937cac7..b0f3f00 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,14 +9,32 @@ on: - master jobs: - build: - runs-on: macOS-13 + build-xcode-15: + runs-on: macos-latest env: - DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer steps: - uses: actions/checkout@v1 + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: 15.4 - name: Run iOS tests - run: make test-ios + run: make test-ios DEVICE="iPhone 15" + - name: Run tvOS tests + run: make test-tvos + + build-xcode-16: + runs-on: macos-latest + env: + DEVELOPER_DIR: /Applications/Xcode_16.2.app/Contents/Developer + + steps: + - uses: actions/checkout@v1 + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: 16.2 + - name: Run iOS tests + run: make test-ios DEVICE="iPhone 16" - name: Run tvOS tests run: make test-tvos diff --git a/Makefile b/Makefile index 6656d07..9a94eeb 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,11 @@ +DEVICE ?= "iPhone 15" + test-ios: set -o pipefail && \ xcodebuild test \ -project Gifu.xcodeproj \ -scheme Gifu \ - -destination platform="iOS Simulator,name=iPhone 15" \ + -destination platform="iOS Simulator,name=$(DEVICE)" \ | xcpretty test-tvos: From 9a5c19605d331e1982cf207c43c4c10ff1cef0d7 Mon Sep 17 00:00:00 2001 From: Reda Lemeden Date: Sun, 19 Jan 2025 15:04:54 +0100 Subject: [PATCH 5/9] Fix tests --- .github/workflows/test.yml | 9 +- Makefile | 8 +- Tests/GifuTests.swift | 240 ++++++++++++++++++------------------- 3 files changed, 129 insertions(+), 128 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b0f3f00..2922682 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,7 @@ on: - master jobs: + # Device list: https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md build-xcode-15: runs-on: macos-latest env: @@ -20,21 +21,21 @@ jobs: with: xcode-version: 15.4 - name: Run iOS tests - run: make test-ios DEVICE="iPhone 15" + run: make test-ios PLATFORM="iOS Simulator,name=iPhone 15" - name: Run tvOS tests run: make test-tvos build-xcode-16: runs-on: macos-latest env: - DEVELOPER_DIR: /Applications/Xcode_16.2.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_16.app/Contents/Developer steps: - uses: actions/checkout@v1 - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 16.2 + xcode-version: 16.0 - name: Run iOS tests - run: make test-ios DEVICE="iPhone 16" + run: make test-ios PLATFORM="iOS Simulator,name=iPhone 16" - name: Run tvOS tests run: make test-tvos diff --git a/Makefile b/Makefile index 9a94eeb..54dd6ae 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ -DEVICE ?= "iPhone 15" +PLATFORM ?= "iOS,name=iPhone 16" test-ios: set -o pipefail && \ xcodebuild test \ -project Gifu.xcodeproj \ -scheme Gifu \ - -destination platform="iOS Simulator,name=$(DEVICE)" \ - | xcpretty + -destination platform=$(PLATFORM) \ + | xcbeautify test-tvos: set -o pipefail && \ @@ -14,4 +14,4 @@ test-tvos: -project Gifu.xcodeproj \ -scheme Gifu \ -destination platform="tvOS Simulator,name=Apple TV" \ - | xcpretty + | xcbeautify diff --git a/Tests/GifuTests.swift b/Tests/GifuTests.swift index 8355d04..4e85e31 100644 --- a/Tests/GifuTests.swift +++ b/Tests/GifuTests.swift @@ -1,157 +1,157 @@ -#if os(iOS) - import XCTest - import ImageIO - @testable import Gifu - - private let imageData = testImageDataNamed("mugen.gif") - private let staticImage = UIImage(data: imageData)! - private let preloadFrameCount = 20 - - class DummyAnimatable: GIFAnimatable { - init() {} - var animator: Animator? = nil - var image: UIImage? = nil - var layer = CALayer() - var frame: CGRect = .zero - var contentMode: UIView.ContentMode = .scaleToFill - func animatorHasNewFrame() {} +import ImageIO +import XCTest + +@testable import Gifu + +private let imageData = testImageDataNamed("mugen.gif") +private let staticImage = UIImage(data: imageData)! +private let preloadFrameCount = 20 + +class DummyAnimatable: GIFAnimatable { + init() {} + var animator: Animator? = nil + var image: UIImage? = nil + var layer = CALayer() + var frame: CGRect = .zero + var contentMode: UIView.ContentMode = .scaleToFill + func animatorHasNewFrame() {} +} + +@MainActor +class GifuTests: XCTestCase { + var animator: Animator! + var originalFrameCount: Int! + let delegate = DummyAnimatable() + + override func setUp() { + super.setUp() + animator = Animator(withDelegate: delegate) + animator.prepareForAnimation( + withGIFData: imageData, size: staticImage.size, contentMode: .scaleToFill) + originalFrameCount = 44 } - class GifuTests: XCTestCase { - var animator: Animator! - var originalFrameCount: Int! - let delegate = DummyAnimatable() - - override func setUp() { - super.setUp() - animator = Animator(withDelegate: delegate) - animator.prepareForAnimation( - withGIFData: imageData, size: staticImage.size, contentMode: .scaleToFill) - originalFrameCount = 44 - } - - func testIsAnimatable() { - XCTAssertNotNil(animator.frameStore) - guard let store = animator.frameStore else { return } - XCTAssertTrue(store.isAnimatable) - } + func testIsAnimatable() { + XCTAssertNotNil(animator.frameStore) + guard let store = animator.frameStore else { return } + XCTAssertTrue(store.isAnimatable) + } - func testCurrentFrame() { - XCTAssertNotNil(animator.frameStore) - guard let store = animator.frameStore else { return } - XCTAssertEqual(store.currentFrameIndex, 0) - } + func testCurrentFrame() { + XCTAssertNotNil(animator.frameStore) + guard let store = animator.frameStore else { return } + XCTAssertEqual(store.currentFrameIndex, 0) + } - func testFramePreload() { - XCTAssertNotNil(animator.frameStore) - guard let store = animator.frameStore else { return } + func testFramePreload() { + XCTAssertNotNil(animator.frameStore) + guard let store = animator.frameStore else { return } - let expectation = self.expectation(description: "frameDuration") + let expectation = self.expectation(description: "frameDuration") - store.prepareFrames { - let animatedFrameCount = store.animatedFrames.count - XCTAssertEqual(animatedFrameCount, self.originalFrameCount) - XCTAssertNotNil(store.frame(at: preloadFrameCount - 1)) - XCTAssertNil(store.frame(at: preloadFrameCount + 1)?.images) - XCTAssertEqual(store.currentFrameIndex, 0) + store.prepareFrames { [originalFrameCount] in + let animatedFrameCount = store.animatedFrames.count + XCTAssertEqual(animatedFrameCount, originalFrameCount) + XCTAssertNotNil(store.frame(at: preloadFrameCount - 1)) + XCTAssertNil(store.frame(at: preloadFrameCount + 1)?.images) + XCTAssertEqual(store.currentFrameIndex, 0) - store.shouldChangeFrame(with: 1.0) { hasNewFrame in - XCTAssertTrue(hasNewFrame) - XCTAssertEqual(store.currentFrameIndex, 1) - expectation.fulfill() - } + store.shouldChangeFrame(with: 1.0) { hasNewFrame in + XCTAssertTrue(hasNewFrame) + XCTAssertEqual(store.currentFrameIndex, 1) + expectation.fulfill() } + } - waitForExpectations(timeout: 1.0) { error in - if let error = error { - print("Error: \(error.localizedDescription)") - } + waitForExpectations(timeout: 1.0) { error in + if let error = error { + print("Error: \(error.localizedDescription)") } } + } - func testFrameInfo() { - XCTAssertNotNil(animator.frameStore) - guard let store = animator.frameStore else { return } + func testFrameInfo() { + XCTAssertNotNil(animator.frameStore) + guard let store = animator.frameStore else { return } - let expectation = self.expectation(description: "testFrameInfoIsAccurate") + let expectation = self.expectation(description: "testFrameInfoIsAccurate") - store.prepareFrames { - let frameDuration = store.frame(at: 5)?.duration ?? 0 - XCTAssertTrue((frameDuration - 0.05) < 0.00001) + store.prepareFrames { + let frameDuration = store.frame(at: 5)?.duration ?? 0 + XCTAssertTrue((frameDuration - 0.05) < 0.00001) - let imageSize = store.frame(at: 5)?.size ?? CGSize.zero - XCTAssertEqual(imageSize, staticImage.size) + let imageSize = store.frame(at: 5)?.size ?? CGSize.zero + XCTAssertEqual(imageSize, staticImage.size) - expectation.fulfill() - } + expectation.fulfill() + } - waitForExpectations(timeout: 1.0) { error in - if let error = error { - print("Error: \(error.localizedDescription)") - } + waitForExpectations(timeout: 1.0) { error in + if let error = error { + print("Error: \(error.localizedDescription)") } } + } - func testFinishedStates() { - animator = Animator(withDelegate: delegate) - animator.prepareForAnimation( - withGIFData: imageData, size: staticImage.size, contentMode: .scaleToFill, loopCount: 2) - - XCTAssertNotNil(animator.frameStore) - guard let store = animator.frameStore else { return } + func testFinishedStates() { + animator = Animator(withDelegate: delegate) + animator.prepareForAnimation( + withGIFData: imageData, size: staticImage.size, contentMode: .scaleToFill, loopCount: 2) - let expectation = self.expectation(description: "testFinishedStatesAreSetCorrectly") + XCTAssertNotNil(animator.frameStore) + guard let store = animator.frameStore else { return } - store.prepareFrames { - let animatedFrameCount = store.animatedFrames.count - XCTAssertEqual(store.currentFrameIndex, 0) + let expectation = self.expectation(description: "testFinishedStatesAreSetCorrectly") - // Animate through all the frames (first loop) - for frame in 1.. Data { - let testBundle = Bundle(for: GifuTests.self) - let imagePath = testBundle.bundleURL.appendingPathComponent(name) - return (try! Data(contentsOf: imagePath)) - } -#endif +private func testImageDataNamed(_ name: String) -> Data { + let testBundle = Bundle(for: GifuTests.self) + let imagePath = testBundle.bundleURL.appendingPathComponent(name) + return (try! Data(contentsOf: imagePath)) +} From b360c101abe0c0f50b0cbc66533575c9a31dc2be Mon Sep 17 00:00:00 2001 From: Reda Lemeden Date: Sun, 19 Jan 2025 15:10:42 +0100 Subject: [PATCH 6/9] Update Makefile and GitHub Actions --- .github/workflows/test.yml | 4 ++-- Makefile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2922682..ff55065 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v1 - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 15.4 + xcode-version: "15.4" - name: Run iOS tests run: make test-ios PLATFORM="iOS Simulator,name=iPhone 15" - name: Run tvOS tests @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@v1 - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 16.0 + xcode-version: "16.0" - name: Run iOS tests run: make test-ios PLATFORM="iOS Simulator,name=iPhone 16" - name: Run tvOS tests diff --git a/Makefile b/Makefile index 54dd6ae..6e2addf 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PLATFORM ?= "iOS,name=iPhone 16" +PLATFORM ?= "iOS Simulator,name=iPhone 16" test-ios: set -o pipefail && \ From d1e55c8e8f79e35ca177748e8ef614494c227bfa Mon Sep 17 00:00:00 2001 From: Reda Lemeden Date: Sun, 19 Jan 2025 15:12:18 +0100 Subject: [PATCH 7/9] Update GitHub Actions workflow to specify exact Xcode versions for iOS and tvOS testing --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ff55065..0e5ff33 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v1 - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: "15.4" + xcode-version: "15.4.0" - name: Run iOS tests run: make test-ios PLATFORM="iOS Simulator,name=iPhone 15" - name: Run tvOS tests @@ -28,13 +28,13 @@ jobs: build-xcode-16: runs-on: macos-latest env: - DEVELOPER_DIR: /Applications/Xcode_16.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_16.1.app/Contents/Developer steps: - uses: actions/checkout@v1 - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: "16.0" + xcode-version: "16.1.0" - name: Run iOS tests run: make test-ios PLATFORM="iOS Simulator,name=iPhone 16" - name: Run tvOS tests From 9dde398bad9cb8f96506a7f5f54737ab3c4ecd52 Mon Sep 17 00:00:00 2001 From: Reda Lemeden Date: Sun, 19 Jan 2025 15:18:16 +0100 Subject: [PATCH 8/9] Update Makefile and GitHub Actions --- .github/workflows/test.yml | 4 ++-- Makefile | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e5ff33..96c91e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: with: xcode-version: "15.4.0" - name: Run iOS tests - run: make test-ios PLATFORM="iOS Simulator,name=iPhone 15" + run: make test-ios-17 - name: Run tvOS tests run: make test-tvos @@ -36,6 +36,6 @@ jobs: with: xcode-version: "16.1.0" - name: Run iOS tests - run: make test-ios PLATFORM="iOS Simulator,name=iPhone 16" + run: make test-ios-18 - name: Run tvOS tests run: make test-tvos diff --git a/Makefile b/Makefile index 6e2addf..5b10f7f 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,17 @@ -PLATFORM ?= "iOS Simulator,name=iPhone 16" +test-ios-18: + set -o pipefail && \ + xcodebuild test \ + -project Gifu.xcodeproj \ + -scheme Gifu \ + -destination "platform=iOS Simulator,name=iPhone 16,OS=18.1" \ + | xcbeautify -test-ios: +test-ios-17: set -o pipefail && \ xcodebuild test \ -project Gifu.xcodeproj \ -scheme Gifu \ - -destination platform=$(PLATFORM) \ + -destination "platform=iOS Simulator,name=iPhone 15,OS=17.5" \ | xcbeautify test-tvos: @@ -13,5 +19,5 @@ test-tvos: xcodebuild test \ -project Gifu.xcodeproj \ -scheme Gifu \ - -destination platform="tvOS Simulator,name=Apple TV" \ + -destination "platform=tvOS Simulator,name=Apple TV" \ | xcbeautify From 4456e59dd08c758ddb66ed2b78d66f3228b61a78 Mon Sep 17 00:00:00 2001 From: Reda Lemeden Date: Mon, 20 Jan 2025 21:23:28 +0100 Subject: [PATCH 9/9] Update podspec --- Gifu.podspec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Gifu.podspec b/Gifu.podspec index 56a72f8..062ad70 100644 --- a/Gifu.podspec +++ b/Gifu.podspec @@ -7,11 +7,11 @@ Pod::Spec.new do |s| s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Reda Lemeden" => "git@redalemeden.com" } s.source = { :git => "https://github.com/kaishin/Gifu.git", :tag => "v#{s.version}", :submodules => true } - s.platform = :ios, "9.0" - s.platform = :tvos, "9.0" - s.swift_versions = ["5.0", "5.1", "5.2", "5.3", "5.4"] + s.platform = :ios, "14.0" + s.platform = :tvos, "14.0" + s.swift_versions = ["5.0", "5.1", "5.2", "5.3", "5.4", "6.0", "6.1", "6.2"] s.ios.source_files = "Sources/**/*.{h,swift}" s.tvos.source_files = "Sources/**/*.{h,swift}" - s.ios.deployment_target = "9.0" - s.tvos.deployment_target = "9.0" + s.ios.deployment_target = "14.0" + s.tvos.deployment_target = "14.0" end