diff --git a/MyApp.xcodeproj/project.pbxproj b/MyApp.xcodeproj/project.pbxproj index 4fb3ac2..97aa17b 100644 --- a/MyApp.xcodeproj/project.pbxproj +++ b/MyApp.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 9A135C032CBE774500808EB3 /* ImageProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A135C022CBE774300808EB3 /* ImageProcessor.swift */; }; 9AAC417C2C74668B00AC0A60 /* MyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AAC417B2C74668B00AC0A60 /* MyApp.swift */; }; 9AAC417E2C74668B00AC0A60 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AAC417D2C74668B00AC0A60 /* ContentView.swift */; }; 9AAC41802C74668C00AC0A60 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9AAC417F2C74668C00AC0A60 /* Assets.xcassets */; }; @@ -15,6 +16,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 9A135C022CBE774300808EB3 /* ImageProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessor.swift; sourceTree = ""; }; 9AAC41782C74668B00AC0A60 /* MyApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9AAC417B2C74668B00AC0A60 /* MyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyApp.swift; sourceTree = ""; }; 9AAC417D2C74668B00AC0A60 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -57,6 +59,7 @@ 9AAC417B2C74668B00AC0A60 /* MyApp.swift */, 9AAC417D2C74668B00AC0A60 /* ContentView.swift */, 9AAC418A2C7466F000AC0A60 /* UIKitViewControllerWrapper.swift */, + 9A135C022CBE774300808EB3 /* ImageProcessor.swift */, 9AAC417F2C74668C00AC0A60 /* Assets.xcassets */, 9AAC41812C74668C00AC0A60 /* MyApp.entitlements */, 9AAC41822C74668C00AC0A60 /* Preview Content */, @@ -100,7 +103,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1540; - LastUpgradeCheck = 1540; + LastUpgradeCheck = 1600; TargetAttributes = { 9AAC41772C74668B00AC0A60 = { CreatedOnToolsVersion = 15.4; @@ -144,6 +147,7 @@ files = ( 9AAC418B2C7466F000AC0A60 /* UIKitViewControllerWrapper.swift in Sources */, 9AAC417E2C74668B00AC0A60 /* ContentView.swift in Sources */, + 9A135C032CBE774500808EB3 /* ImageProcessor.swift in Sources */, 9AAC417C2C74668B00AC0A60 /* MyApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -185,6 +189,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -246,6 +251,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -273,6 +279,7 @@ CODE_SIGN_ENTITLEMENTS = MyApp/MyApp.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"MyApp/Preview Content\""; DEVELOPMENT_TEAM = 99WD9D6Y46; ENABLE_HARDENED_RUNTIME = YES; @@ -311,6 +318,7 @@ CODE_SIGN_ENTITLEMENTS = MyApp/MyApp.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"MyApp/Preview Content\""; DEVELOPMENT_TEAM = 99WD9D6Y46; ENABLE_HARDENED_RUNTIME = YES; diff --git a/MyApp.xcodeproj/project.xcworkspace/xcuserdata/orangecloud.xcuserdatad/UserInterfaceState.xcuserstate b/MyApp.xcodeproj/project.xcworkspace/xcuserdata/orangecloud.xcuserdatad/UserInterfaceState.xcuserstate index e453f94..d27e88a 100644 Binary files a/MyApp.xcodeproj/project.xcworkspace/xcuserdata/orangecloud.xcuserdatad/UserInterfaceState.xcuserstate and b/MyApp.xcodeproj/project.xcworkspace/xcuserdata/orangecloud.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/MyApp/Assets.xcassets/Round_logo.imageset/Contents.json b/MyApp/Assets.xcassets/Round_logo.imageset/Contents.json new file mode 100644 index 0000000..31f3378 --- /dev/null +++ b/MyApp/Assets.xcassets/Round_logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Round_logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MyApp/Assets.xcassets/Round_logo.imageset/Round_logo.png b/MyApp/Assets.xcassets/Round_logo.imageset/Round_logo.png new file mode 100644 index 0000000..40130cd Binary files /dev/null and b/MyApp/Assets.xcassets/Round_logo.imageset/Round_logo.png differ diff --git a/MyApp/ImageProcessor.swift b/MyApp/ImageProcessor.swift new file mode 100644 index 0000000..0e87c32 --- /dev/null +++ b/MyApp/ImageProcessor.swift @@ -0,0 +1,136 @@ +// +// ImageProcessor.swift +// MyApp +// +// Created by Cong Le on 10/15/24. +// + +import UIKit + +/** + Defines the interface for image compression strategies. This protocol ensures that all concrete strategies adhere to a consistent method signature. + */ +protocol ImageCompressionStrategy { + /// Compresses the given image and returns the compressed data or an error. + /// - Parameter image: The `UIImage` to compress. + /// - Returns: A `Result` containing the compressed image data on success, or an `Error` on failure. + func compress(image: UIImage) -> Result +} + +/** + Implements JPEG compression strategy. Conforms to the `ImageCompressionStrategy` protocol. + */ +class JPEGCompression: ImageCompressionStrategy { + + /// Compresses the input image using JPEG compression with a quality of 0.8. + /// - Parameter image: The `UIImage` to be compressed. + /// - Returns: A `Result` containing the compressed JPEG data or an error if compression fails. + func compress(image: UIImage) -> Result { + guard let data = image.jpegData(compressionQuality: 0.8) else { + return .failure(NSError(domain: "JPEGCompression", code: 0, userInfo: [NSLocalizedDescriptionKey: "JPEG compression failed"])) + } + return .success(data) + } +} + +/** + Implements PNG compression strategy. Conforms to the `ImageCompressionStrategy` protocol. + */ +class PNGCompression: ImageCompressionStrategy { + + /// Compresses the input image using PNG compression. + /// - Parameter image: The `UIImage` to be compressed. + /// - Returns: A `Result` containing the compressed PNG data or an error if compression fails. + func compress(image: UIImage) -> Result { + guard let data = image.pngData() else { + return .failure(NSError(domain: "PNGCompression", code: 0, userInfo: [NSLocalizedDescriptionKey: "PNG compression failed"])) + } + return .success(data) + } +} + + + +// MARK: - Context Class +/** + The context class that uses a concrete strategy to compress images. Manages the currently selected compression strategy. + */ +class ImageProcessor { + /// The currently selected image compression strategy. + var compressionStrategy: ImageCompressionStrategy + + /** + Initializes the `ImageProcessor` with a specific compression strategy. + - Parameter compressionStrategy: The initial `ImageCompressionStrategy` to use. + */ + init(compressionStrategy: ImageCompressionStrategy) { + self.compressionStrategy = compressionStrategy + } + + /** + Processes the given image using the currently assigned compression strategy. + - Parameter image: The `UIImage` to process. + - Returns: A `Result` containing the compressed image data or an error if processing fails. + */ + func processImage(image: UIImage) -> Result { + return compressionStrategy.compress(image: image) + } + + /** + Sets a new compression strategy for the image processor. + - Parameter strategy: The new `ImageCompressionStrategy` to use. + */ + func setCompressionStrategy(strategy: ImageCompressionStrategy) { + self.compressionStrategy = strategy + } +} + +/** + Represents the available image compression types. Used for selecting the appropriate strategy. + Conforms to `CaseIterable` and `RawRepresentable` for easy iteration and string conversion. + */ +enum CompressionType: CaseIterable, RawRepresentable { + case jpeg, png + + typealias RawValue = String + + /// The string representation of the compression type. + var rawValue: String { + switch self { + case .jpeg: return "jpeg" + case .png: return "png" + } + } + + /** + Initializes a `CompressionType` from a raw string value. + - Parameter rawValue: The string representation of the compression type. + */ + init?(rawValue: String) { + switch rawValue { + case "jpeg": self = .jpeg + case "png": self = .png + default: return nil + } + } +} + +// MARK: - Strategy Factory (Advanced Usage) +/** + A factory struct responsible for creating concrete `ImageCompressionStrategy` instances based on a given `CompressionType`. +*/ +struct CompressionStrategyFactory { + + /** + Creates and returns an `ImageCompressionStrategy` based on the specified compression type. + - Parameter type: The `CompressionType` indicating the desired strategy. + - Returns: An optional `ImageCompressionStrategy` instance. Returns `nil` if the compression type is not supported. + */ + static func createStrategy(for type: CompressionType) -> ImageCompressionStrategy? { + switch type { + case .jpeg: return JPEGCompression() + case .png: return PNGCompression() + } + } +} + diff --git a/MyApp/UIKitViewControllerWrapper.swift b/MyApp/UIKitViewControllerWrapper.swift index ba247db..5ad47a0 100644 --- a/MyApp/UIKitViewControllerWrapper.swift +++ b/MyApp/UIKitViewControllerWrapper.swift @@ -25,9 +25,59 @@ struct UIKitViewControllerWrapper: UIViewControllerRepresentable { // Example UIKit view controller class MyUIKitViewController: UIViewController { + + override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBlue - // Additional setup + + // Simulate dynamic selection by iterating through all compression types + for compressionType in CompressionType.allCases { + performImageCompression(using: compressionType) + } + } + + // Function to simulate usage of Image Compression Strategy + func performImageCompression(using compressionType: CompressionType) { + // Using the factory to create the selected compression strategy: + guard let strategy = CompressionStrategyFactory.createStrategy(for: compressionType) else { + print("Unsupported compression type selected.") + return + } + let imageProcessor = ImageProcessor(compressionStrategy: strategy) + + // Replace "Round_logo" with the actual image name in your asset catalog + // or use another method to load the UIImage + guard let image = UIImage(named: "Round_logo") else { + print("Failed to load the image.") + return + } + + // Retrieve original image data based on the selected compression type + let originalImageData: Data? + switch compressionType { + case .jpeg: + // Assuming the original image is best represented as JPEG for comparison + originalImageData = image.jpegData(compressionQuality: 1.0) + case .png: + // Assuming the original image is best represented as PNG for comparison + originalImageData = image.pngData() + } + + if let originalData = originalImageData { + print("Compression Type: \(compressionType.rawValue.uppercased())") + print("Original image data size: \(originalData.count) bytes") + } else { + print("Failed to retrieve original image data.") + } + + // Perform compression using the selected strategy + let result = imageProcessor.processImage(image: image) + switch result { + case .success(let compressedData): + print("Compressed image data size: \(compressedData.count) bytes\n") + case .failure(let error): + print("Image compression failed: \(error.localizedDescription)\n") + } } }