├── AnimationPrincipleDemo ├── AnimationPrincipleDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── HsinYuTang.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── HsinYuTang.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── AnimationPrincipleDemo │ ├── Animation.swift │ ├── AnimationPrinciples.swift │ ├── Animations │ ├── Action.swift │ ├── CloningAction.swift │ ├── DimensionalityAction.swift │ ├── DollyAndZoomAction.swift │ ├── EasingAction.swift │ ├── MaskingAction.swift │ ├── ObscurationAction.swift │ ├── OffsetAndDelayAction.swift │ ├── OverlayAction.swift │ ├── ParallaxAction.swift │ ├── ParentingAction.swift │ ├── TransformationAction.swift │ └── ValueChangeAction.swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── README.md └── ScreenRecording ├── Cloning.gif ├── Dimensionality.gif ├── DollyAndZoom.gif ├── Easing.gif ├── Masking.gif ├── Obscuration.gif ├── OffsetAndDelay.gif ├── Overlay.gif ├── Parallax.gif ├── Parenting.gif ├── Transformation.gif └── ValueChange.gif /AnimationPrincipleDemo/AnimationPrincipleDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 431F41B1204CD297002E9946 /* OverlayAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431F41B0204CD297002E9946 /* OverlayAction.swift */; }; 11 | 431F41B3204CD3AD002E9946 /* CloningAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431F41B2204CD3AD002E9946 /* CloningAction.swift */; }; 12 | 431F41D3204E8E06002E9946 /* ParallaxAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431F41D2204E8E06002E9946 /* ParallaxAction.swift */; }; 13 | 431F41D5204E981C002E9946 /* ObscurationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431F41D4204E981C002E9946 /* ObscurationAction.swift */; }; 14 | 431F41D7204E9B57002E9946 /* DimensionalityAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431F41D6204E9B57002E9946 /* DimensionalityAction.swift */; }; 15 | 431F41D9204F9A3D002E9946 /* DollyAndZoomAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431F41D8204F9A3D002E9946 /* DollyAndZoomAction.swift */; }; 16 | 438FDFDF203DA5C900F8D1EF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438FDFDE203DA5C900F8D1EF /* AppDelegate.swift */; }; 17 | 438FDFE1203DA5C900F8D1EF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438FDFE0203DA5C900F8D1EF /* ViewController.swift */; }; 18 | 438FDFE6203DA5C900F8D1EF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 438FDFE5203DA5C900F8D1EF /* Assets.xcassets */; }; 19 | 438FDFE9203DA5C900F8D1EF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 438FDFE7203DA5C900F8D1EF /* LaunchScreen.storyboard */; }; 20 | 438FDFF1203DAA9600F8D1EF /* AnimationPrinciples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438FDFF0203DAA9600F8D1EF /* AnimationPrinciples.swift */; }; 21 | 438FDFF3203DAE6300F8D1EF /* Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438FDFF2203DAE6300F8D1EF /* Animation.swift */; }; 22 | 438FDFF8203E56C900F8D1EF /* EasingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438FDFF7203E56C900F8D1EF /* EasingAction.swift */; }; 23 | 438FDFFA203E62FF00F8D1EF /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438FDFF9203E62FF00F8D1EF /* Action.swift */; }; 24 | 438FDFFD203EBF7600F8D1EF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 438FDFFB203EBF7600F8D1EF /* Main.storyboard */; }; 25 | 438FDFFF203EC1E800F8D1EF /* OffsetAndDelayAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438FDFFE203EC1E800F8D1EF /* OffsetAndDelayAction.swift */; }; 26 | 438FE001203F193D00F8D1EF /* ParentingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438FE000203F193D00F8D1EF /* ParentingAction.swift */; }; 27 | 438FE003203FBAC900F8D1EF /* TransformationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438FE002203FBAC900F8D1EF /* TransformationAction.swift */; }; 28 | 438FE005203FC01A00F8D1EF /* ValueChangeAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438FE004203FC01A00F8D1EF /* ValueChangeAction.swift */; }; 29 | 438FE00720403CDF00F8D1EF /* MaskingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438FE00620403CDF00F8D1EF /* MaskingAction.swift */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 431F41B0204CD297002E9946 /* OverlayAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayAction.swift; sourceTree = ""; }; 34 | 431F41B2204CD3AD002E9946 /* CloningAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloningAction.swift; sourceTree = ""; }; 35 | 431F41D2204E8E06002E9946 /* ParallaxAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParallaxAction.swift; sourceTree = ""; }; 36 | 431F41D4204E981C002E9946 /* ObscurationAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObscurationAction.swift; sourceTree = ""; }; 37 | 431F41D6204E9B57002E9946 /* DimensionalityAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DimensionalityAction.swift; sourceTree = ""; }; 38 | 431F41D8204F9A3D002E9946 /* DollyAndZoomAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DollyAndZoomAction.swift; sourceTree = ""; }; 39 | 438FDFDB203DA5C900F8D1EF /* AnimationPrincipleDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AnimationPrincipleDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 438FDFDE203DA5C900F8D1EF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41 | 438FDFE0203DA5C900F8D1EF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 42 | 438FDFE5203DA5C900F8D1EF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 438FDFE8203DA5C900F8D1EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 438FDFEA203DA5C900F8D1EF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | 438FDFF0203DAA9600F8D1EF /* AnimationPrinciples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationPrinciples.swift; sourceTree = ""; }; 46 | 438FDFF2203DAE6300F8D1EF /* Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Animation.swift; sourceTree = ""; }; 47 | 438FDFF7203E56C900F8D1EF /* EasingAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EasingAction.swift; sourceTree = ""; }; 48 | 438FDFF9203E62FF00F8D1EF /* Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; 49 | 438FDFFC203EBF7600F8D1EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = AnimationPrincipleDemo/Base.lproj/Main.storyboard; sourceTree = SOURCE_ROOT; }; 50 | 438FDFFE203EC1E800F8D1EF /* OffsetAndDelayAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetAndDelayAction.swift; sourceTree = ""; }; 51 | 438FE000203F193D00F8D1EF /* ParentingAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParentingAction.swift; sourceTree = ""; }; 52 | 438FE002203FBAC900F8D1EF /* TransformationAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransformationAction.swift; sourceTree = ""; }; 53 | 438FE004203FC01A00F8D1EF /* ValueChangeAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueChangeAction.swift; sourceTree = ""; }; 54 | 438FE00620403CDF00F8D1EF /* MaskingAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaskingAction.swift; sourceTree = ""; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | 438FDFD8203DA5C900F8D1EF /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 438FDFD2203DA5C900F8D1EF = { 69 | isa = PBXGroup; 70 | children = ( 71 | 438FDFDD203DA5C900F8D1EF /* AnimationPrincipleDemo */, 72 | 438FDFDC203DA5C900F8D1EF /* Products */, 73 | ); 74 | sourceTree = ""; 75 | }; 76 | 438FDFDC203DA5C900F8D1EF /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 438FDFDB203DA5C900F8D1EF /* AnimationPrincipleDemo.app */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | 438FDFDD203DA5C900F8D1EF /* AnimationPrincipleDemo */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 438FDFDE203DA5C900F8D1EF /* AppDelegate.swift */, 88 | 438FDFE0203DA5C900F8D1EF /* ViewController.swift */, 89 | 438FDFF2203DAE6300F8D1EF /* Animation.swift */, 90 | 438FDFF0203DAA9600F8D1EF /* AnimationPrinciples.swift */, 91 | 438FDFF6203E569C00F8D1EF /* Animations */, 92 | 438FDFE5203DA5C900F8D1EF /* Assets.xcassets */, 93 | 438FDFE7203DA5C900F8D1EF /* LaunchScreen.storyboard */, 94 | 438FDFEA203DA5C900F8D1EF /* Info.plist */, 95 | ); 96 | path = AnimationPrincipleDemo; 97 | sourceTree = ""; 98 | }; 99 | 438FDFF6203E569C00F8D1EF /* Animations */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 438FDFF9203E62FF00F8D1EF /* Action.swift */, 103 | 438FDFF7203E56C900F8D1EF /* EasingAction.swift */, 104 | 438FDFFE203EC1E800F8D1EF /* OffsetAndDelayAction.swift */, 105 | 438FE000203F193D00F8D1EF /* ParentingAction.swift */, 106 | 438FE002203FBAC900F8D1EF /* TransformationAction.swift */, 107 | 438FE004203FC01A00F8D1EF /* ValueChangeAction.swift */, 108 | 438FE00620403CDF00F8D1EF /* MaskingAction.swift */, 109 | 431F41B0204CD297002E9946 /* OverlayAction.swift */, 110 | 431F41B2204CD3AD002E9946 /* CloningAction.swift */, 111 | 431F41D2204E8E06002E9946 /* ParallaxAction.swift */, 112 | 431F41D4204E981C002E9946 /* ObscurationAction.swift */, 113 | 431F41D6204E9B57002E9946 /* DimensionalityAction.swift */, 114 | 431F41D8204F9A3D002E9946 /* DollyAndZoomAction.swift */, 115 | 438FDFFB203EBF7600F8D1EF /* Main.storyboard */, 116 | ); 117 | path = Animations; 118 | sourceTree = ""; 119 | }; 120 | /* End PBXGroup section */ 121 | 122 | /* Begin PBXNativeTarget section */ 123 | 438FDFDA203DA5C900F8D1EF /* AnimationPrincipleDemo */ = { 124 | isa = PBXNativeTarget; 125 | buildConfigurationList = 438FDFED203DA5C900F8D1EF /* Build configuration list for PBXNativeTarget "AnimationPrincipleDemo" */; 126 | buildPhases = ( 127 | 438FDFD7203DA5C900F8D1EF /* Sources */, 128 | 438FDFD8203DA5C900F8D1EF /* Frameworks */, 129 | 438FDFD9203DA5C900F8D1EF /* Resources */, 130 | ); 131 | buildRules = ( 132 | ); 133 | dependencies = ( 134 | ); 135 | name = AnimationPrincipleDemo; 136 | productName = AnimationPrincipleDemo; 137 | productReference = 438FDFDB203DA5C900F8D1EF /* AnimationPrincipleDemo.app */; 138 | productType = "com.apple.product-type.application"; 139 | }; 140 | /* End PBXNativeTarget section */ 141 | 142 | /* Begin PBXProject section */ 143 | 438FDFD3203DA5C900F8D1EF /* Project object */ = { 144 | isa = PBXProject; 145 | attributes = { 146 | LastSwiftUpdateCheck = 0920; 147 | LastUpgradeCheck = 0920; 148 | ORGANIZATIONNAME = "Hsin-Yu Tang"; 149 | TargetAttributes = { 150 | 438FDFDA203DA5C900F8D1EF = { 151 | CreatedOnToolsVersion = 9.2; 152 | ProvisioningStyle = Automatic; 153 | }; 154 | }; 155 | }; 156 | buildConfigurationList = 438FDFD6203DA5C900F8D1EF /* Build configuration list for PBXProject "AnimationPrincipleDemo" */; 157 | compatibilityVersion = "Xcode 8.0"; 158 | developmentRegion = en; 159 | hasScannedForEncodings = 0; 160 | knownRegions = ( 161 | en, 162 | Base, 163 | ); 164 | mainGroup = 438FDFD2203DA5C900F8D1EF; 165 | productRefGroup = 438FDFDC203DA5C900F8D1EF /* Products */; 166 | projectDirPath = ""; 167 | projectRoot = ""; 168 | targets = ( 169 | 438FDFDA203DA5C900F8D1EF /* AnimationPrincipleDemo */, 170 | ); 171 | }; 172 | /* End PBXProject section */ 173 | 174 | /* Begin PBXResourcesBuildPhase section */ 175 | 438FDFD9203DA5C900F8D1EF /* Resources */ = { 176 | isa = PBXResourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 438FDFFD203EBF7600F8D1EF /* Main.storyboard in Resources */, 180 | 438FDFE9203DA5C900F8D1EF /* LaunchScreen.storyboard in Resources */, 181 | 438FDFE6203DA5C900F8D1EF /* Assets.xcassets in Resources */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXResourcesBuildPhase section */ 186 | 187 | /* Begin PBXSourcesBuildPhase section */ 188 | 438FDFD7203DA5C900F8D1EF /* Sources */ = { 189 | isa = PBXSourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | 431F41D5204E981C002E9946 /* ObscurationAction.swift in Sources */, 193 | 431F41D3204E8E06002E9946 /* ParallaxAction.swift in Sources */, 194 | 438FE003203FBAC900F8D1EF /* TransformationAction.swift in Sources */, 195 | 431F41B3204CD3AD002E9946 /* CloningAction.swift in Sources */, 196 | 438FDFE1203DA5C900F8D1EF /* ViewController.swift in Sources */, 197 | 431F41B1204CD297002E9946 /* OverlayAction.swift in Sources */, 198 | 438FDFFF203EC1E800F8D1EF /* OffsetAndDelayAction.swift in Sources */, 199 | 438FDFF1203DAA9600F8D1EF /* AnimationPrinciples.swift in Sources */, 200 | 438FDFF3203DAE6300F8D1EF /* Animation.swift in Sources */, 201 | 431F41D9204F9A3D002E9946 /* DollyAndZoomAction.swift in Sources */, 202 | 438FDFFA203E62FF00F8D1EF /* Action.swift in Sources */, 203 | 438FDFDF203DA5C900F8D1EF /* AppDelegate.swift in Sources */, 204 | 438FE001203F193D00F8D1EF /* ParentingAction.swift in Sources */, 205 | 431F41D7204E9B57002E9946 /* DimensionalityAction.swift in Sources */, 206 | 438FE00720403CDF00F8D1EF /* MaskingAction.swift in Sources */, 207 | 438FDFF8203E56C900F8D1EF /* EasingAction.swift in Sources */, 208 | 438FE005203FC01A00F8D1EF /* ValueChangeAction.swift in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 438FDFE7203DA5C900F8D1EF /* LaunchScreen.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 438FDFE8203DA5C900F8D1EF /* Base */, 219 | ); 220 | name = LaunchScreen.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 438FDFFB203EBF7600F8D1EF /* Main.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 438FDFFC203EBF7600F8D1EF /* Base */, 227 | ); 228 | name = Main.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 438FDFEB203DA5C900F8D1EF /* Debug */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 240 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 241 | CLANG_CXX_LIBRARY = "libc++"; 242 | CLANG_ENABLE_MODULES = YES; 243 | CLANG_ENABLE_OBJC_ARC = YES; 244 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 245 | CLANG_WARN_BOOL_CONVERSION = YES; 246 | CLANG_WARN_COMMA = YES; 247 | CLANG_WARN_CONSTANT_CONVERSION = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 250 | CLANG_WARN_EMPTY_BODY = YES; 251 | CLANG_WARN_ENUM_CONVERSION = YES; 252 | CLANG_WARN_INFINITE_RECURSION = YES; 253 | CLANG_WARN_INT_CONVERSION = YES; 254 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 261 | CLANG_WARN_UNREACHABLE_CODE = YES; 262 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 263 | CODE_SIGN_IDENTITY = "iPhone Developer"; 264 | COPY_PHASE_STRIP = NO; 265 | DEBUG_INFORMATION_FORMAT = dwarf; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | ENABLE_TESTABILITY = YES; 268 | GCC_C_LANGUAGE_STANDARD = gnu11; 269 | GCC_DYNAMIC_NO_PIC = NO; 270 | GCC_NO_COMMON_BLOCKS = YES; 271 | GCC_OPTIMIZATION_LEVEL = 0; 272 | GCC_PREPROCESSOR_DEFINITIONS = ( 273 | "DEBUG=1", 274 | "$(inherited)", 275 | ); 276 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 277 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 278 | GCC_WARN_UNDECLARED_SELECTOR = YES; 279 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 280 | GCC_WARN_UNUSED_FUNCTION = YES; 281 | GCC_WARN_UNUSED_VARIABLE = YES; 282 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 283 | MTL_ENABLE_DEBUG_INFO = YES; 284 | ONLY_ACTIVE_ARCH = YES; 285 | SDKROOT = iphoneos; 286 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 287 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 288 | }; 289 | name = Debug; 290 | }; 291 | 438FDFEC203DA5C900F8D1EF /* Release */ = { 292 | isa = XCBuildConfiguration; 293 | buildSettings = { 294 | ALWAYS_SEARCH_USER_PATHS = NO; 295 | CLANG_ANALYZER_NONNULL = YES; 296 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 297 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 298 | CLANG_CXX_LIBRARY = "libc++"; 299 | CLANG_ENABLE_MODULES = YES; 300 | CLANG_ENABLE_OBJC_ARC = YES; 301 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 302 | CLANG_WARN_BOOL_CONVERSION = YES; 303 | CLANG_WARN_COMMA = YES; 304 | CLANG_WARN_CONSTANT_CONVERSION = YES; 305 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 306 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 307 | CLANG_WARN_EMPTY_BODY = YES; 308 | CLANG_WARN_ENUM_CONVERSION = YES; 309 | CLANG_WARN_INFINITE_RECURSION = YES; 310 | CLANG_WARN_INT_CONVERSION = YES; 311 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 312 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 313 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 314 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 315 | CLANG_WARN_STRICT_PROTOTYPES = YES; 316 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 317 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 318 | CLANG_WARN_UNREACHABLE_CODE = YES; 319 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 320 | CODE_SIGN_IDENTITY = "iPhone Developer"; 321 | COPY_PHASE_STRIP = NO; 322 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 323 | ENABLE_NS_ASSERTIONS = NO; 324 | ENABLE_STRICT_OBJC_MSGSEND = YES; 325 | GCC_C_LANGUAGE_STANDARD = gnu11; 326 | GCC_NO_COMMON_BLOCKS = YES; 327 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 328 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 329 | GCC_WARN_UNDECLARED_SELECTOR = YES; 330 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 331 | GCC_WARN_UNUSED_FUNCTION = YES; 332 | GCC_WARN_UNUSED_VARIABLE = YES; 333 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 334 | MTL_ENABLE_DEBUG_INFO = NO; 335 | SDKROOT = iphoneos; 336 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 337 | VALIDATE_PRODUCT = YES; 338 | }; 339 | name = Release; 340 | }; 341 | 438FDFEE203DA5C900F8D1EF /* Debug */ = { 342 | isa = XCBuildConfiguration; 343 | buildSettings = { 344 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 345 | CODE_SIGN_STYLE = Automatic; 346 | DEVELOPMENT_TEAM = ABW6V45P3T; 347 | INFOPLIST_FILE = AnimationPrincipleDemo/Info.plist; 348 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 349 | PRODUCT_BUNDLE_IDENTIFIER = com.hsinyutang.AnimationPrincipleDemo; 350 | PRODUCT_NAME = "$(TARGET_NAME)"; 351 | SWIFT_VERSION = 4.0; 352 | TARGETED_DEVICE_FAMILY = "1,2"; 353 | }; 354 | name = Debug; 355 | }; 356 | 438FDFEF203DA5C900F8D1EF /* Release */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 360 | CODE_SIGN_STYLE = Automatic; 361 | DEVELOPMENT_TEAM = ABW6V45P3T; 362 | INFOPLIST_FILE = AnimationPrincipleDemo/Info.plist; 363 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 364 | PRODUCT_BUNDLE_IDENTIFIER = com.hsinyutang.AnimationPrincipleDemo; 365 | PRODUCT_NAME = "$(TARGET_NAME)"; 366 | SWIFT_VERSION = 4.0; 367 | TARGETED_DEVICE_FAMILY = "1,2"; 368 | }; 369 | name = Release; 370 | }; 371 | /* End XCBuildConfiguration section */ 372 | 373 | /* Begin XCConfigurationList section */ 374 | 438FDFD6203DA5C900F8D1EF /* Build configuration list for PBXProject "AnimationPrincipleDemo" */ = { 375 | isa = XCConfigurationList; 376 | buildConfigurations = ( 377 | 438FDFEB203DA5C900F8D1EF /* Debug */, 378 | 438FDFEC203DA5C900F8D1EF /* Release */, 379 | ); 380 | defaultConfigurationIsVisible = 0; 381 | defaultConfigurationName = Release; 382 | }; 383 | 438FDFED203DA5C900F8D1EF /* Build configuration list for PBXNativeTarget "AnimationPrincipleDemo" */ = { 384 | isa = XCConfigurationList; 385 | buildConfigurations = ( 386 | 438FDFEE203DA5C900F8D1EF /* Debug */, 387 | 438FDFEF203DA5C900F8D1EF /* Release */, 388 | ); 389 | defaultConfigurationIsVisible = 0; 390 | defaultConfigurationName = Release; 391 | }; 392 | /* End XCConfigurationList section */ 393 | }; 394 | rootObject = 438FDFD3203DA5C900F8D1EF /* Project object */; 395 | } 396 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo.xcodeproj/project.xcworkspace/xcuserdata/HsinYuTang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/AnimationPrincipleDemo/AnimationPrincipleDemo.xcodeproj/project.xcworkspace/xcuserdata/HsinYuTang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo.xcodeproj/xcuserdata/HsinYuTang.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo.xcodeproj/xcuserdata/HsinYuTang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AnimationPrincipleDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Animation.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/2/21. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum AnimationState { 12 | case active, stopped, inactive 13 | } 14 | 15 | class Animation { 16 | 17 | let name: String 18 | var state: AnimationState = .inactive 19 | 20 | lazy var action: Action = { 21 | let action = ActionFactory().createAction(of: name) 22 | return action! 23 | }() 24 | 25 | init(name: String) { 26 | self.name = name 27 | } 28 | 29 | func start() { 30 | state = .active 31 | action.start() 32 | } 33 | 34 | func pause() { 35 | action.pause() 36 | } 37 | 38 | func resume() { 39 | action.resume() 40 | } 41 | 42 | func stop() { 43 | state = .stopped 44 | action.stop { [weak self] stopped in 45 | if stopped { 46 | self?.state = .inactive 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/AnimationPrinciples.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationPrinciples.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/2/21. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct AnimationPrinciples { 12 | 13 | struct Timing { 14 | static let easing = "Easing" 15 | static let offsetAndDelay = "Offset & Delay" 16 | } 17 | 18 | struct ObjectRelationship { 19 | static let parenting = "Parenting" 20 | } 21 | 22 | struct ObjectContinuity { 23 | static let transformation = "Transformation" 24 | static let valueChange = "Value Change" 25 | static let masking = "Masking" 26 | static let overlay = "Overlay" 27 | static let cloning = "Cloning" 28 | } 29 | 30 | struct TemporalHierarchy { 31 | static let parallax = "Parallax" 32 | } 33 | 34 | struct SpatialContinuity { 35 | static let obscuration = "Obscuration" 36 | static let dimensionality = "Dimensionality" 37 | static let dollyAndZoom = "Dolly & Zoom" 38 | } 39 | 40 | 41 | static var animations: [Animation] { 42 | let easing = Animation(name: Timing.easing) 43 | let OffsetAndDelay = Animation(name: Timing.offsetAndDelay) 44 | let parenting = Animation(name: ObjectRelationship.parenting) 45 | let transformation = Animation(name: ObjectContinuity.transformation) 46 | let valueChange = Animation(name: ObjectContinuity.valueChange) 47 | let masking = Animation(name: ObjectContinuity.masking) 48 | let overlay = Animation(name: ObjectContinuity.overlay) 49 | let cloning = Animation(name: ObjectContinuity.cloning) 50 | let parallax = Animation(name: TemporalHierarchy.parallax) 51 | let obscuration = Animation(name: SpatialContinuity.obscuration) 52 | let dimensionality = Animation(name: SpatialContinuity.dimensionality) 53 | let dollyAndZoom = Animation(name: SpatialContinuity.dollyAndZoom) 54 | 55 | return [easing, OffsetAndDelay, parenting, transformation, valueChange, masking, overlay, cloning, parallax, obscuration, dimensionality, dollyAndZoom] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/Action.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Action.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/2/22. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol Action { 12 | 13 | init(parent: UIViewController) 14 | 15 | func start() 16 | 17 | func pause() 18 | 19 | func resume() 20 | 21 | func stop(completion: ((Bool) -> Swift.Void)?) 22 | 23 | } 24 | 25 | extension Action { 26 | 27 | func pause() {} 28 | 29 | func resume() {} 30 | 31 | func pauseAnimate(views: [UIView]) { 32 | views.forEach { 33 | let pausedTime = $0.layer.convertTime(CACurrentMediaTime(), from: nil) 34 | $0.layer.speed = 0.0 35 | $0.layer.timeOffset = pausedTime 36 | } 37 | } 38 | 39 | func resumeAnimate(views: [UIView]) { 40 | views.forEach { 41 | let pausedTime = $0.layer.timeOffset 42 | $0.layer.speed = 1.0 43 | $0.layer.timeOffset = 0.0 44 | $0.layer.beginTime = 0.0 45 | let timeSincePause = $0.layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime 46 | $0.layer.beginTime = timeSincePause 47 | } 48 | } 49 | 50 | func stopAnimate(views: [UIView]) { 51 | views.forEach { 52 | $0.layer.removeAllAnimations() 53 | $0.removeFromSuperview() 54 | } 55 | } 56 | 57 | func stopAnimate(layers: [CALayer]) { 58 | layers.forEach { 59 | $0.removeAllAnimations() 60 | $0.removeFromSuperlayer() 61 | } 62 | } 63 | } 64 | 65 | class ActionFactory { 66 | 67 | func createAction(of name: String) -> Action? { 68 | 69 | guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, 70 | let viewController = appDelegate.window?.rootViewController else { 71 | print("--Error: ViewController is nil") 72 | return nil 73 | } 74 | 75 | switch name { 76 | case AnimationPrinciples.Timing.easing: return EasingAction(parent: viewController) 77 | case AnimationPrinciples.Timing.offsetAndDelay: return OffsetAndDelayAction(parent: viewController) 78 | case AnimationPrinciples.ObjectRelationship.parenting: return ParentingAction(parent: viewController) 79 | case AnimationPrinciples.ObjectContinuity.transformation: return TransformationAction(parent: viewController) 80 | case AnimationPrinciples.ObjectContinuity.valueChange: return ValueChangeAction(parent: viewController) 81 | case AnimationPrinciples.ObjectContinuity.masking: return MaskingAction(parent: viewController) 82 | case AnimationPrinciples.ObjectContinuity.overlay: return OverlayAction(parent: viewController) 83 | case AnimationPrinciples.ObjectContinuity.cloning: return CloningAction(parent: viewController) 84 | case AnimationPrinciples.TemporalHierarchy.parallax: return ParallaxAction(parent: viewController) 85 | case AnimationPrinciples.SpatialContinuity.obscuration: return ObscurationAction(parent: viewController) 86 | case AnimationPrinciples.SpatialContinuity.dimensionality: return DimensionalityAction(parent: viewController) 87 | case AnimationPrinciples.SpatialContinuity.dollyAndZoom: return DollyAndZoomAction(parent: viewController) 88 | 89 | default: return EasingAction(parent: viewController) 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/CloningAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloningAction.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/3/5. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CloningAction: Action { 12 | 13 | let parent: UIViewController 14 | let layer1 = CAShapeLayer() 15 | let layer2 = CAShapeLayer() 16 | let fullSize = UIScreen.main.bounds 17 | 18 | required init(parent: UIViewController) { 19 | self.parent = parent 20 | } 21 | 22 | func start() { 23 | 24 | configure() 25 | 26 | position() 27 | 28 | } 29 | 30 | func stop(completion: ((Bool) -> Void)?) { 31 | stopAnimate(layers: [layer1, layer2]) 32 | completion?(true) 33 | } 34 | 35 | private func configure() { 36 | let path = UIBezierPath() 37 | let leftCenter = CGPoint(x: fullSize.width * 0.35, y: fullSize.height * 0.5) 38 | path.addArc(withCenter: leftCenter, radius: 50, startAngle: CGFloat(0).toRadians(), endAngle: CGFloat(360).toRadians(), clockwise: true) 39 | layer1.path = path.cgPath 40 | layer1.fillColor = UIColor.lightGray.cgColor 41 | parent.view.layer.addSublayer(layer1) 42 | 43 | let path2 = UIBezierPath() 44 | let rightCenter = CGPoint(x: fullSize.width * 0.65, y: fullSize.height * 0.5) 45 | path2.addArc(withCenter: rightCenter, radius: 50, startAngle: CGFloat(0).toRadians(), endAngle: CGFloat(360).toRadians(), clockwise: true) 46 | layer2.path = path2.cgPath 47 | layer2.fillColor = UIColor.lightGray.cgColor 48 | parent.view.layer.addSublayer(layer2) 49 | } 50 | 51 | private func position() { 52 | 53 | let anim: CABasicAnimation = { 54 | 55 | let anim = CABasicAnimation(keyPath: "path") 56 | let path = UIBezierPath() 57 | let center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * 0.5) 58 | path.addArc(withCenter: center, radius: 50, startAngle: CGFloat(0).toRadians(), endAngle: CGFloat(360).toRadians(), clockwise: true) 59 | anim.toValue = path.cgPath 60 | anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 61 | anim.duration = 2 62 | anim.speed = 2 63 | anim.autoreverses = true 64 | anim.repeatCount = .infinity 65 | 66 | return anim 67 | }() 68 | 69 | layer1.add(anim, forKey: "position\(layer1)") 70 | layer2.add(anim, forKey: "position\(layer2)") 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/DimensionalityAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DimensionalityAction.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/3/6. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DimensionalityAction: Action { 12 | 13 | let parent: UIViewController 14 | let view1 = UIView() 15 | let fullSize = UIScreen.main.bounds 16 | 17 | required init(parent: UIViewController) { 18 | self.parent = parent 19 | } 20 | 21 | func start() { 22 | 23 | configure() 24 | 25 | flip() 26 | 27 | } 28 | 29 | func stop(completion: ((Bool) -> Void)?) { 30 | stopAnimate(views: [view1]) 31 | completion?(true) 32 | } 33 | 34 | private func configure() { 35 | view1.frame.size = CGSize(width: 200, height: 50) 36 | view1.center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * 0.5) 37 | view1.backgroundColor = .lightGray 38 | view1.layer.anchorPoint = CGPoint(x: 0.5, y: 0) 39 | parent.view.addSubview(view1) 40 | 41 | let view2 = UIView() 42 | view2.frame.size = CGSize(width: 200, height: 50) 43 | view2.center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * 0.5 - 25) 44 | view2.backgroundColor = .white 45 | parent.view.addSubview(view2) 46 | } 47 | 48 | private func flip() { 49 | 50 | let anim: CABasicAnimation = { 51 | 52 | let anim = CABasicAnimation(keyPath: "transform") 53 | var transform = CATransform3DIdentity 54 | let angle = CGFloat(-90) 55 | transform.m34 = -1 / 500 56 | anim.toValue = CATransform3DRotate(transform, angle, 1, 0, 0) 57 | 58 | return anim 59 | }() 60 | 61 | let anim2: CABasicAnimation = { 62 | 63 | let anim = CABasicAnimation(keyPath: "backgroundColor") 64 | anim.fromValue = UIColor.lightGray.cgColor 65 | anim.toValue = UIColor.darkGray.cgColor 66 | 67 | return anim 68 | }() 69 | 70 | let group: CAAnimationGroup = { 71 | let group = CAAnimationGroup() 72 | group.animations = [anim, anim2] 73 | group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 74 | group.duration = 2 75 | group.speed = 2 76 | group.autoreverses = true 77 | group.repeatCount = .infinity 78 | return group 79 | }() 80 | 81 | 82 | view1.layer.add(group, forKey: "group\(view1)") 83 | } 84 | 85 | } 86 | 87 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/DollyAndZoomAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DollyAndZoomAction.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/3/7. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DollyAndZoomAction: Action { 12 | 13 | let parent: UIViewController 14 | var layer = CAShapeLayer() 15 | let fullSize = UIScreen.main.bounds 16 | var stackView = UIStackView() 17 | var view: UIView { 18 | let view = createView() 19 | return view 20 | } 21 | required init(parent: UIViewController) { 22 | self.parent = parent 23 | } 24 | 25 | func start() { 26 | 27 | configure() 28 | 29 | transform() 30 | } 31 | 32 | func stop(completion: ((Bool) -> Void)?) { 33 | stopAnimate(layers: [stackView.layer, layer]) 34 | completion?(true) 35 | } 36 | 37 | private func configure() { 38 | 39 | let stackView1 = createStackView(of: .horizontal, arrangedSubviews: [view, view, view]) 40 | let stackView2 = createStackView(of: .horizontal, arrangedSubviews: [view, view, view]) 41 | let stackView3 = createStackView(of: .horizontal, arrangedSubviews: [view, view, view]) 42 | 43 | let subview = UIView(frame: CGRect(x: 0, y: 0, width: fullSize.width, height: fullSize.height)) 44 | parent.view.addSubview(subview) 45 | 46 | stackView = createStackView(of: .vertical, arrangedSubviews: [stackView1, stackView2, stackView3]) 47 | subview.addSubview(stackView) 48 | stackView.centerXAnchor.constraint(equalTo: parent.view.centerXAnchor).isActive = true 49 | stackView.centerYAnchor.constraint(equalTo: parent.view.centerYAnchor).isActive = true 50 | 51 | layer = triangleShapeLayer() 52 | subview.layer.addSublayer(layer) 53 | subview.layer.mask = layer 54 | } 55 | 56 | private func triangleShapeLayer() -> CAShapeLayer { 57 | let shapeLayer = CAShapeLayer() 58 | let rect = CGRect(x: parent.view.center.x - 150, y: parent.view.center.y - 75, width: 300, height: 150) 59 | let bezierPath = UIBezierPath(rect: rect) 60 | shapeLayer.path = bezierPath.cgPath 61 | shapeLayer.fillColor = UIColor.darkGray.cgColor 62 | return shapeLayer 63 | } 64 | 65 | private func transform() { 66 | 67 | let anim: CABasicAnimation = { 68 | 69 | let anim = CABasicAnimation(keyPath: "transform.scale") 70 | anim.toValue = 3 71 | anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 72 | anim.duration = 2 73 | anim.speed = 2 74 | anim.autoreverses = true 75 | anim.repeatCount = .infinity 76 | 77 | return anim 78 | }() 79 | 80 | stackView.layer.add(anim, forKey: "transform.scale\(layer)") 81 | } 82 | 83 | private func createStackView(of axis: UILayoutConstraintAxis, arrangedSubviews subviews: [UIView]) -> UIStackView { 84 | let stackView = UIStackView(arrangedSubviews: subviews) 85 | stackView.axis = axis 86 | stackView.distribution = .fillEqually 87 | stackView.alignment = .fill 88 | stackView.spacing = 10 89 | stackView.translatesAutoresizingMaskIntoConstraints = false 90 | 91 | return stackView 92 | } 93 | 94 | private func createView() -> UIView { 95 | let view = UIView() 96 | view.backgroundColor = .lightGray 97 | view.heightAnchor.constraint(equalToConstant: 50).isActive = true 98 | view.widthAnchor.constraint(equalToConstant: 100).isActive = true 99 | 100 | return view 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/EasingAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasingViewController.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/2/22. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class EasingAction: Action { 12 | 13 | let parent: UIViewController 14 | let view1 = UIView() 15 | let fullSize = UIScreen.main.bounds 16 | 17 | required init(parent: UIViewController) { 18 | self.parent = parent 19 | } 20 | 21 | func start() { 22 | 23 | let middleX = fullSize.width * 0.5 24 | let middleY = fullSize.height * 0.5 25 | 26 | view1.frame.size = CGSize(width: 100, height: 100) 27 | view1.center = CGPoint(x: middleX, y: middleY) 28 | view1.backgroundColor = .lightGray 29 | parent.view.addSubview(view1) 30 | 31 | let anim: CAKeyframeAnimation = { 32 | 33 | let anim = CAKeyframeAnimation(keyPath: "position") 34 | 35 | let p1 = CGPoint(x: view1.center.x - 250, y: middleY) 36 | let p2 = CGPoint(x: view1.center.x, y: middleY) 37 | let p3 = CGPoint(x: view1.center.x + 250, y: middleY) 38 | 39 | anim.values = [p1, p2, p2, p3] 40 | anim.keyTimes = [0, 0.5, 0.7, 1] 41 | anim.timingFunctions = [ 42 | CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault), 43 | CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault), 44 | CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear), 45 | CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)] 46 | anim.duration = 3 47 | anim.speed = 1.5 48 | anim.repeatCount = .infinity 49 | 50 | return anim 51 | }() 52 | 53 | view1.layer.add(anim, forKey: "Animation") 54 | } 55 | 56 | func stop(completion: ((Bool) -> Void)?) { 57 | stopAnimate(views: [view1]) 58 | completion?(true) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/MaskingAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MaskingAction.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/2/23. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MaskingAction: Action { 12 | 13 | let parent: UIViewController 14 | var layer = CAShapeLayer() 15 | let view1 = UIView() 16 | let view2 = UIView() 17 | let fullSize = UIScreen.main.bounds 18 | 19 | required init(parent: UIViewController) { 20 | self.parent = parent 21 | } 22 | 23 | func start() { 24 | 25 | configure() 26 | 27 | path() 28 | } 29 | 30 | func stop(completion: ((Bool) -> Void)?) { 31 | stopAnimate(layers: [view1.layer, view2.layer, layer]) 32 | completion?(true) 33 | } 34 | 35 | private func configure() { 36 | 37 | view1.frame.size = CGSize(width: 200, height: 100) 38 | view1.center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * 0.5) 39 | view1.backgroundColor = .gray 40 | parent.view.addSubview(view1) 41 | 42 | view2.frame.size = CGSize(width: 200, height: 100) 43 | view2.center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * 0.5) 44 | view2.backgroundColor = .lightGray 45 | parent.view.addSubview(view2) 46 | 47 | layer = circleShapeLayer() 48 | view2.layer.addSublayer(layer) 49 | view2.layer.mask = layer 50 | } 51 | 52 | private func circleShapeLayer() -> CAShapeLayer { 53 | let shapeLayer = CAShapeLayer() 54 | let bezierPath = self.bezierPath(withLeftCenter: CGPoint(x: 100, y: 50), rightCenter: CGPoint(x: 100, y: 50), radius: 20) 55 | shapeLayer.path = bezierPath.cgPath 56 | shapeLayer.fillColor = UIColor.darkGray.cgColor 57 | return shapeLayer 58 | } 59 | 60 | private func path() { 61 | 62 | let anim: CABasicAnimation = { 63 | 64 | let anim = CABasicAnimation(keyPath: "path") 65 | let bezierPath = self.bezierPath(withLeftCenter: CGPoint(x: 0, y: 50), rightCenter: CGPoint(x: 200, y: 50), radius: 70) 66 | anim.toValue = bezierPath.cgPath 67 | anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 68 | anim.duration = 2 69 | anim.speed = 2 70 | anim.autoreverses = true 71 | anim.repeatCount = .infinity 72 | 73 | return anim 74 | }() 75 | 76 | layer.add(anim, forKey: "path\(view2)") 77 | } 78 | 79 | private func bezierPath(withLeftCenter leftCenter: CGPoint, rightCenter: CGPoint, radius: CGFloat) -> UIBezierPath { 80 | let path = UIBezierPath() 81 | path.lineJoinStyle = .round 82 | path.lineCapStyle = .round 83 | path.addArc(withCenter: leftCenter, radius: radius, startAngle: CGFloat(90).toRadians(), endAngle: CGFloat(270).toRadians(), clockwise: true) 84 | path.addArc(withCenter: rightCenter, radius: radius, startAngle: CGFloat(-90).toRadians(), endAngle: CGFloat(90).toRadians(), clockwise: true) 85 | path.close() 86 | return path 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/ObscurationAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObscurationAction.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/3/6. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ObscurationAction: Action { 12 | 13 | let parent: UIViewController 14 | let view1 = UIView() 15 | let view2 = UIView() 16 | let fullSize = UIScreen.main.bounds 17 | 18 | required init(parent: UIViewController) { 19 | self.parent = parent 20 | } 21 | 22 | func start() { 23 | 24 | configure(view: view1, width: 150, height: 200, color: .gray) 25 | configure(view: view2, width: 180, height: 240, color: .clear) 26 | 27 | position(view: view2) 28 | blur(view: view2) 29 | } 30 | 31 | func stop(completion: ((Bool) -> Void)?) { 32 | stopAnimate(views: [view1, view2]) 33 | completion?(true) 34 | } 35 | 36 | private func configure(view: UIView, width: CGFloat, height: CGFloat, color: UIColor) { 37 | view.frame.size = CGSize(width: width, height: height) 38 | view.center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * 0.5) 39 | view.backgroundColor = color 40 | parent.view.addSubview(view) 41 | } 42 | 43 | private func position(view: UIView) { 44 | 45 | let anim: CABasicAnimation = { 46 | 47 | let anim = CABasicAnimation(keyPath: "position.x") 48 | anim.fromValue = view.center.x + 100 49 | anim.toValue = view.center.x - 100 50 | anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 51 | anim.duration = 2 52 | anim.speed = 2 53 | anim.autoreverses = true 54 | anim.repeatCount = .infinity 55 | 56 | return anim 57 | }() 58 | 59 | view.layer.add(anim, forKey: "position\(view)") 60 | } 61 | 62 | private func blur(view: UIView) { 63 | let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.extraLight) 64 | let blurEffectView = UIVisualEffectView(effect: blurEffect) 65 | blurEffectView.frame = view.bounds 66 | view.addSubview(blurEffectView) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/OffsetAndDelayAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OffsetAndDelayAction.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/2/22. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class OffsetAndDelayAction: Action { 12 | 13 | let parent: UIViewController 14 | let view1 = UIView() 15 | let view2 = UIView() 16 | let view3 = UIView() 17 | let fullSize = UIScreen.main.bounds 18 | 19 | required init(parent: UIViewController) { 20 | self.parent = parent 21 | } 22 | 23 | func start() { 24 | 25 | configure(view: view1, y: 0.35) 26 | configure(view: view2, y: 0.45) 27 | configure(view: view3, y: 0.55) 28 | 29 | animate(view: view1, timing: kCAMediaTimingFunctionDefault, keyTime: 0.7) 30 | animate(view: view2, timing: kCAMediaTimingFunctionDefault, keyTime: 0.7) 31 | animate(view: view3, timing: kCAMediaTimingFunctionEaseIn, keyTime: 0.8) 32 | } 33 | 34 | func stop(completion: ((Bool) -> Void)?) { 35 | stopAnimate(views: [view1, view2, view3]) 36 | completion?(true) 37 | } 38 | 39 | private func configure(view: UIView, y: CGFloat) { 40 | view.frame.size = CGSize(width: 150, height: 50) 41 | view.center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * y) 42 | view.backgroundColor = .lightGray 43 | parent.view.addSubview(view) 44 | } 45 | 46 | private func animate(view: UIView, timing: String, keyTime: NSNumber) { 47 | 48 | let anim: CAKeyframeAnimation = { 49 | 50 | let anim = CAKeyframeAnimation(keyPath: "position") 51 | 52 | let p1 = CGPoint(x: view.center.x - 250, y: view.center.y) 53 | let p2 = CGPoint(x: view.center.x, y: view.center.y) 54 | let p3 = CGPoint(x: view.center.x + 250, y: view.center.y) 55 | 56 | anim.values = [p1, p2, p2, p3] 57 | anim.keyTimes = [0, 0.5, keyTime, 1] 58 | anim.timingFunctions = [ 59 | CAMediaTimingFunction(name: timing), 60 | CAMediaTimingFunction(name: timing), 61 | CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear), 62 | CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)] 63 | anim.duration = 3 64 | anim.speed = 1.5 65 | anim.repeatCount = .infinity 66 | 67 | return anim 68 | }() 69 | 70 | view.layer.add(anim, forKey: "\(view)") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/OverlayAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverlayAction.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/3/5. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class OverlayAction: Action { 12 | 13 | let parent: UIViewController 14 | let view1 = UIView() 15 | let view2 = UIView() 16 | let fullSize = UIScreen.main.bounds 17 | 18 | required init(parent: UIViewController) { 19 | self.parent = parent 20 | } 21 | 22 | func start() { 23 | 24 | configure() 25 | 26 | position() 27 | } 28 | 29 | func stop(completion: ((Bool) -> Void)?) { 30 | stopAnimate(views: [view1, view2]) 31 | completion?(true) 32 | } 33 | 34 | private func configure() { 35 | 36 | view1.frame.size = CGSize(width: 100, height: 100) 37 | view1.center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * 0.5) 38 | view1.backgroundColor = .gray 39 | parent.view.addSubview(view1) 40 | 41 | view2.frame.size = CGSize(width: 100, height: 100) 42 | view2.center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * 0.5) 43 | view2.backgroundColor = .lightGray 44 | parent.view.addSubview(view2) 45 | } 46 | 47 | private func position() { 48 | 49 | let anim: CABasicAnimation = { 50 | 51 | let anim = CABasicAnimation(keyPath: "position") 52 | anim.fromValue = CGPoint(x: fullSize.width * 0.7, y: fullSize.height * 0.5) 53 | anim.toValue = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * 0.5) 54 | anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 55 | anim.duration = 2 56 | anim.speed = 2 57 | anim.autoreverses = true 58 | anim.repeatCount = .infinity 59 | 60 | return anim 61 | }() 62 | 63 | view2.layer.add(anim, forKey: "path\(view2)") 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/ParallaxAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParallaxAction.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/3/6. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ParallaxAction: Action { 12 | 13 | let parent: UIViewController 14 | let view1 = UIView() 15 | let view2 = UIView() 16 | let fullSize = UIScreen.main.bounds 17 | 18 | required init(parent: UIViewController) { 19 | self.parent = parent 20 | } 21 | 22 | func start() { 23 | 24 | configure(view: view1, width: 200, color: .gray) 25 | configure(view: view2, width: 150, color: .lightGray) 26 | 27 | position(view: view1, y: 10) 28 | position(view: view2, y: 30) 29 | } 30 | 31 | func stop(completion: ((Bool) -> Void)?) { 32 | stopAnimate(views: [view1, view2]) 33 | completion?(true) 34 | } 35 | 36 | private func configure(view: UIView, width: CGFloat, color: UIColor) { 37 | view.frame.size = CGSize(width: width, height: 100) 38 | view.center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * 0.5) 39 | view.backgroundColor = color 40 | parent.view.addSubview(view) 41 | } 42 | 43 | private func position(view: UIView, y: CGFloat) { 44 | 45 | let anim: CABasicAnimation = { 46 | 47 | let anim = CABasicAnimation(keyPath: "position.y") 48 | anim.fromValue = view.center.y + y 49 | anim.toValue = view.center.y - y 50 | anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 51 | anim.duration = 2 52 | anim.speed = 2 53 | anim.autoreverses = true 54 | anim.repeatCount = .infinity 55 | 56 | return anim 57 | }() 58 | 59 | view.layer.add(anim, forKey: "position\(view)") 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/ParentingAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParentingAction.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/2/22. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ParentingAction: Action { 12 | 13 | let parent: UIViewController 14 | let view1 = UIView() 15 | let view2 = UIView() 16 | let fullSize = UIScreen.main.bounds 17 | 18 | required init(parent: UIViewController) { 19 | self.parent = parent 20 | } 21 | 22 | func start() { 23 | 24 | configure(view: view1, y: 0.45) 25 | configure(view: view2, y: 0.5) 26 | 27 | transform(view: view1) 28 | position(view: view1) 29 | position(view: view2) 30 | } 31 | 32 | func stop(completion: ((Bool) -> Void)?) { 33 | stopAnimate(views: [view1, view2]) 34 | completion?(true) 35 | } 36 | 37 | private func configure(view: UIView, y: CGFloat) { 38 | view.frame.size = CGSize(width: 100, height: 50) 39 | view.center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * y) 40 | view.backgroundColor = .lightGray 41 | parent.view.addSubview(view) 42 | } 43 | 44 | private func position(view: UIView) { 45 | 46 | let anim: CABasicAnimation = { 47 | 48 | let anim = CABasicAnimation(keyPath: "position.x") 49 | anim.fromValue = view.center.x + 100 50 | anim.toValue = view.center.x - 100 51 | anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) 52 | anim.duration = 2 53 | anim.speed = 2 54 | anim.autoreverses = true 55 | anim.repeatCount = .infinity 56 | 57 | return anim 58 | }() 59 | 60 | view.layer.add(anim, forKey: "position\(view)") 61 | } 62 | 63 | private func transform(view: UIView) { 64 | 65 | view.layer.anchorPoint = CGPoint(x: 0.5, y: 1) 66 | 67 | let anim: CABasicAnimation = { 68 | 69 | let anim = CABasicAnimation(keyPath: "transform.scale") 70 | anim.fromValue = 1 71 | anim.toValue = 0.1 72 | anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) 73 | anim.duration = 2 74 | anim.speed = 2 75 | anim.autoreverses = true 76 | anim.repeatCount = .infinity 77 | 78 | return anim 79 | }() 80 | 81 | view.layer.add(anim, forKey: "transform\(view)") 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/TransformationAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransformationAction.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/2/23. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TransformationAction: Action { 12 | 13 | let parent: UIViewController 14 | let view1 = UIView() 15 | let fullSize = UIScreen.main.bounds 16 | 17 | required init(parent: UIViewController) { 18 | self.parent = parent 19 | } 20 | 21 | func start() { 22 | 23 | configure(view: view1) 24 | 25 | bounds(view: view1) 26 | cornerRadius(view: view1) 27 | } 28 | 29 | func stop(completion: ((Bool) -> Void)?) { 30 | stopAnimate(views: [view1]) 31 | completion?(true) 32 | } 33 | 34 | private func configure(view: UIView) { 35 | view.frame.size = CGSize(width: 50, height: 50) 36 | view.layer.cornerRadius = 25 37 | view.center = CGPoint(x: fullSize.width * 0.5, y: fullSize.height * 0.5) 38 | view.backgroundColor = .lightGray 39 | parent.view.addSubview(view) 40 | } 41 | 42 | private func bounds(view: UIView) { 43 | 44 | let anim: CABasicAnimation = { 45 | 46 | let anim = CABasicAnimation(keyPath: "bounds.size") 47 | anim.fromValue = CGSize(width: 50, height: 50) 48 | anim.toValue = CGSize(width: 200, height: 100) 49 | anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) 50 | anim.duration = 2 51 | anim.speed = 2 52 | anim.autoreverses = true 53 | anim.repeatCount = .infinity 54 | 55 | return anim 56 | }() 57 | 58 | view.layer.add(anim, forKey: "bounds\(view)") 59 | } 60 | 61 | private func cornerRadius(view: UIView) { 62 | 63 | let anim: CABasicAnimation = { 64 | 65 | let anim = CABasicAnimation(keyPath: "cornerRadius") 66 | anim.fromValue = 25 67 | anim.toValue = 0 68 | anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) 69 | anim.duration = 2 70 | anim.speed = 2 71 | anim.autoreverses = true 72 | anim.repeatCount = .infinity 73 | 74 | return anim 75 | }() 76 | 77 | view.layer.add(anim, forKey: "cornerRadius\(view)") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Animations/ValueChangeAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValueChangeAction.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/2/23. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ValueChangeAction: NSObject, Action { 12 | 13 | let parent: UIViewController 14 | let fullSize = UIScreen.main.bounds 15 | 16 | var shapeLayer = CAShapeLayer() 17 | var trackLayer = CAShapeLayer() 18 | 19 | let percentageLabel: UILabel = { 20 | let label = UILabel() 21 | label.text = "Processing" 22 | label.textAlignment = .center 23 | label.font = UIFont.boldSystemFont(ofSize: 18) 24 | return label 25 | }() 26 | 27 | var downloadTask = URLSessionDownloadTask() 28 | 29 | required init(parent: UIViewController) { 30 | self.parent = parent 31 | } 32 | 33 | func start() { 34 | configure() 35 | beginDownloadingFile() 36 | // strokeEnd(shapeLayer: shapeLayer) 37 | } 38 | 39 | func stop(completion: ((Bool) -> Void)?) { 40 | downloadTask.cancel() 41 | stopAnimate(layers: [trackLayer, shapeLayer]) 42 | percentageLabel.removeFromSuperview() 43 | completion?(true) 44 | } 45 | 46 | private func configure() { 47 | 48 | parent.view.addSubview(percentageLabel) 49 | percentageLabel.frame = CGRect(x: 0, y: 0, width: 100, height: 100) 50 | percentageLabel.center = parent.view.center 51 | 52 | trackLayer = customShapeLayer() 53 | 54 | shapeLayer = { 55 | let shapeLayer = customShapeLayer() 56 | shapeLayer.strokeColor = UIColor.black.cgColor 57 | shapeLayer.lineCap = kCALineCapRound 58 | shapeLayer.strokeEnd = 0 59 | return shapeLayer 60 | }() 61 | 62 | parent.view.layer.addSublayer(trackLayer) 63 | parent.view.layer.addSublayer(shapeLayer) 64 | } 65 | 66 | private func customShapeLayer() -> CAShapeLayer { 67 | let shapeLayer = CAShapeLayer() 68 | let circlePath = UIBezierPath(arcCenter: parent.view.center, 69 | radius: 100, 70 | startAngle: CGFloat(-90).toRadians(), 71 | endAngle: CGFloat(270).toRadians(), 72 | clockwise: true) 73 | shapeLayer.path = circlePath.cgPath 74 | shapeLayer.fillColor = UIColor.clear.cgColor 75 | shapeLayer.strokeColor = UIColor.lightGray.cgColor 76 | shapeLayer.lineWidth = 10 77 | return shapeLayer 78 | } 79 | 80 | private func strokeEnd(shapeLayer: CAShapeLayer) { 81 | 82 | let anim: CABasicAnimation = { 83 | let anim = CABasicAnimation(keyPath: "strokeEnd") 84 | anim.toValue = 1 85 | anim.duration = 2 86 | anim.fillMode = kCAFillModeForwards 87 | anim.isRemovedOnCompletion = false 88 | 89 | return anim 90 | }() 91 | 92 | shapeLayer.add(anim, forKey: "strokeEnd\(shapeLayer)") 93 | } 94 | 95 | private func beginDownloadingFile() { 96 | 97 | shapeLayer.strokeEnd = 0 98 | 99 | let urlString = "https://drive.google.com/uc?export=download&id=14TFlwU-6KuQrEx7OgrjNfUyStqI6YQwZ" 100 | let configuration = URLSessionConfiguration.default 101 | let operationQueue = OperationQueue() 102 | let urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: operationQueue) 103 | guard let url = URL(string: urlString) else { return } 104 | downloadTask = urlSession.downloadTask(with: url) 105 | downloadTask.resume() 106 | } 107 | } 108 | 109 | extension ValueChangeAction: URLSessionDownloadDelegate { 110 | 111 | func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { 112 | 113 | // let percentage = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite) 114 | let percentage = CGFloat(totalBytesWritten) / CGFloat(55180268.0) 115 | 116 | DispatchQueue.main.async { 117 | self.shapeLayer.strokeEnd = percentage 118 | self.percentageLabel.font = UIFont.boldSystemFont(ofSize: 32) 119 | self.percentageLabel.text = "\(Int(percentage * 100))%" 120 | } 121 | 122 | // print(percentage) 123 | } 124 | 125 | func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { 126 | print("Finished downloading file") 127 | } 128 | 129 | } 130 | 131 | extension CGFloat { 132 | func toRadians() -> CGFloat { 133 | return self * CGFloat.pi / 180.0 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/2/21. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /AnimationPrincipleDemo/AnimationPrincipleDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AnimationPrincipleDemo 4 | // 5 | // Created by 湯芯瑜 on 2018/2/21. 6 | // Copyright © 2018年 Hsin-Yu Tang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet weak var animationName: UILabel! 14 | @IBOutlet weak var animationPickerView: UIPickerView! 15 | 16 | var pickedAnimation: Animation = Animation(name: AnimationPrinciples.Timing.easing) 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | pickedAnimation.start() 22 | animationPickerView.dataSource = self 23 | animationPickerView.delegate = self 24 | 25 | } 26 | 27 | } 28 | 29 | // MARK: - UIPickerViewDataSource 30 | extension ViewController: UIPickerViewDataSource { 31 | 32 | func numberOfComponents(in pickerView: UIPickerView) -> Int { 33 | return 1 34 | } 35 | 36 | func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 37 | return AnimationPrinciples.animations.count 38 | } 39 | 40 | 41 | } 42 | 43 | // MARK: - UIPickerViewDelegate 44 | extension ViewController: UIPickerViewDelegate { 45 | 46 | func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { 47 | return AnimationPrinciples.animations[row].name 48 | } 49 | 50 | func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { 51 | pickedAnimation.stop() 52 | 53 | pickedAnimation = AnimationPrinciples.animations[row] 54 | animationName.text = pickedAnimation.name 55 | 56 | if pickedAnimation.state == .inactive { 57 | pickedAnimation.start() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AnimationPrincipleDemo 2 | The idea of this project came from [Issara Willenskomer](https://medium.com/ux-in-motion/creating-usability-with-motion-the-ux-in-motion-manifesto-a87a4584ddc). The results are shown below. 3 | 4 | ## Screen Recording 5 | ### Easing 6 | 7 | 8 | ### OffsetAndDelay 9 | 10 | 11 | ### Parenting 12 | 13 | 14 | ### Transformation 15 | 16 | 17 | ### ValueChange 18 | 19 | 20 | ### Masking 21 | 22 | 23 | ### Overlay 24 | 25 | 26 | ### Cloning 27 | 28 | 29 | ### Parallax 30 | 31 | 32 | ### Obscuration 33 | 34 | 35 | ### Dimensionality 36 | 37 | 38 | ### DollyAndZoom 39 | 40 | -------------------------------------------------------------------------------- /ScreenRecording/Cloning.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/Cloning.gif -------------------------------------------------------------------------------- /ScreenRecording/Dimensionality.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/Dimensionality.gif -------------------------------------------------------------------------------- /ScreenRecording/DollyAndZoom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/DollyAndZoom.gif -------------------------------------------------------------------------------- /ScreenRecording/Easing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/Easing.gif -------------------------------------------------------------------------------- /ScreenRecording/Masking.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/Masking.gif -------------------------------------------------------------------------------- /ScreenRecording/Obscuration.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/Obscuration.gif -------------------------------------------------------------------------------- /ScreenRecording/OffsetAndDelay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/OffsetAndDelay.gif -------------------------------------------------------------------------------- /ScreenRecording/Overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/Overlay.gif -------------------------------------------------------------------------------- /ScreenRecording/Parallax.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/Parallax.gif -------------------------------------------------------------------------------- /ScreenRecording/Parenting.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/Parenting.gif -------------------------------------------------------------------------------- /ScreenRecording/Transformation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/Transformation.gif -------------------------------------------------------------------------------- /ScreenRecording/ValueChange.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CelesteTang/AnimationPrincipleDemo/a7a2807059e1159e8dd517eff5a9e4416051c1ea/ScreenRecording/ValueChange.gif --------------------------------------------------------------------------------