├── .gitignore ├── Demo ├── Demo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Demo │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Box.swift │ ├── ContentView.swift │ ├── Demo.entitlements │ ├── DemoApp.swift │ └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── LICENSE ├── Package.swift ├── README.md └── Sources └── ImageMorphing └── MorphingImage.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 455E9DDA28E8CBD70033E264 /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 455E9DD928E8CBD70033E264 /* DemoApp.swift */; }; 11 | 455E9DDC28E8CBD70033E264 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 455E9DDB28E8CBD70033E264 /* ContentView.swift */; }; 12 | 455E9DDE28E8CBD80033E264 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 455E9DDD28E8CBD80033E264 /* Assets.xcassets */; }; 13 | 455E9DE228E8CBD80033E264 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 455E9DE128E8CBD80033E264 /* Preview Assets.xcassets */; }; 14 | 455E9DEB28E8CBF60033E264 /* ImageMorphing in Frameworks */ = {isa = PBXBuildFile; productRef = 455E9DEA28E8CBF60033E264 /* ImageMorphing */; }; 15 | 4561D7A828E8CDE100F90609 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4561D7A728E8CDE100F90609 /* Box.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 455E9DD628E8CBD70033E264 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 455E9DD928E8CBD70033E264 /* DemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoApp.swift; sourceTree = ""; }; 21 | 455E9DDB28E8CBD70033E264 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 22 | 455E9DDD28E8CBD80033E264 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | 455E9DDF28E8CBD80033E264 /* Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Demo.entitlements; sourceTree = ""; }; 24 | 455E9DE128E8CBD80033E264 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 25 | 455E9DE828E8CBEB0033E264 /* ImageMorphing */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = ImageMorphing; path = ..; sourceTree = ""; }; 26 | 4561D7A728E8CDE100F90609 /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 455E9DD328E8CBD70033E264 /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | 455E9DEB28E8CBF60033E264 /* ImageMorphing in Frameworks */, 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | 455E9DCD28E8CBD70033E264 = { 42 | isa = PBXGroup; 43 | children = ( 44 | 455E9DE828E8CBEB0033E264 /* ImageMorphing */, 45 | 455E9DD828E8CBD70033E264 /* Demo */, 46 | 455E9DD728E8CBD70033E264 /* Products */, 47 | 455E9DE928E8CBF60033E264 /* Frameworks */, 48 | ); 49 | sourceTree = ""; 50 | }; 51 | 455E9DD728E8CBD70033E264 /* Products */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | 455E9DD628E8CBD70033E264 /* Demo.app */, 55 | ); 56 | name = Products; 57 | sourceTree = ""; 58 | }; 59 | 455E9DD828E8CBD70033E264 /* Demo */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 455E9DD928E8CBD70033E264 /* DemoApp.swift */, 63 | 455E9DDB28E8CBD70033E264 /* ContentView.swift */, 64 | 4561D7A728E8CDE100F90609 /* Box.swift */, 65 | 455E9DDD28E8CBD80033E264 /* Assets.xcassets */, 66 | 455E9DDF28E8CBD80033E264 /* Demo.entitlements */, 67 | 455E9DE028E8CBD80033E264 /* Preview Content */, 68 | ); 69 | path = Demo; 70 | sourceTree = ""; 71 | }; 72 | 455E9DE028E8CBD80033E264 /* Preview Content */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 455E9DE128E8CBD80033E264 /* Preview Assets.xcassets */, 76 | ); 77 | path = "Preview Content"; 78 | sourceTree = ""; 79 | }; 80 | 455E9DE928E8CBF60033E264 /* Frameworks */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | ); 84 | name = Frameworks; 85 | sourceTree = ""; 86 | }; 87 | /* End PBXGroup section */ 88 | 89 | /* Begin PBXNativeTarget section */ 90 | 455E9DD528E8CBD70033E264 /* Demo */ = { 91 | isa = PBXNativeTarget; 92 | buildConfigurationList = 455E9DE528E8CBD80033E264 /* Build configuration list for PBXNativeTarget "Demo" */; 93 | buildPhases = ( 94 | 455E9DD228E8CBD70033E264 /* Sources */, 95 | 455E9DD328E8CBD70033E264 /* Frameworks */, 96 | 455E9DD428E8CBD70033E264 /* Resources */, 97 | ); 98 | buildRules = ( 99 | ); 100 | dependencies = ( 101 | ); 102 | name = Demo; 103 | packageProductDependencies = ( 104 | 455E9DEA28E8CBF60033E264 /* ImageMorphing */, 105 | ); 106 | productName = Demo; 107 | productReference = 455E9DD628E8CBD70033E264 /* Demo.app */; 108 | productType = "com.apple.product-type.application"; 109 | }; 110 | /* End PBXNativeTarget section */ 111 | 112 | /* Begin PBXProject section */ 113 | 455E9DCE28E8CBD70033E264 /* Project object */ = { 114 | isa = PBXProject; 115 | attributes = { 116 | BuildIndependentTargetsInParallel = 1; 117 | LastSwiftUpdateCheck = 1410; 118 | LastUpgradeCheck = 1410; 119 | TargetAttributes = { 120 | 455E9DD528E8CBD70033E264 = { 121 | CreatedOnToolsVersion = 14.1; 122 | }; 123 | }; 124 | }; 125 | buildConfigurationList = 455E9DD128E8CBD70033E264 /* Build configuration list for PBXProject "Demo" */; 126 | compatibilityVersion = "Xcode 14.0"; 127 | developmentRegion = en; 128 | hasScannedForEncodings = 0; 129 | knownRegions = ( 130 | en, 131 | Base, 132 | ); 133 | mainGroup = 455E9DCD28E8CBD70033E264; 134 | productRefGroup = 455E9DD728E8CBD70033E264 /* Products */; 135 | projectDirPath = ""; 136 | projectRoot = ""; 137 | targets = ( 138 | 455E9DD528E8CBD70033E264 /* Demo */, 139 | ); 140 | }; 141 | /* End PBXProject section */ 142 | 143 | /* Begin PBXResourcesBuildPhase section */ 144 | 455E9DD428E8CBD70033E264 /* Resources */ = { 145 | isa = PBXResourcesBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | 455E9DE228E8CBD80033E264 /* Preview Assets.xcassets in Resources */, 149 | 455E9DDE28E8CBD80033E264 /* Assets.xcassets in Resources */, 150 | ); 151 | runOnlyForDeploymentPostprocessing = 0; 152 | }; 153 | /* End PBXResourcesBuildPhase section */ 154 | 155 | /* Begin PBXSourcesBuildPhase section */ 156 | 455E9DD228E8CBD70033E264 /* Sources */ = { 157 | isa = PBXSourcesBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | 4561D7A828E8CDE100F90609 /* Box.swift in Sources */, 161 | 455E9DDC28E8CBD70033E264 /* ContentView.swift in Sources */, 162 | 455E9DDA28E8CBD70033E264 /* DemoApp.swift in Sources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXSourcesBuildPhase section */ 167 | 168 | /* Begin XCBuildConfiguration section */ 169 | 455E9DE328E8CBD80033E264 /* Debug */ = { 170 | isa = XCBuildConfiguration; 171 | buildSettings = { 172 | ALWAYS_SEARCH_USER_PATHS = NO; 173 | CLANG_ANALYZER_NONNULL = YES; 174 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 175 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 176 | CLANG_ENABLE_MODULES = YES; 177 | CLANG_ENABLE_OBJC_ARC = YES; 178 | CLANG_ENABLE_OBJC_WEAK = YES; 179 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 180 | CLANG_WARN_BOOL_CONVERSION = YES; 181 | CLANG_WARN_COMMA = YES; 182 | CLANG_WARN_CONSTANT_CONVERSION = YES; 183 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 184 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 185 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 186 | CLANG_WARN_EMPTY_BODY = YES; 187 | CLANG_WARN_ENUM_CONVERSION = YES; 188 | CLANG_WARN_INFINITE_RECURSION = YES; 189 | CLANG_WARN_INT_CONVERSION = YES; 190 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 191 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 192 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 193 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 194 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 195 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 196 | CLANG_WARN_STRICT_PROTOTYPES = YES; 197 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 198 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 199 | CLANG_WARN_UNREACHABLE_CODE = YES; 200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 201 | COPY_PHASE_STRIP = NO; 202 | DEBUG_INFORMATION_FORMAT = dwarf; 203 | ENABLE_STRICT_OBJC_MSGSEND = YES; 204 | ENABLE_TESTABILITY = YES; 205 | GCC_C_LANGUAGE_STANDARD = gnu11; 206 | GCC_DYNAMIC_NO_PIC = NO; 207 | GCC_NO_COMMON_BLOCKS = YES; 208 | GCC_OPTIMIZATION_LEVEL = 0; 209 | GCC_PREPROCESSOR_DEFINITIONS = ( 210 | "DEBUG=1", 211 | "$(inherited)", 212 | ); 213 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 214 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 215 | GCC_WARN_UNDECLARED_SELECTOR = YES; 216 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 217 | GCC_WARN_UNUSED_FUNCTION = YES; 218 | GCC_WARN_UNUSED_VARIABLE = YES; 219 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 220 | MTL_FAST_MATH = YES; 221 | ONLY_ACTIVE_ARCH = YES; 222 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 223 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 224 | }; 225 | name = Debug; 226 | }; 227 | 455E9DE428E8CBD80033E264 /* Release */ = { 228 | isa = XCBuildConfiguration; 229 | buildSettings = { 230 | ALWAYS_SEARCH_USER_PATHS = NO; 231 | CLANG_ANALYZER_NONNULL = YES; 232 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 234 | CLANG_ENABLE_MODULES = YES; 235 | CLANG_ENABLE_OBJC_ARC = YES; 236 | CLANG_ENABLE_OBJC_WEAK = YES; 237 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 238 | CLANG_WARN_BOOL_CONVERSION = YES; 239 | CLANG_WARN_COMMA = YES; 240 | CLANG_WARN_CONSTANT_CONVERSION = YES; 241 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 243 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 244 | CLANG_WARN_EMPTY_BODY = YES; 245 | CLANG_WARN_ENUM_CONVERSION = YES; 246 | CLANG_WARN_INFINITE_RECURSION = YES; 247 | CLANG_WARN_INT_CONVERSION = YES; 248 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 249 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 250 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 251 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 252 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 253 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 254 | CLANG_WARN_STRICT_PROTOTYPES = YES; 255 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 256 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 257 | CLANG_WARN_UNREACHABLE_CODE = YES; 258 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 259 | COPY_PHASE_STRIP = NO; 260 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 261 | ENABLE_NS_ASSERTIONS = NO; 262 | ENABLE_STRICT_OBJC_MSGSEND = YES; 263 | GCC_C_LANGUAGE_STANDARD = gnu11; 264 | GCC_NO_COMMON_BLOCKS = YES; 265 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 266 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 267 | GCC_WARN_UNDECLARED_SELECTOR = YES; 268 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 269 | GCC_WARN_UNUSED_FUNCTION = YES; 270 | GCC_WARN_UNUSED_VARIABLE = YES; 271 | MTL_ENABLE_DEBUG_INFO = NO; 272 | MTL_FAST_MATH = YES; 273 | SWIFT_COMPILATION_MODE = wholemodule; 274 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 275 | }; 276 | name = Release; 277 | }; 278 | 455E9DE628E8CBD80033E264 /* Debug */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 282 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 283 | CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements; 284 | CODE_SIGN_STYLE = Automatic; 285 | CURRENT_PROJECT_VERSION = 1; 286 | DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\""; 287 | ENABLE_HARDENED_RUNTIME = YES; 288 | ENABLE_PREVIEWS = YES; 289 | GENERATE_INFOPLIST_FILE = YES; 290 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 291 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 292 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 293 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 294 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 295 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 296 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 297 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 298 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 299 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 300 | IPHONEOS_DEPLOYMENT_TARGET = 15.1; 301 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 302 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 303 | MACOSX_DEPLOYMENT_TARGET = 13.0; 304 | MARKETING_VERSION = 1.0; 305 | PRODUCT_BUNDLE_IDENTIFIER = com.kodlian.ImageMorphingDemo; 306 | PRODUCT_NAME = "$(TARGET_NAME)"; 307 | SDKROOT = auto; 308 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; 309 | SWIFT_EMIT_LOC_STRINGS = YES; 310 | SWIFT_VERSION = 5.0; 311 | TARGETED_DEVICE_FAMILY = "1,2"; 312 | }; 313 | name = Debug; 314 | }; 315 | 455E9DE728E8CBD80033E264 /* Release */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 319 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 320 | CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements; 321 | CODE_SIGN_STYLE = Automatic; 322 | CURRENT_PROJECT_VERSION = 1; 323 | DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\""; 324 | ENABLE_HARDENED_RUNTIME = YES; 325 | ENABLE_PREVIEWS = YES; 326 | GENERATE_INFOPLIST_FILE = YES; 327 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 328 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 329 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 330 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 331 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 332 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 333 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 334 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 335 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 336 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 337 | IPHONEOS_DEPLOYMENT_TARGET = 15.1; 338 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 339 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 340 | MACOSX_DEPLOYMENT_TARGET = 13.0; 341 | MARKETING_VERSION = 1.0; 342 | PRODUCT_BUNDLE_IDENTIFIER = com.kodlian.ImageMorphingDemo; 343 | PRODUCT_NAME = "$(TARGET_NAME)"; 344 | SDKROOT = auto; 345 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; 346 | SWIFT_EMIT_LOC_STRINGS = YES; 347 | SWIFT_VERSION = 5.0; 348 | TARGETED_DEVICE_FAMILY = "1,2"; 349 | }; 350 | name = Release; 351 | }; 352 | /* End XCBuildConfiguration section */ 353 | 354 | /* Begin XCConfigurationList section */ 355 | 455E9DD128E8CBD70033E264 /* Build configuration list for PBXProject "Demo" */ = { 356 | isa = XCConfigurationList; 357 | buildConfigurations = ( 358 | 455E9DE328E8CBD80033E264 /* Debug */, 359 | 455E9DE428E8CBD80033E264 /* Release */, 360 | ); 361 | defaultConfigurationIsVisible = 0; 362 | defaultConfigurationName = Release; 363 | }; 364 | 455E9DE528E8CBD80033E264 /* Build configuration list for PBXNativeTarget "Demo" */ = { 365 | isa = XCConfigurationList; 366 | buildConfigurations = ( 367 | 455E9DE628E8CBD80033E264 /* Debug */, 368 | 455E9DE728E8CBD80033E264 /* Release */, 369 | ); 370 | defaultConfigurationIsVisible = 0; 371 | defaultConfigurationName = Release; 372 | }; 373 | /* End XCConfigurationList section */ 374 | 375 | /* Begin XCSwiftPackageProductDependency section */ 376 | 455E9DEA28E8CBF60033E264 /* ImageMorphing */ = { 377 | isa = XCSwiftPackageProductDependency; 378 | productName = ImageMorphing; 379 | }; 380 | /* End XCSwiftPackageProductDependency section */ 381 | }; 382 | rootObject = 455E9DCE28E8CBD70033E264 /* Project object */; 383 | } 384 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "1x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "2x", 16 | "size" : "16x16" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "1x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "2x", 26 | "size" : "32x32" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "2x", 36 | "size" : "128x128" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "1x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "2x", 46 | "size" : "256x256" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "1x", 51 | "size" : "512x512" 52 | }, 53 | { 54 | "idiom" : "mac", 55 | "scale" : "2x", 56 | "size" : "512x512" 57 | } 58 | ], 59 | "info" : { 60 | "author" : "xcode", 61 | "version" : 1 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo/Box.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Box.swift 3 | // Demo 4 | // 5 | // Created by Jeremy Marchand on 01/10/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct Box: View { 11 | let title: String 12 | let subtitle: String 13 | 14 | 15 | @ViewBuilder 16 | let content: () -> Content 17 | 18 | var headerText: Text { 19 | Text(title) 20 | .font(.title.weight(.semibold)) 21 | + Text("\n") 22 | + Text(subtitle) 23 | .font(.subheadline) 24 | } 25 | 26 | var body: some View { 27 | VStack { 28 | headerText 29 | .fixedSize() 30 | .multilineTextAlignment(.center) 31 | Divider() 32 | content() 33 | .padding(.top) 34 | .shadow(color: .white, radius: 4) 35 | .shadow(color: .purple.opacity(0.45), radius: 30) 36 | } 37 | .padding(30) 38 | .background(background) 39 | .padding(.horizontal, 30) 40 | .padding(.vertical, 15) 41 | } 42 | 43 | var background: some View { 44 | RoundedRectangle(cornerRadius: 30, style: .continuous) 45 | .foregroundColor(.white) 46 | .shadow(color: .black.opacity(0.10), radius: 30) 47 | } 48 | } 49 | 50 | struct Box_Previews: PreviewProvider { 51 | static var previews: some View { 52 | Box(title: "title", subtitle: "subtile") { 53 | Text("Box Content") 54 | }.padding(30) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Demo/Demo/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Demo 4 | // 5 | // Created by Jeremy Marchand on 01/10/2022. 6 | // 7 | 8 | import SwiftUI 9 | import ImageMorphing 10 | 11 | struct ContentView: View { 12 | let names = [ 13 | "circle.fill", 14 | "heart.fill", 15 | "star.fill", 16 | "bell.fill", 17 | "bookmark.fill", 18 | "tag.fill", 19 | "bolt.fill", 20 | "play.fill", 21 | "pause.fill", 22 | "squareshape.fill", 23 | "key.fill", 24 | "hexagon.fill", 25 | "gearshape.fill", 26 | ] 27 | 28 | @State 29 | var index = 0 30 | var nextIndex: Int { 31 | return (index + 1) % names.count 32 | } 33 | 34 | let gradient = Gradient(colors: [.purple, .red]) 35 | 36 | var body: some View { 37 | ScrollView { 38 | Box(title: "It is morphing time!", subtitle: "Demo") { 39 | demo 40 | } 41 | Box(title: "How to use?", subtitle: "100% SwiftUI") { 42 | howTo 43 | } 44 | } 45 | .foregroundStyle( 46 | .linearGradient(gradient, startPoint: .top, endPoint: .bottom) 47 | ) 48 | .frame(maxWidth: .infinity, maxHeight: .infinity) 49 | } 50 | 51 | @ViewBuilder 52 | var demo: some View { 53 | MorphingImage(systemName: names[index]) 54 | .frame(width: 128, height: 128) 55 | .padding() 56 | Button { 57 | index = nextIndex 58 | } label: { 59 | Label { 60 | Text("Next") 61 | .fontWeight(.semibold) 62 | } icon: { 63 | MorphingImage(systemName: names[nextIndex]) 64 | .frame(width: 16, height: 16) 65 | } 66 | } 67 | .foregroundColor(.white) 68 | .buttonStyle(.borderedProminent) 69 | .buttonBorderShape(.capsule) 70 | .accentColor(.purple) 71 | .multilineTextAlignment(.center) 72 | } 73 | 74 | @ViewBuilder 75 | var howTo: some View { 76 | Text(""" 77 | **MorphingImage("MyImage")** 78 | .frame(width: 64, height: 64) 79 | 80 | **MorphingImage(systemName: "star.fill")** 81 | .frame(width: 64, height: 64) 82 | """).font(.caption) 83 | } 84 | } 85 | 86 | 87 | struct ContentView_Previews: PreviewProvider { 88 | static var previews: some View { 89 | ContentView() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Demo/Demo/Demo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/Demo/DemoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoApp.swift 3 | // Demo 4 | // 5 | // Created by Jeremy Marchand on 01/10/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct DemoApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Demo/Demo/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jérémy Marchand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ImageMorphing", 8 | platforms: [.iOS(.v15), .macOS(.v12), .tvOS(.v15)], 9 | products: [ 10 | .library( 11 | name: "ImageMorphing", 12 | targets: ["ImageMorphing"]), 13 | ], 14 | targets: [ 15 | .target( 16 | name: "ImageMorphing", 17 | dependencies: []) 18 | ] 19 | ) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageMorphing 2 | 3 | Apply morphing animation to images. 4 | 5 | Implementation is using 100% SwiftUI API by leveraging SwiftUI animation and Canvas. 6 | 7 | https://user-images.githubusercontent.com/4249097/193521856-17ec03ea-4a33-481d-808b-0c21d142509b.mp4 8 | 9 | ## Adding Image Morphing as a Dependency 10 | Add package to XCode or in a `Package.swift` 11 | 12 | ```swift 13 | .package(url: "https://github.com/kodlian/ImageMorphing", from: "1.0.0"), 14 | ``` 15 | 16 | ## Usage 17 | 18 | Use MorphingImage as you will do with standard Image: 19 | 20 | ```swift 21 | MorphingImage("MyImage") // custom asset 22 | .frame(width: 64, height: 64) 23 | 24 | MorphingImage(systemName: "heart.fill") // SF Symbol 25 | .frame(width: 64, height: 64) 26 | ``` 27 | 28 | When the image changes, a morphing animation will be performed between the old and new images. The base images are only used as template, the resulting image applies the foreground style currently defined in the context. 29 | 30 | Duration of morphing animation can be customized through a modifier: 31 | 32 | ```swift 33 | MorphingImage(..) 34 | .morphingImageDuration(1.5) 35 | ``` 36 | -------------------------------------------------------------------------------- /Sources/ImageMorphing/MorphingImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MorphingImage.swift 3 | // Morphing 4 | // 5 | // Created by Jeremy Marchand on 01/10/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | /// A Morphing Image that animates when image change. 11 | /// 12 | /// When images change, an animation will be performed to morph previous image to the new image. It uses a combinaison of a blur and an alpha threshold effects. 13 | /// 14 | /// The morphing image uses a base Image as a the template for the resulting image and applies the current foreground style. 15 | /// 16 | /// The animation duration is by default set to 1 second but can be customized through the modifier ``morphingImageDuration(_:)`` 17 | /// 18 | /// The size of the morphing image is inherited from the container and not the template image. 19 | /// - Parameter duration: Duration of the morphing animate. 20 | public struct MorphingImage: View { 21 | @Environment(\.morphingImageDuration) 22 | var duration 23 | 24 | /// Creates a morphing image that you can use as symbol for controls. 25 | /// 26 | /// - Parameters: 27 | /// - name: The name of the image resource to lookup. 28 | /// - bundle: The bundle to search for the image resource. 29 | /// If `nil`, SwiftUI uses the main `Bundle`. Defaults to `nil`. 30 | public init(_ name: String, bundle: Bundle? = nil) { 31 | self.image = Image(name, bundle: bundle) 32 | self.name = name 33 | } 34 | 35 | /// Creates a system symbol morphing image. 36 | /// 37 | /// This initializer creates an image using a system-provided symbol. Use 38 | /// [SF Symbols](https://developer.apple.com/design/resources/#sf-symbols) 39 | /// to find symbols and their corresponding names. 40 | /// 41 | /// - Parameters: 42 | /// - systemName: The name of the system symbol image. 43 | /// Use the SF Symbols app to look up the names of system symbol images. 44 | public init(systemName: String) { 45 | self.image = Image(systemName: systemName) 46 | self.name = systemName 47 | } 48 | 49 | let image: Image 50 | let name: String 51 | 52 | @State 53 | private var blurRadius: Double = 0 54 | 55 | @State 56 | private var currentTask: Task? 57 | 58 | public var body: some View { 59 | GeometryReader { reader in 60 | // Adapt blur radius to the size. 61 | let size = max(reader.size.width, reader.size.height) 62 | let blurRadius = min(size * 0.05, 20) 63 | 64 | Canvas { context, size in 65 | context.clipToLayer { context in 66 | context.addFilter(.alphaThreshold(min: 0.5)) 67 | context.drawLayer { context in 68 | let view = context.resolveSymbol(id: 0)! 69 | context.draw(view, at: CGPoint(x: size.width / 2, y: size.height / 2)) 70 | } 71 | } 72 | context.fill(Path(CGRect(origin: .zero, size: size)), with: .foreground) 73 | } symbols: { 74 | symbol(forSize: reader.size).tag(0) 75 | } 76 | .onChange(of: image) { _ in 77 | currentTask?.cancel() 78 | currentTask = Task { 79 | let halfDuration = duration / 2 80 | withAnimation(.easeIn(duration: halfDuration)) { 81 | self.blurRadius = blurRadius 82 | } 83 | try await Task.sleep(nanoseconds: UInt64(Int64(duration * 500_000_000))) 84 | withAnimation(.easeOut(duration: halfDuration)) { 85 | self.blurRadius = 0 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | private func symbol(forSize size: CGSize) -> some View { 93 | ZStack { 94 | image 95 | .resizable() 96 | .aspectRatio(contentMode: .fit) 97 | .frame(width: size.width, height: size.height) 98 | .id(name) 99 | } 100 | .animation(.easeInOut(duration: duration), value: name) 101 | .blur(radius: self.blurRadius) 102 | } 103 | } 104 | 105 | // MARK: - Environment 106 | private struct MorphingDurationKey: EnvironmentKey { 107 | static var defaultValue: Double = 1 108 | } 109 | 110 | private extension EnvironmentValues { 111 | var morphingImageDuration: Double { 112 | get { 113 | self[MorphingDurationKey.self] 114 | } set { 115 | self[MorphingDurationKey.self] = newValue 116 | } 117 | } 118 | } 119 | 120 | public extension View { 121 | func morphingImageDuration(_ value: Double) -> some View { 122 | self.environment(\.morphingImageDuration, value) 123 | } 124 | } 125 | 126 | // MARK: - Preview 127 | struct MorphingImage_Previews: PreviewProvider { 128 | static let names = [ 129 | "circle.fill", 130 | "heart.fill", 131 | "star.fill", 132 | "bell.fill", 133 | "bookmark.fill", 134 | "tag.fill", 135 | "bolt.fill", 136 | "play.fill", 137 | "pause.fill", 138 | "squareshape.fill", 139 | "key.fill", 140 | "hexagon.fill", 141 | "gearshape.fill", 142 | ] 143 | 144 | static var previews: some View { 145 | TimelineView(.animation(minimumInterval: 2)) { context in 146 | MorphingImage(systemName: names.randomElement()!) 147 | } 148 | .foregroundColor(.accentColor) 149 | .frame(width: 128, height: 128) 150 | .previewLayout(.fixed(width: 128, height: 128)) 151 | } 152 | } 153 | --------------------------------------------------------------------------------