diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3196c67..c6719e5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,12 +22,12 @@ jobs: - name: Build Example Project run: | cd Example - xcodebuild -workspace ImageKit.xcworkspace -scheme ImageKit-Example -destination 'platform=iOS Simulator,name=iPhone 15' clean build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO + xcodebuild -workspace ImageKit.xcworkspace -scheme ImageKit-Example -destination 'platform=iOS Simulator,name=iPhone 17' clean build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO - name: Run Tests run: | cd Example - xcodebuild -workspace ImageKit.xcworkspace -scheme ImageKit-Example -destination 'platform=iOS Simulator,name=iPhone 15' -enableCodeCoverage YES clean test CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO + xcodebuild -workspace ImageKit.xcworkspace -scheme ImageKit-Example -destination 'platform=iOS Simulator,name=iPhone 17' -enableCodeCoverage YES clean test CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO - name: Upload Coverage to codecov run: bash <(curl -s https://codecov.io/bash) -J '^ImageKitIO$' -X coveragepy diff --git a/.gitignore b/.gitignore index eb137ee..3cc75e1 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,8 @@ Carthage/Build Pods/* Example/Pods/* Server/node_modules -Server/*.lock \ No newline at end of file +Server/*.lock + +#Swift package build outputs +.swiftpm +.build/ diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Example/ImageKit.xcodeproj/project.pbxproj b/Example/ImageKit.xcodeproj/project.pbxproj index 6a97bd0..fcca67c 100644 --- a/Example/ImageKit.xcodeproj/project.pbxproj +++ b/Example/ImageKit.xcodeproj/project.pbxproj @@ -160,6 +160,7 @@ TargetAttributes = { 607FACCF1AFB9204008FA782 = { CreatedOnToolsVersion = 6.3.1; + DevelopmentTeam = FY66VYM646; LastSwiftMigration = 0900; }; }; @@ -385,11 +386,12 @@ baseConfigurationReference = 7661EF116E06444B352B18F0 /* Pods-ImageKit_Example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = FY66VYM646; INFOPLIST_FILE = ImageKit/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MODULE_NAME = ExampleApp; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.--PRODUCT-NAME-"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; @@ -401,11 +403,12 @@ baseConfigurationReference = BFEF935EF71672E4888B2B32 /* Pods-ImageKit_Example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = FY66VYM646; INFOPLIST_FILE = ImageKit/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MODULE_NAME = ExampleApp; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.--PRODUCT-NAME-"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; diff --git a/Example/Podfile b/Example/Podfile index 573d4b9..e094ec8 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -7,7 +7,7 @@ end post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' end end installer.pods_project.targets.each do |target| diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 30691a3..3c4c07e 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,6 +1,6 @@ PODS: - - ImageKitIO (3.0.0) - - ImageKitIO/Tests (3.0.0): + - ImageKitIO (3.1.0) + - ImageKitIO/Tests (3.1.0): - Mocker (~> 2.5) - Nimble (~> 10.0.0) - Quick (~> 5.0.1) @@ -23,11 +23,11 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - ImageKitIO: 804da6f03a903c4f540d6a3c0dd8ee87e9f29040 + ImageKitIO: 9cb4bb64a800a248f0f60ed5070458bd3e6ac61a Mocker: 8c731a8104962f246cadf2b02556218e9edc1390 Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84 Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179 -PODFILE CHECKSUM: 406170311ac5dd5482330203b884d5db8ef323c7 +PODFILE CHECKSUM: 8224e8c767b2e111bf4f338e9aaa55d7cd447b9e -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/ImageKitIO.podspec b/ImageKitIO.podspec index 7200e75..411fa79 100644 --- a/ImageKitIO.podspec +++ b/ImageKitIO.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ImageKitIO' - s.version = '3.0.0' + s.version = '3.1.0' s.summary = 'iOS SDK for ImageKit.io' @@ -14,13 +14,16 @@ ImageKit is a complete image optimization and transformation solution that comes s.author = { 'ImageKit Developer' => 'developer@imagekit.io', 'ahnv' => 'abhinav@imagekit.io' } s.source = { :git => 'https://github.com/imagekit-developer/imagekit-ios.git', :tag => s.version.to_s } - s.swift_version = '4.0' - s.ios.deployment_target = '12.0' + s.swift_version = '6.0' + s.ios.deployment_target = '13.0' - s.source_files = 'ImageKit/**/*' + s.source_files = 'Sources/ImageKit/**/*' s.test_spec 'Tests' do |test_spec| test_spec.source_files = 'Tests/**/*' + test_spec.resource_bundles = { + "ImageKitIO_ImageKitIO-Tests" => ["Tests/fixtures/**"] + } test_spec.dependency 'Quick', '~> 5.0.1' test_spec.dependency 'Nimble', '~> 10.0.0' test_spec.dependency 'Mocker', '~> 2.5' diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..2eaf445 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,60 @@ +{ + "originHash" : "60afdccc3655ee729de5d4e7daa470f850930a652f5b2e7235ced4ef83f077f9", + "pins" : [ + { + "identity" : "cwlcatchexception", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlCatchException.git", + "state" : { + "revision" : "07b2ba21d361c223e25e3c1e924288742923f08c", + "version" : "2.2.1" + } + }, + { + "identity" : "cwlpreconditiontesting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git", + "state" : { + "revision" : "0139c665ebb45e6a9fbdb68aabfd7c39f3fe0071", + "version" : "2.2.2" + } + }, + { + "identity" : "mocker", + "kind" : "remoteSourceControl", + "location" : "https://github.com/WeTransfer/Mocker.git", + "state" : { + "revision" : "5d86f27a8f80d4ba388bc1a379a3c2289a1f3d18", + "version" : "2.6.0" + } + }, + { + "identity" : "nimble", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Nimble.git", + "state" : { + "revision" : "1f3bde57bde12f5e7b07909848c071e9b73d6edc", + "version" : "10.0.0" + } + }, + { + "identity" : "quick", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Quick.git", + "state" : { + "revision" : "f9d519828bb03dfc8125467d8f7b93131951124c", + "version" : "5.0.1" + } + }, + { + "identity" : "swifter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/httpswift/swifter.git", + "state" : { + "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", + "version" : "1.5.0" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..dbeb2c2 --- /dev/null +++ b/Package.swift @@ -0,0 +1,35 @@ +// swift-tools-version: 6.1 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "ImageKitIO", + platforms: [.iOS(.v13)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "ImageKitIO", + targets: ["ImageKitIO"]), + ], + dependencies: [ + .package(url: "https://github.com/Quick/Quick.git", from: "5.0.1"), + .package(url: "https://github.com/Quick/Nimble.git", from: "10.0.0"), + .package(url: "https://github.com/WeTransfer/Mocker.git", from: "2.5.0"), + .package(url: "https://github.com/httpswift/swifter.git", from: "1.5.0") + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "ImageKitIO", + path: "Sources", + ), + .testTarget( + name: "ImageKitIO-Tests", + dependencies: ["ImageKitIO", "Quick", "Nimble", "Mocker", .product(name: "Swifter", package: "swifter")], + path: "Tests", + resources: [.process("fixtures")] + ), + ] +) diff --git a/README.md b/README.md index f27421b..1ba3f54 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,58 @@ ImageKit iOS Pod allows you to use real-time [image resizing](https://docs.image ## Installation ### Requirements -The library requires Swift 4.0 or above. +The library requires Swift 6.0 and minimum iOS version for deployment be 13.0 or above. -#### CocoaPods +### Swift Package Manager + +You can integrate ImageKit into your iOS project using the Swift Package Manager (SPM). + +After completing the Swift Package setup in your project, you can add ImageKit in one of the following ways: + +#### Using Xcode + +Open your project in Xcode, then go to File → Add Packages… + +``` +https://github.com/imagekit-developer/imagekit-ios.git +``` + +Select the Up to Next Major version rule, starting from version 3.1.0, and add the package to your app target’s Package Dependencies list. + +#### Using `Package.swift` + +If you prefer managing dependencies manually, add ImageKit to the dependencies array in your `Package.swift` file: + +```swift +// swift-tools-version: 6.0 +import PackageDescription + +let package = Package( + name: "Example", + platforms: [ + .iOS(.v13) + ], + dependencies: [ + .package(url: "https://github.com/imagekit-developer/imagekit-ios.git", .upToNextMajor(from: "3.1.0")) + ], + targets: [ + .target( + name: "Example", + dependencies: [ + "ImageKitIO" + ] + ) + ] +) +``` + +Once added, you can import ImageKitIO into your Swift files: + +``` +import ImageKitIO +``` + +### CocoaPods You can use CocoaPods to install ImageKit by adding it to your Podfile: diff --git a/ImageKit/Entity/CropMode.swift b/Sources/ImageKit/Entity/CropMode.swift similarity index 100% rename from ImageKit/Entity/CropMode.swift rename to Sources/ImageKit/Entity/CropMode.swift diff --git a/ImageKit/Entity/CropType.swift b/Sources/ImageKit/Entity/CropType.swift similarity index 100% rename from ImageKit/Entity/CropType.swift rename to Sources/ImageKit/Entity/CropType.swift diff --git a/ImageKit/Entity/FocusType.swift b/Sources/ImageKit/Entity/FocusType.swift similarity index 100% rename from ImageKit/Entity/FocusType.swift rename to Sources/ImageKit/Entity/FocusType.swift diff --git a/ImageKit/Entity/Format.swift b/Sources/ImageKit/Entity/Format.swift similarity index 100% rename from ImageKit/Entity/Format.swift rename to Sources/ImageKit/Entity/Format.swift diff --git a/ImageKit/Entity/IKError.swift b/Sources/ImageKit/Entity/IKError.swift similarity index 88% rename from ImageKit/Entity/IKError.swift rename to Sources/ImageKit/Entity/IKError.swift index ff93a01..ad3f3cb 100644 --- a/ImageKit/Entity/IKError.swift +++ b/Sources/ImageKit/Entity/IKError.swift @@ -17,7 +17,7 @@ public enum IKError: Error { case serverSideError(Int) } - public enum MultipartEncodingFailureReason { + public enum MultipartEncodingFailureReason : Sendable { case inputStreamReadFailed(error: Error) } diff --git a/ImageKit/Entity/InvalidArgumentError.swift b/Sources/ImageKit/Entity/InvalidArgumentError.swift similarity index 100% rename from ImageKit/Entity/InvalidArgumentError.swift rename to Sources/ImageKit/Entity/InvalidArgumentError.swift diff --git a/ImageKit/Entity/Rotation.swift b/Sources/ImageKit/Entity/Rotation.swift similarity index 100% rename from ImageKit/Entity/Rotation.swift rename to Sources/ImageKit/Entity/Rotation.swift diff --git a/ImageKit/Entity/StreamingFormat.swift b/Sources/ImageKit/Entity/StreamingFormat.swift similarity index 100% rename from ImageKit/Entity/StreamingFormat.swift rename to Sources/ImageKit/Entity/StreamingFormat.swift diff --git a/ImageKit/Entity/TransformationMapping.swift b/Sources/ImageKit/Entity/TransformationMapping.swift similarity index 100% rename from ImageKit/Entity/TransformationMapping.swift rename to Sources/ImageKit/Entity/TransformationMapping.swift diff --git a/ImageKit/Entity/TransformationPosition.swift b/Sources/ImageKit/Entity/TransformationPosition.swift similarity index 100% rename from ImageKit/Entity/TransformationPosition.swift rename to Sources/ImageKit/Entity/TransformationPosition.swift diff --git a/ImageKit/Entity/UploadAPIError.swift b/Sources/ImageKit/Entity/UploadAPIError.swift similarity index 100% rename from ImageKit/Entity/UploadAPIError.swift rename to Sources/ImageKit/Entity/UploadAPIError.swift diff --git a/ImageKit/Entity/UploadAPIResponse.swift b/Sources/ImageKit/Entity/UploadAPIResponse.swift similarity index 100% rename from ImageKit/Entity/UploadAPIResponse.swift rename to Sources/ImageKit/Entity/UploadAPIResponse.swift diff --git a/ImageKit/Entity/UploadPolicy.swift b/Sources/ImageKit/Entity/UploadPolicy.swift similarity index 95% rename from ImageKit/Entity/UploadPolicy.swift rename to Sources/ImageKit/Entity/UploadPolicy.swift index 1e0ebfd..a261348 100644 --- a/ImageKit/Entity/UploadPolicy.swift +++ b/Sources/ImageKit/Entity/UploadPolicy.swift @@ -7,7 +7,7 @@ import Foundation -public class UploadPolicy { +public final class UploadPolicy : Sendable { private static let DEFAULT_MAX_ERROR_RETRIES = 5 private static let DEFAULT_BACKOFF_MILLIS = 1000 private static let DEFAULT_BACKOFF_POLICY = BackoffPolicy.LINEAR @@ -26,12 +26,12 @@ public class UploadPolicy { self.backoffPolicy = backoffPolicy } - public enum NetworkType { + public enum NetworkType : Sendable { case ANY case UNMETERED } - public enum BackoffPolicy { + public enum BackoffPolicy : Sendable { case LINEAR case EXPONENTIAL } diff --git a/ImageKit/ImageKit.swift b/Sources/ImageKit/ImageKit.swift similarity index 95% rename from ImageKit/ImageKit.swift rename to Sources/ImageKit/ImageKit.swift index 5f9bfb1..6d76d4e 100644 --- a/ImageKit/ImageKit.swift +++ b/Sources/ImageKit/ImageKit.swift @@ -6,7 +6,6 @@ // import Foundation -public var TESTING: Bool = true public struct UserDefaultKeys { public static let KEY_CLIENT_PUBLIC_KEY = "IKClientKey" @@ -14,7 +13,7 @@ public struct UserDefaultKeys { public static let KEY_IMAGEKIT_TRANSFORMATION_POSITION = "IKTransformationPosition" } -open class ImageKit: NSObject { +open class ImageKit: NSObject, @unchecked Sendable { open fileprivate(set) var clientPublicKey: String! = "" open fileprivate(set) var imageKitEndpoint: String! = "" @@ -26,7 +25,8 @@ open class ImageKit: NSObject { public static let shared = ImageKit() - private let sharedUploader = ImageKitUploader() + @MainActor + private lazy var sharedUploader = ImageKitUploader() public override init() { @@ -43,11 +43,13 @@ open class ImageKit: NSObject { } @available(*, deprecated, message: "clientPublicKey Renamed to publicKey") + @MainActor public convenience init(clientPublicKey: String = "", imageKitEndpoint: String, transformationPosition: TransformationPosition = TransformationPosition.PATH) { self.init(publicKey: clientPublicKey, imageKitEndpoint: imageKitEndpoint, transformationPosition: transformationPosition) } @available(*, deprecated, message: "imageKitEndpoint Renamed to urlEndpoint") + @MainActor public convenience init(publicKey: String = "", imageKitEndpoint: String, transformationPosition: TransformationPosition = TransformationPosition.PATH) { self.init(publicKey: publicKey, urlEndpoint: imageKitEndpoint, transformationPosition: transformationPosition) } @@ -80,6 +82,7 @@ open class ImageKit: NSObject { return ImagekitUrlConstructor(src: src, transformationPosition: transformationPosition) } + @MainActor public func uploader() -> ImageKitUploader { return sharedUploader } diff --git a/ImageKit/ImageKitURLConstructor.swift b/Sources/ImageKit/ImageKitURLConstructor.swift similarity index 99% rename from ImageKit/ImageKitURLConstructor.swift rename to Sources/ImageKit/ImageKitURLConstructor.swift index 2971942..cf66f05 100644 --- a/ImageKit/ImageKitURLConstructor.swift +++ b/Sources/ImageKit/ImageKitURLConstructor.swift @@ -5,6 +5,7 @@ // Created by Abhinav Dhiman on 16/07/20. // import Foundation +import UIKit public class ImagekitUrlConstructor { @@ -528,6 +529,7 @@ public class ImagekitUrlConstructor { * @param focus Possible values include the values defined in enum FocusType. * @return the current ImagekitUrlConstructor object. */ + @MainActor public func setResponsive( view: UIView, minSize: Int = 0, diff --git a/ImageKit/ImageKitUploader.swift b/Sources/ImageKit/ImageKitUploader.swift similarity index 91% rename from ImageKit/ImageKitUploader.swift rename to Sources/ImageKit/ImageKitUploader.swift index 91ef6bf..c52028a 100644 --- a/ImageKit/ImageKitUploader.swift +++ b/Sources/ImageKit/ImageKitUploader.swift @@ -7,11 +7,13 @@ import Foundation import Network +import UIKit -public class ImageKitUploader { +public class ImageKitUploader : @unchecked Sendable { var currentNetworkPath: NWPath + @MainActor public init() { let monitor = NWPathMonitor() currentNetworkPath = monitor.currentPath @@ -22,6 +24,7 @@ public class ImageKitUploader { UIDevice.current.isBatteryMonitoringEnabled = true } + @MainActor public func upload( file: Data, token: String, @@ -39,17 +42,19 @@ public class ImageKitUploader { overwriteTags: Bool? = nil, overwriteCustomMetadata: Bool? = nil, customMetadata: [String : Any]? = nil, - progress: ((Progress) -> Void)? = nil, + progress: (@Sendable (Progress) -> Void)? = nil, urlConfiguration: URLSessionConfiguration = URLSessionConfiguration.default, policy: UploadPolicy = ImageKit.shared.defaultUploadPolicy, preprocessor: UploadPreprocessor? = nil, - completion: @escaping (Result<(HTTPURLResponse?, UploadAPIResponse?), Error>) -> Void) { + completion: @escaping @Sendable (Result<(HTTPURLResponse?, UploadAPIResponse?), Error>) -> Void) { if checkUploadPolicy(policy, completion) { DispatchQueue.global(qos: .default).async { var fileData = file if let imageProcessor = preprocessor as? ImageUploadPreprocessor { fileData = imageProcessor.outputFile(input: file, fileName: fileName) } else if let videoProcessor = preprocessor as? VideoUploadPreprocessor { + let ext = extensions + let metadata = customMetadata videoProcessor.listener = { data in UploadAPI.upload( file: data, @@ -61,13 +66,13 @@ public class ImageKitUploader { isPrivateFile: isPrivateFile, customCoordinates: customCoordinates, responseFields: responseFields, - extensions: extensions, + extensions: ext, webhookUrl: webhookUrl, overwriteFile: overwriteFile, overwriteAITags: overwriteAITags, overwriteTags: overwriteTags, overwriteCustomMetadata: overwriteCustomMetadata, - customMetadata: customMetadata, + customMetadata: metadata, progressClosure: progress, urlConfiguration: urlConfiguration, uploadPolicy: policy, @@ -107,6 +112,7 @@ public class ImageKitUploader { } } + @MainActor public func upload( file: UIImage, token: String, @@ -124,14 +130,14 @@ public class ImageKitUploader { overwriteTags: Bool? = nil, overwriteCustomMetadata: Bool? = nil, customMetadata: [String : Any]? = nil, - progress: ((Progress) -> Void)? = nil, + progress: (@Sendable (Progress) -> Void)? = nil, urlConfiguration: URLSessionConfiguration = URLSessionConfiguration.default, policy: UploadPolicy = ImageKit.shared.defaultUploadPolicy, preprocessor: ImageUploadPreprocessor? = nil, - completion: @escaping (Result<(HTTPURLResponse?, UploadAPIResponse?), Error>) -> Void) { + completion: @escaping @Sendable (Result<(HTTPURLResponse?, UploadAPIResponse?), Error>) -> Void) { if checkUploadPolicy(policy, completion) { DispatchQueue.global(qos: .default).async { - let image = preprocessor != nil ? preprocessor!.outputFile(input: file, fileName: fileName) : UIImagePNGRepresentation(file)! + let image = preprocessor != nil ? preprocessor!.outputFile(input: file, fileName: fileName) : file.pngData()! UploadAPI.upload( file: image, token: token, @@ -177,10 +183,10 @@ public class ImageKitUploader { overwriteTags: Bool? = nil, overwriteCustomMetadata: Bool? = nil, customMetadata: [String : Any]? = nil, - progress: ((Progress) -> Void)? = nil, + progress: (@Sendable (Progress) -> Void)? = nil, urlConfiguration: URLSessionConfiguration = URLSessionConfiguration.default, policy: UploadPolicy = ImageKit.shared.defaultUploadPolicy, - completion: @escaping (Result<(HTTPURLResponse?, UploadAPIResponse?), Error>) -> Void) { + completion: @escaping @Sendable (Result<(HTTPURLResponse?, UploadAPIResponse?), Error>) -> Void) { UploadAPI.upload( file: file.data(using: .utf8)!, token: token, @@ -207,6 +213,7 @@ public class ImageKitUploader { ) } + @MainActor internal func checkUploadPolicy(_ policy: UploadPolicy, _ completion: @escaping (Result<(HTTPURLResponse?, UploadAPIResponse?), Error>) -> Void) -> Bool { if policy.networkType == .UNMETERED && currentNetworkPath.isExpensive { completion(Result.failure(UploadAPIError(message: "POLICY_ERROR_METERED_NETWORK", help: nil))) diff --git a/ImageKit/Preprocess/ImageCrop.swift b/Sources/ImageKit/Preprocess/ImageCrop.swift similarity index 98% rename from ImageKit/Preprocess/ImageCrop.swift rename to Sources/ImageKit/Preprocess/ImageCrop.swift index 672e234..9aefe46 100644 --- a/ImageKit/Preprocess/ImageCrop.swift +++ b/Sources/ImageKit/Preprocess/ImageCrop.swift @@ -6,6 +6,7 @@ // import Foundation +import UIKit internal class ImageCrop : Preprocess { typealias T = UIImage diff --git a/ImageKit/Preprocess/ImageDimensionsLimiter.swift b/Sources/ImageKit/Preprocess/ImageDimensionsLimiter.swift similarity index 98% rename from ImageKit/Preprocess/ImageDimensionsLimiter.swift rename to Sources/ImageKit/Preprocess/ImageDimensionsLimiter.swift index 96d528c..dd07b8b 100644 --- a/ImageKit/Preprocess/ImageDimensionsLimiter.swift +++ b/Sources/ImageKit/Preprocess/ImageDimensionsLimiter.swift @@ -6,6 +6,7 @@ // import Foundation +import UIKit internal class ImageDimensionsLimiter : Preprocess { typealias T = UIImage diff --git a/ImageKit/Preprocess/ImageRotation.swift b/Sources/ImageKit/Preprocess/ImageRotation.swift similarity index 98% rename from ImageKit/Preprocess/ImageRotation.swift rename to Sources/ImageKit/Preprocess/ImageRotation.swift index aa6b1ee..b0092e3 100644 --- a/ImageKit/Preprocess/ImageRotation.swift +++ b/Sources/ImageKit/Preprocess/ImageRotation.swift @@ -6,6 +6,7 @@ // import Foundation +import UIKit internal class ImageRotation : Preprocess { typealias T = UIImage diff --git a/ImageKit/Preprocess/ImageUploadPreprocessor.swift b/Sources/ImageKit/Preprocess/ImageUploadPreprocessor.swift similarity index 95% rename from ImageKit/Preprocess/ImageUploadPreprocessor.swift rename to Sources/ImageKit/Preprocess/ImageUploadPreprocessor.swift index 805341e..19bf697 100644 --- a/ImageKit/Preprocess/ImageUploadPreprocessor.swift +++ b/Sources/ImageKit/Preprocess/ImageUploadPreprocessor.swift @@ -6,6 +6,7 @@ // import Foundation +import UIKit public final class ImageUploadPreprocessor : UploadPreprocessor { private let limit: ImageDimensionsLimiter @@ -30,7 +31,7 @@ public final class ImageUploadPreprocessor : UploadPreprocessor { image = limit.process(source: image) image = cropPoints?.process(source: image) ?? image image = rotation.process(source: image) - return format == .JPEG ? UIImageJPEGRepresentation(image, 1.0)! : UIImagePNGRepresentation(image)! + return format == .JPEG ? image.jpegData(compressionQuality: 1.0)! : image.pngData()! } public enum OutputFormat { diff --git a/ImageKit/Preprocess/Preprocess.swift b/Sources/ImageKit/Preprocess/Preprocess.swift similarity index 100% rename from ImageKit/Preprocess/Preprocess.swift rename to Sources/ImageKit/Preprocess/Preprocess.swift diff --git a/ImageKit/Preprocess/UploadPreprocessor.swift b/Sources/ImageKit/Preprocess/UploadPreprocessor.swift similarity index 100% rename from ImageKit/Preprocess/UploadPreprocessor.swift rename to Sources/ImageKit/Preprocess/UploadPreprocessor.swift diff --git a/ImageKit/Preprocess/VideoUploadPreprocessor.swift b/Sources/ImageKit/Preprocess/VideoUploadPreprocessor.swift similarity index 79% rename from ImageKit/Preprocess/VideoUploadPreprocessor.swift rename to Sources/ImageKit/Preprocess/VideoUploadPreprocessor.swift index 871d9a9..9395de3 100644 --- a/ImageKit/Preprocess/VideoUploadPreprocessor.swift +++ b/Sources/ImageKit/Preprocess/VideoUploadPreprocessor.swift @@ -6,7 +6,8 @@ // import Foundation -import AVFoundation +@preconcurrency import AVFoundation +import AudioToolbox public final class VideoUploadPreprocessor : UploadPreprocessor { @@ -43,23 +44,17 @@ public final class VideoUploadPreprocessor : UploadPreprocessor { let videoTrack = asset.tracks(withMediaType: .video).first! let audioTrack = asset.tracks(withMediaType: .audio).first let dimensions = videoTrack.naturalSize.applying(videoTrack.preferredTransform) - let size = CGSize(width: fabs(dimensions.width), height: fabs(dimensions.height)) - print("dimensions: width: \(fabs(dimensions.width)), height: \(fabs(dimensions.height))") + let size = CGSize(width: abs(dimensions.width), height: abs(dimensions.height)) + print("dimensions: width: \(abs(dimensions.width)), height: \(abs(dimensions.height))") print("fps: \(videoTrack.minFrameDuration.seconds)") - - var audioWriteFinished = false - var videoWriteFinished = false - let reader = try! AVAssetReader(asset: asset) let assetReaderVideoTrackOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32ARGB]) var assetReaderAudioTrackOutput: AVAssetReaderTrackOutput? var audioWriterInput: AVAssetWriterInput? if audioTrack != nil { - assetReaderAudioTrackOutput = AVAssetReaderTrackOutput(track: audioTrack!, outputSettings: nil) + assetReaderAudioTrackOutput = AVAssetReaderTrackOutput(track: audioTrack!, outputSettings: [AVFormatIDKey: kAudioFormatLinearPCM]) reader.add(assetReaderAudioTrackOutput!) - } else { - audioWriteFinished = true } reader.add(assetReaderVideoTrackOutput) @@ -89,52 +84,42 @@ public final class VideoUploadPreprocessor : UploadPreprocessor { outputSettings: [ AVFormatIDKey: pointee.mFormatID, AVEncoderBitRateKey: targetAudioBitrate, - AVSampleRateKey: pointee.mSampleRate + AVSampleRateKey: pointee.mSampleRate, + AVNumberOfChannelsKey: pointee.mChannelsPerFrame ] ) writer.add(audioWriterInput!) } writer.startWriting() reader.startReading() - writer.startSession(atSourceTime: kCMTimeZero) + writer.startSession(atSourceTime: CMTime.zero) videoWriterInput.requestMediaDataWhenReady(on: videoDispatchQueue, using: { while videoWriterInput.isReadyForMoreMediaData { guard let sample = assetReaderVideoTrackOutput.copyNextSampleBuffer() else { guard writer.inputs.contains(videoWriterInput) == true else { return } videoWriterInput.markAsFinished() - videoWriteFinished = true - if videoWriteFinished && audioWriteFinished { - writer.finishWriting(completionHandler: { - reader.cancelReading() - self.completionListener(try! Data(contentsOf: processedVideoUrl)) - }) - } break } videoWriterInput.append(sample) } - }) - - if audioTrack != nil { - audioWriterInput?.requestMediaDataWhenReady(on: audioDispatchQueue, using: { - while audioWriterInput!.isReadyForMoreMediaData { - guard let sample = assetReaderAudioTrackOutput?.copyNextSampleBuffer() else { - guard writer.inputs.contains(audioWriterInput!) == true else { return } - audioWriterInput!.markAsFinished() - audioWriteFinished = true - if videoWriteFinished && audioWriteFinished { + if audioTrack != nil { + audioWriterInput?.requestMediaDataWhenReady(on: audioDispatchQueue, using: { + while audioWriterInput!.isReadyForMoreMediaData { + guard let sample = assetReaderAudioTrackOutput?.copyNextSampleBuffer() else { + guard writer.inputs.contains(audioWriterInput!) == true else { return } + audioWriterInput!.markAsFinished() writer.finishWriting(completionHandler: { reader.cancelReading() self.completionListener(try! Data(contentsOf: processedVideoUrl)) }) + break } - break + audioWriterInput!.append(sample) } - audioWriterInput!.append(sample) - } - }) - } + }) + } + }) return Data() } diff --git a/ImageKit/Util/MimeDetector.swift b/Sources/ImageKit/Util/MimeDetector.swift similarity index 100% rename from ImageKit/Util/MimeDetector.swift rename to Sources/ImageKit/Util/MimeDetector.swift diff --git a/ImageKit/Util/MimeType.swift b/Sources/ImageKit/Util/MimeType.swift similarity index 99% rename from ImageKit/Util/MimeType.swift rename to Sources/ImageKit/Util/MimeType.swift index d7b9eac..2fde2c8 100644 --- a/ImageKit/Util/MimeType.swift +++ b/Sources/ImageKit/Util/MimeType.swift @@ -7,7 +7,7 @@ import Foundation -public enum FileType { +public enum FileType : Sendable{ case amr case ar case avi @@ -69,7 +69,7 @@ public enum FileType { case zip } -public struct MimeType { +public struct MimeType : Sendable { /// Mime type string representation. For example "application/pdf" public let mime: String @@ -84,7 +84,7 @@ public struct MimeType { fileprivate let bytesCount: Int /// A function to check if the bytes match the `MimeType` specifications. - fileprivate let matches: ([UInt8], MimeDetector) -> Bool + fileprivate let matches: @Sendable ([UInt8], MimeDetector) -> Bool /// Check if the given bytes matches with `MimeType` /// it will check for the `bytes.count` first before delegating the diff --git a/ImageKit/Util/UploadApi.swift b/Sources/ImageKit/Util/UploadApi.swift similarity index 77% rename from ImageKit/Util/UploadApi.swift rename to Sources/ImageKit/Util/UploadApi.swift index 7d0a2cc..ac74e18 100644 --- a/ImageKit/Util/UploadApi.swift +++ b/Sources/ImageKit/Util/UploadApi.swift @@ -8,12 +8,20 @@ import Foundation import OSLog -class UploadAPI: NSObject, URLSessionTaskDelegate { - internal static var baseUrl = "https://upload.imagekit.io" +fileprivate class SendableAny : @unchecked Sendable { + let value: Any + + init(value: Any) { + self.value = value + } +} + +class UploadAPI: NSObject, URLSessionTaskDelegate, @unchecked Sendable { + nonisolated(unsafe) internal static var baseUrl = "https://upload.imagekit.io" internal static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ImageKitIO") internal static func upload( - file: Any, + file: Sendable, token: String, fileName: String, useUniqueFileName: Bool? = nil, @@ -29,10 +37,58 @@ class UploadAPI: NSObject, URLSessionTaskDelegate { overwriteTags: Bool? = nil, overwriteCustomMetadata: Bool? = nil, customMetadata: [String : Any]? = nil, - progressClosure: ((Progress) -> Void)? = nil, + progressClosure: (@Sendable (Progress) -> Void)? = nil, + urlConfiguration: URLSessionConfiguration = URLSessionConfiguration.default, + uploadPolicy: UploadPolicy, + completion: @escaping @Sendable (Result<(HTTPURLResponse?, UploadAPIResponse?), Error>) -> Void, + retryCount: Int = 0 + ) { + uploadWithSendableValues( + file: file, + token: token, + fileName: fileName, + useUniqueFileName: useUniqueFileName, + tags: tags, + folder: folder, + isPrivateFile: isPrivateFile, + customCoordinates: customCoordinates, + responseFields: responseFields, + extensions: extensions?.map { $0.mapValues { SendableAny(value: $0) } }, + webhookUrl: webhookUrl, + overwriteFile: overwriteFile, + overwriteAITags: overwriteAITags, + overwriteTags: overwriteTags, + overwriteCustomMetadata: overwriteCustomMetadata, + customMetadata: customMetadata?.mapValues { SendableAny(value: $0) }, + progressClosure: progressClosure, + urlConfiguration: urlConfiguration, + uploadPolicy: uploadPolicy, + completion: completion, + retryCount: retryCount + 1 + ) + } + + private static func uploadWithSendableValues( + file: Sendable, + token: String, + fileName: String, + useUniqueFileName: Bool? = nil, + tags: String? = nil, + folder: String? = nil, + isPrivateFile: Bool?, + customCoordinates: String? = nil, + responseFields: String? = nil, + extensions: [[String : SendableAny]]? = nil, + webhookUrl: String? = nil, + overwriteFile: Bool? = nil, + overwriteAITags: Bool? = nil, + overwriteTags: Bool? = nil, + overwriteCustomMetadata: Bool? = nil, + customMetadata: [String : SendableAny]? = nil, + progressClosure: (@Sendable (Progress) -> Void)? = nil, urlConfiguration: URLSessionConfiguration = URLSessionConfiguration.default, uploadPolicy: UploadPolicy, - completion: @escaping (Result<(HTTPURLResponse?, UploadAPIResponse?), Error>) -> Void, + completion: @escaping @Sendable (Result<(HTTPURLResponse?, UploadAPIResponse?), Error>) -> Void, retryCount: Int = 0 ) { var request = URLRequest(url: URL(string: "\(baseUrl)/api/v2/files/upload")!) @@ -50,10 +106,12 @@ class UploadAPI: NSObject, URLSessionTaskDelegate { var extData: Data? = nil var metaData: Data? = nil if let extensions = extensions { - extData = try? JSONSerialization.data(withJSONObject: extensions) + let extsObject = extensions.map { $0.mapValues { $0.value } } + extData = try? JSONSerialization.data(withJSONObject: extsObject) } if let customMetadata = customMetadata { - metaData = try? JSONSerialization.data(withJSONObject: customMetadata) + let metaObject = customMetadata.mapValues { $0.value } + metaData = try? JSONSerialization.data(withJSONObject: metaObject) } formData.append(fileData, withName: "file", fileName: fileName, mimeType: file is Data ? mimeType! : "text/plain") formData.append(token.data(using: String.Encoding.utf8)!, withName: "token") @@ -112,6 +170,15 @@ class UploadAPI: NSObject, URLSessionTaskDelegate { tags: tags, folder: folder, isPrivateFile: isPrivateFile, + customCoordinates: customCoordinates, + responseFields: responseFields, + extensions: extensions, + webhookUrl: webhookUrl, + overwriteFile: overwriteFile, + overwriteAITags: overwriteAITags, + overwriteTags: overwriteTags, + overwriteCustomMetadata: overwriteCustomMetadata, + customMetadata: customMetadata, progressClosure: progressClosure, urlConfiguration: urlConfiguration, uploadPolicy: uploadPolicy, @@ -140,6 +207,15 @@ class UploadAPI: NSObject, URLSessionTaskDelegate { tags: tags, folder: folder, isPrivateFile: isPrivateFile, + customCoordinates: customCoordinates, + responseFields: responseFields, + extensions: extensions, + webhookUrl: webhookUrl, + overwriteFile: overwriteFile, + overwriteAITags: overwriteAITags, + overwriteTags: overwriteTags, + overwriteCustomMetadata: overwriteCustomMetadata, + customMetadata: customMetadata, progressClosure: progressClosure, urlConfiguration: urlConfiguration, uploadPolicy: uploadPolicy, @@ -164,6 +240,15 @@ class UploadAPI: NSObject, URLSessionTaskDelegate { tags: tags, folder: folder, isPrivateFile: isPrivateFile, + customCoordinates: customCoordinates, + responseFields: responseFields, + extensions: extensions, + webhookUrl: webhookUrl, + overwriteFile: overwriteFile, + overwriteAITags: overwriteAITags, + overwriteTags: overwriteTags, + overwriteCustomMetadata: overwriteCustomMetadata, + customMetadata: customMetadata, progressClosure: progressClosure, urlConfiguration: urlConfiguration, uploadPolicy: uploadPolicy, @@ -332,7 +417,7 @@ class UploadAPI: NSObject, URLSessionTaskDelegate { } } -class UploadTaskDelegate: NSObject, URLSessionDataDelegate { +class UploadTaskDelegate: NSObject, URLSessionDataDelegate, @unchecked Sendable { var uploadProgress: Progress var uploadProgressHandler: ((Progress) -> Void)? diff --git a/Tests/Tests.swift b/Tests/Tests.swift index d8a8f0b..b68b9ad 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -1,4 +1,6 @@ import Quick +import UIKit +import Foundation import Nimble @testable import ImageKitIO import Mocker @@ -480,7 +482,7 @@ class UnitTestSpec: QuickSpec { it("Background: UIColor.black") { let actual = ImageKit.shared .url(path: "medium_cafe_B1iTdD0C.jpg") - .background(backgroundColor: UIColor.black) + .background(backgroundColor: "000000") .create() expect(actual).to(equal(String(format: "https://ik.imagekit.io/demo/tr:bg-000000/medium_cafe_B1iTdD0C.jpg"))) } @@ -495,7 +497,7 @@ class UnitTestSpec: QuickSpec { it("Border: 5, UIColor.blue") { let actual = ImageKit.shared .url(path: "medium_cafe_B1iTdD0C.jpg") - .border(borderWidth: 5, borderColor: UIColor.blue) + .border(borderWidth: 5, borderColor: "0000FF") .create() expect(actual).to(equal(String(format: "https://ik.imagekit.io/demo/tr:b-5_0000FF/medium_cafe_B1iTdD0C.jpg"))) } @@ -639,14 +641,18 @@ class UnitTestSpec: QuickSpec { describe("Responsive URL loading for UIViews") { it("With default params") { - let view = UIView() - let dpr = String(format: "%.2f", Float(UIScreen.main.scale.rounded(.toNearestOrAwayFromZero))) - view.frame = CGRect(x: 0, y: 0, width: 400, height: 300) - let actual = try! ImageKit.shared - .url(src: "https://ik.imagekit.io/demo/medium_cafe_B1iTdD0C.jpg") - .setResponsive(view: view) - .create() - expect(actual).to(equal(String(format: "https://ik.imagekit.io/demo/medium_cafe_B1iTdD0C.jpg?tr=w-400,h-300,dpr-\(dpr),cm-resize,fo-center"))) + Task.detached(operation: { + await MainActor.run(body: { + let view = UIView() + let dpr = String(format: "%.2f", Float(UIScreen.main.scale.rounded(.toNearestOrAwayFromZero))) + view.frame = CGRect(x: 0, y: 0, width: 400, height: 300) + let actual = try! ImageKit.shared + .url(src: "https://ik.imagekit.io/demo/medium_cafe_B1iTdD0C.jpg") + .setResponsive(view: view) + .create() + expect(actual).to(equal(String(format: "https://ik.imagekit.io/demo/medium_cafe_B1iTdD0C.jpg?tr=w-400,h-300,dpr-\(dpr),cm-resize,fo-center"))) + }) + }) } } } @@ -745,7 +751,7 @@ class MimeDetectorSpec: QuickSpec { for ext in extensions { context("when extension is \(ext)") { it("shoud guess the correct mime type") { - let data = loadFileData(path: "/Tests/fixtures/fixture.\(ext)") + let data = self.loadFileData(path: "/fixture.\(ext)") let mimeType = MimeDetector.mimeType(data: data) if let mime = mimeTypeByExtension[ext] { @@ -781,7 +787,7 @@ class MimeDetectorSpec: QuickSpec { describe("MimeDetector.mimeType(bytes:).type") { context("when file type is image/jpeg") { it("should return true") { - let data: Data = loadFileData(path: "/Tests/fixtures/fixture.jpg") + let data: Data = self.loadFileData(path: "/fixture.jpg") let mimeType = MimeDetector.mimeType(data: data) expect(mimeType?.type) == .jpg @@ -790,7 +796,7 @@ class MimeDetectorSpec: QuickSpec { context("when file type is application/pdf") { it("should return true") { - let data: Data = loadFileData(path: "/Tests/fixtures/fixture.pdf") + let data: Data = self.loadFileData(path: "/fixture.pdf") let mimeType = MimeDetector.mimeType(data: data) expect(mimeType?.type) == .pdf @@ -799,7 +805,7 @@ class MimeDetectorSpec: QuickSpec { context("when file type is not image/jpeg") { it("should return true") { - let data: Data = loadFileData(path: "/Tests/fixtures/fixture.png") + let data: Data = self.loadFileData(path: "/fixture.png") let mimeType = MimeDetector.mimeType(data: data) expect(mimeType?.type) != .jpg @@ -807,15 +813,15 @@ class MimeDetectorSpec: QuickSpec { } } } -} - -func loadFileData(path: String) -> Data { - let projectDir = URL(fileURLWithPath: #file).pathComponents.prefix(while: { $0 != "Tests" }).joined(separator: "/").dropFirst() - print(projectDir) - let absolutePath = "\(projectDir)\(path)" - print(absolutePath) - let url = URL(fileURLWithPath: absolutePath, isDirectory: false) - return try! Data(contentsOf: url) + + func loadFileData(path: String) -> Data { + let projectDir = Bundle(for: MimeDetectorSpec.self).resourceURL?.appendingPathComponent("ImageKitIO_ImageKitIO-Tests.bundle").path ?? "" + print(projectDir) + let absolutePath = "\(projectDir)\(path)" + print(absolutePath) + let url = URL(fileURLWithPath: absolutePath, isDirectory: false) + return try! Data(contentsOf: url) + } } class UploadSpec: QuickSpec { @@ -836,22 +842,30 @@ class UploadSpec: QuickSpec { let sampleMetadata = ["device_name": "Emulator", "uid": 167434] it("Upload From Url") { Mock(url: URL(string: "https://upload.imagekit.io/api/v2/files/upload")!, dataType: .json, statusCode: 200, data: [ - .post : Data(""" - { - "fileId": "5f881125ce8f14336dda25b6", - "name": "default-image-test_1JO5mllWR.jpg", - "size": 146974, - "filePath": "/default-image-test_1JO5mllWR.jpg", - "url": "https://ik.imagekit.io/demo/default-image-test_1JO5mllWR.jpg", - "fileType": "image", - "height": 1000, - "width": 1000, - "thumbnailUrl": "https://ik.imagekit.io/demo/tr:n-media_library_thumbnail/default-image-test_1JO5mllWR.jpg" - } - """.utf8) - ]).register() + .post : Data(""" + { + "fileId": "5f881125ce8f14336dda25b6", + "name": "default-image-test_1JO5mllWR.jpg", + "size": 146974, + "filePath": "/default-image-test_1JO5mllWR.jpg", + "url": "https://ik.imagekit.io/demo/default-image-test_1JO5mllWR.jpg", + "fileType": "image", + "height": 1000, + "width": 1000, + "thumbnailUrl": "https://ik.imagekit.io/demo/tr:n-media_library_thumbnail/default-image-test_1JO5mllWR.jpg" + } + """.utf8) + ]).register() + + let urlConfiguration = self.urlConfiguration - waitUntil(timeout: DispatchTimeInterval.seconds(60)){ done in + Task { @MainActor in + + let sampleExtensions = [ + ["name" : "remove-bg", "options" : ["add_shadow" : true]], + ["name": "google-auto-tagging", "minConfidence": 80, "maxTags": 5] + ] + let sampleMetadata = ["device_name": "Emulator", "uid": 167434] ImageKit.shared.uploader().upload( file: "https://ik.imagekit.io/demo/default-image.jpg", token: "test1", @@ -867,7 +881,7 @@ class UploadSpec: QuickSpec { overwriteTags: true, overwriteCustomMetadata: true, customMetadata: sampleMetadata, - urlConfiguration: self.urlConfiguration, + urlConfiguration: urlConfiguration, completion: { result in switch result{ case .success((_, let uploadAPIResponse)): @@ -882,20 +896,16 @@ class UploadSpec: QuickSpec { expect(uploadAPIResponse.width).to(equal(1000)) expect(uploadAPIResponse.thumbnailUrl).to(equal("https://ik.imagekit.io/demo/tr:n-media_library_thumbnail/default-image-test_1JO5mllWR.jpg")) } - break; - case .failure( _ as UploadAPIError): - fail("Should not throw Error") - break; - case .failure( _): + case .failure(_): fail("Should not throw Error") break; } - done() }) } } it("Upload UIImage") { + Mock(url: URL(string: "https://upload.imagekit.io/api/v2/files/upload")!, dataType: .json, statusCode: 200, data: [ .post : Data(""" { @@ -913,7 +923,13 @@ class UploadSpec: QuickSpec { ]).register() let image = getImageWithColor(color: .red, size: .init(width: 200, height: 200)) - waitUntil(timeout: DispatchTimeInterval.seconds(60)){ done in + let urlConfiguration = self.urlConfiguration + Task { @MainActor in + let sampleExtensions = [ + ["name" : "remove-bg", "options" : ["add_shadow" : true]], + ["name": "google-auto-tagging", "minConfidence": 80, "maxTags": 5] + ] + let sampleMetadata = ["device_name": "Emulator", "uid": 167434] ImageKit.shared.uploader().upload( file: image, token: "test2", @@ -929,7 +945,7 @@ class UploadSpec: QuickSpec { overwriteTags: true, overwriteCustomMetadata: true, customMetadata: sampleMetadata, - urlConfiguration: self.urlConfiguration, + urlConfiguration: urlConfiguration, completion: { result in switch result{ case .success((_, let uploadAPIResponse)): @@ -952,8 +968,7 @@ class UploadSpec: QuickSpec { fail("Should not throw Error") break; } - done() - }) + }) } } it("Upload Data"){ @@ -974,9 +989,15 @@ class UploadSpec: QuickSpec { ]).register() let image = getImageWithColor(color: .red, size: .init(width: 200, height: 200)) - waitUntil(timeout: DispatchTimeInterval.seconds(60)){ done in + let urlConfiguration = self.urlConfiguration + Task { @MainActor in + let sampleExtensions = [ + ["name" : "remove-bg", "options" : ["add_shadow" : true]], + ["name": "google-auto-tagging", "minConfidence": 80, "maxTags": 5] + ] + let sampleMetadata = ["device_name": "Emulator", "uid": 167434] ImageKit.shared.uploader().upload( - file: UIImagePNGRepresentation(image)!, + file: image.pngData()!, token: "test3", fileName: "default-image-test.jpg", tags: ["test", "image",], @@ -990,7 +1011,7 @@ class UploadSpec: QuickSpec { overwriteTags: true, overwriteCustomMetadata: true, customMetadata: sampleMetadata, - urlConfiguration: self.urlConfiguration, + urlConfiguration: urlConfiguration, completion: { result in switch result{ case .success((_, let uploadAPIResponse)): @@ -1013,7 +1034,6 @@ class UploadSpec: QuickSpec { fail("Should not throw Error") break; } - done() }) } } @@ -1144,3 +1164,4 @@ extension UIImage { return UIColor(red: r, green: g, blue: b, alpha: a) } } +