├── .gitignore ├── .swift-version ├── CONTRIBUTING.md ├── Demo └── Walker │ ├── Podfile │ ├── Podfile.lock │ ├── README.md │ ├── Walker.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── Walker.xcworkspace │ └── contents.xcworkspacedata │ └── Walker │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-40.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-72.png │ │ ├── Icon-72@2x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-83.5@2x.png │ │ ├── Icon-Small-50.png │ │ ├── Icon-Small-50@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── Icon.png │ │ └── Icon@2x.png │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ └── ViewController.swift ├── LICENSE.md ├── Others └── Info.plist ├── README.md ├── ROADMAP.md ├── Resources ├── Design.sketch ├── bezier.gif ├── chain.gif ├── demo.gif ├── header-image.png └── spring.gif ├── Source ├── .gitkeep ├── Background │ ├── Animation.swift │ └── Distill.swift ├── Constructors │ ├── Bezier.swift │ ├── Chain.swift │ ├── Spring.swift │ └── Still.swift └── Distillery │ ├── Distillery.swift │ └── Ingredient.swift ├── Walker.podspec └── Walker.xcodeproj ├── project.pbxproj ├── project.xcworkspace └── contents.xcworkspacedata └── xcshareddata └── xcschemes └── Walker.xcscheme /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | Icon 6 | ._* 7 | .Spotlight-V100 8 | .Trashes 9 | 10 | # Xcode 11 | # 12 | build/ 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | *.xccheckout 23 | *.moved-aside 24 | DerivedData 25 | *.hmap 26 | *.ipa 27 | *.xcuserstate 28 | 29 | Pods/ 30 | vendor/bundle 31 | .bundle/ 32 | bin 33 | mystore-temp 34 | /screenshots 35 | 36 | *.dSYM.zip 37 | 38 | *.paw 39 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribute 2 | 3 | 1. Fork it 4 | 2. Create your feature branch (`git checkout -b my-new-feature`) 5 | 3. Commit your changes (`git commit -am 'Add some feature'`) 6 | 4. Push to the branch (`git push origin my-new-feature`) 7 | 5. Create pull request 8 | 9 | Make sure to follow the guidelines of the code and the two indentation as is, you can also check the [Playbook](https://github.com/hyperoslo/iOS-playbook) before contributing! :) 10 | 11 | ### Other information 12 | 13 | GitHub Issues is for reporting bugs, discussing features and general feedback in **Walker**. If you are posting about a crash in your application, a stack trace is helpful, but additional context, in the form of code and explanation, is necessary to be of any use. 14 | -------------------------------------------------------------------------------- /Demo/Walker/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '8.0' 2 | 3 | use_frameworks! 4 | inhibit_all_warnings! 5 | 6 | pod 'Walker', path: '../../' 7 | target 'WalkerDemo' 8 | -------------------------------------------------------------------------------- /Demo/Walker/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Walker (0.9.1) 3 | 4 | DEPENDENCIES: 5 | - Walker (from `../../`) 6 | 7 | EXTERNAL SOURCES: 8 | Walker: 9 | :path: "../../" 10 | 11 | SPEC CHECKSUMS: 12 | Walker: 9a765e7f35adb19ce59079e26d425c1e8c463241 13 | 14 | PODFILE CHECKSUM: cc3735b5559b2ca2ab8883bc6df0f2a75346a90d 15 | 16 | COCOAPODS: 1.0.1 17 | -------------------------------------------------------------------------------- /Demo/Walker/README.md: -------------------------------------------------------------------------------- 1 | # Walker demo 2 | 3 | ![Walker demo](https://github.com/RamonGilabert/Walker/blob/master/Resources/demo.gif) 4 | 5 | This demo intends to show you a bit of insight in Walker, check out the source code in the `ViewController` for more information, and more concretely [here](https://github.com/RamonGilabert/Walker/blob/master/Demo/Walker/Walker/ViewController.swift#L51). Where the real animations happen. 6 | 7 | If you have any questions, don't hesitate to [contact me](mailto:ramon.gilabert.llop@gmail.com)! :) 8 | -------------------------------------------------------------------------------- /Demo/Walker/Walker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 292D71A11C930A8200CFC060 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D719B1C930A8200CFC060 /* AppDelegate.swift */; }; 11 | 292D71A21C930A8200CFC060 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 292D719C1C930A8200CFC060 /* Assets.xcassets */; }; 12 | 292D71A31C930A8200CFC060 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 292D719D1C930A8200CFC060 /* LaunchScreen.storyboard */; }; 13 | 292D71A51C930A8200CFC060 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D71A01C930A8200CFC060 /* ViewController.swift */; }; 14 | C4D1EB86CDD6B25EC709029A /* Pods_WalkerDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62D97E72DE4BA6EE9A50D431 /* Pods_WalkerDemo.framework */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 291128941BADBFBD00C66827 /* WalkerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WalkerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | 292D719B1C930A8200CFC060 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 20 | 292D719C1C930A8200CFC060 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 21 | 292D719E1C930A8200CFC060 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 22 | 292D719F1C930A8200CFC060 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 23 | 292D71A01C930A8200CFC060 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 24 | 35E582BAC85E775687B231AD /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 62D97E72DE4BA6EE9A50D431 /* Pods_WalkerDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WalkerDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | CC33DCD894C03515A6C49C21 /* Pods-WalkerDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WalkerDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-WalkerDemo/Pods-WalkerDemo.debug.xcconfig"; sourceTree = ""; }; 27 | E928746C7CCBBA6A32B1A167 /* Pods-WalkerDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WalkerDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-WalkerDemo/Pods-WalkerDemo.release.xcconfig"; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | 291128911BADBFBD00C66827 /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | C4D1EB86CDD6B25EC709029A /* Pods_WalkerDemo.framework in Frameworks */, 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 2911288B1BADBFBD00C66827 = { 43 | isa = PBXGroup; 44 | children = ( 45 | 292D719A1C930A8200CFC060 /* Walker */, 46 | 291128951BADBFBD00C66827 /* Products */, 47 | 39F09D49120F3BDF1A295310 /* Frameworks */, 48 | EA42BE90A0003D092210860F /* Pods */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | 291128951BADBFBD00C66827 /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 291128941BADBFBD00C66827 /* WalkerDemo.app */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | 292D719A1C930A8200CFC060 /* Walker */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 292D719B1C930A8200CFC060 /* AppDelegate.swift */, 64 | 292D71A01C930A8200CFC060 /* ViewController.swift */, 65 | 292D719C1C930A8200CFC060 /* Assets.xcassets */, 66 | 292D719D1C930A8200CFC060 /* LaunchScreen.storyboard */, 67 | 292D719F1C930A8200CFC060 /* Info.plist */, 68 | ); 69 | path = Walker; 70 | sourceTree = ""; 71 | }; 72 | 39F09D49120F3BDF1A295310 /* Frameworks */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 35E582BAC85E775687B231AD /* Pods.framework */, 76 | 62D97E72DE4BA6EE9A50D431 /* Pods_WalkerDemo.framework */, 77 | ); 78 | name = Frameworks; 79 | sourceTree = ""; 80 | }; 81 | EA42BE90A0003D092210860F /* Pods */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | CC33DCD894C03515A6C49C21 /* Pods-WalkerDemo.debug.xcconfig */, 85 | E928746C7CCBBA6A32B1A167 /* Pods-WalkerDemo.release.xcconfig */, 86 | ); 87 | name = Pods; 88 | sourceTree = ""; 89 | }; 90 | /* End PBXGroup section */ 91 | 92 | /* Begin PBXNativeTarget section */ 93 | 291128931BADBFBD00C66827 /* WalkerDemo */ = { 94 | isa = PBXNativeTarget; 95 | buildConfigurationList = 291128A61BADBFBD00C66827 /* Build configuration list for PBXNativeTarget "WalkerDemo" */; 96 | buildPhases = ( 97 | B1B545511470FA3A46DBDDF5 /* [CP] Check Pods Manifest.lock */, 98 | 291128901BADBFBD00C66827 /* Sources */, 99 | 291128911BADBFBD00C66827 /* Frameworks */, 100 | 291128921BADBFBD00C66827 /* Resources */, 101 | DD7263BAE45AFECA026BA748 /* [CP] Embed Pods Frameworks */, 102 | 8F11DBC2D63E0AEF30601A6A /* [CP] Copy Pods Resources */, 103 | ); 104 | buildRules = ( 105 | ); 106 | dependencies = ( 107 | ); 108 | name = WalkerDemo; 109 | productName = Bakery; 110 | productReference = 291128941BADBFBD00C66827 /* WalkerDemo.app */; 111 | productType = "com.apple.product-type.application"; 112 | }; 113 | /* End PBXNativeTarget section */ 114 | 115 | /* Begin PBXProject section */ 116 | 2911288C1BADBFBD00C66827 /* Project object */ = { 117 | isa = PBXProject; 118 | attributes = { 119 | LastUpgradeCheck = 0800; 120 | ORGANIZATIONNAME = "Ramon Gilabert Llop"; 121 | TargetAttributes = { 122 | 291128931BADBFBD00C66827 = { 123 | CreatedOnToolsVersion = 7.0; 124 | LastSwiftMigration = 0800; 125 | }; 126 | }; 127 | }; 128 | buildConfigurationList = 2911288F1BADBFBD00C66827 /* Build configuration list for PBXProject "Walker" */; 129 | compatibilityVersion = "Xcode 3.2"; 130 | developmentRegion = English; 131 | hasScannedForEncodings = 0; 132 | knownRegions = ( 133 | en, 134 | Base, 135 | ); 136 | mainGroup = 2911288B1BADBFBD00C66827; 137 | productRefGroup = 291128951BADBFBD00C66827 /* Products */; 138 | projectDirPath = ""; 139 | projectRoot = ""; 140 | targets = ( 141 | 291128931BADBFBD00C66827 /* WalkerDemo */, 142 | ); 143 | }; 144 | /* End PBXProject section */ 145 | 146 | /* Begin PBXResourcesBuildPhase section */ 147 | 291128921BADBFBD00C66827 /* Resources */ = { 148 | isa = PBXResourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | 292D71A21C930A8200CFC060 /* Assets.xcassets in Resources */, 152 | 292D71A31C930A8200CFC060 /* LaunchScreen.storyboard in Resources */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXResourcesBuildPhase section */ 157 | 158 | /* Begin PBXShellScriptBuildPhase section */ 159 | 8F11DBC2D63E0AEF30601A6A /* [CP] Copy Pods Resources */ = { 160 | isa = PBXShellScriptBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | ); 164 | inputPaths = ( 165 | ); 166 | name = "[CP] Copy Pods Resources"; 167 | outputPaths = ( 168 | ); 169 | runOnlyForDeploymentPostprocessing = 0; 170 | shellPath = /bin/sh; 171 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-WalkerDemo/Pods-WalkerDemo-resources.sh\"\n"; 172 | showEnvVarsInLog = 0; 173 | }; 174 | B1B545511470FA3A46DBDDF5 /* [CP] Check Pods Manifest.lock */ = { 175 | isa = PBXShellScriptBuildPhase; 176 | buildActionMask = 2147483647; 177 | files = ( 178 | ); 179 | inputPaths = ( 180 | ); 181 | name = "[CP] Check Pods Manifest.lock"; 182 | outputPaths = ( 183 | ); 184 | runOnlyForDeploymentPostprocessing = 0; 185 | shellPath = /bin/sh; 186 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 187 | showEnvVarsInLog = 0; 188 | }; 189 | DD7263BAE45AFECA026BA748 /* [CP] Embed Pods Frameworks */ = { 190 | isa = PBXShellScriptBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | ); 194 | inputPaths = ( 195 | ); 196 | name = "[CP] Embed Pods Frameworks"; 197 | outputPaths = ( 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | shellPath = /bin/sh; 201 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-WalkerDemo/Pods-WalkerDemo-frameworks.sh\"\n"; 202 | showEnvVarsInLog = 0; 203 | }; 204 | /* End PBXShellScriptBuildPhase section */ 205 | 206 | /* Begin PBXSourcesBuildPhase section */ 207 | 291128901BADBFBD00C66827 /* Sources */ = { 208 | isa = PBXSourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | 292D71A51C930A8200CFC060 /* ViewController.swift in Sources */, 212 | 292D71A11C930A8200CFC060 /* AppDelegate.swift in Sources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXSourcesBuildPhase section */ 217 | 218 | /* Begin PBXVariantGroup section */ 219 | 292D719D1C930A8200CFC060 /* LaunchScreen.storyboard */ = { 220 | isa = PBXVariantGroup; 221 | children = ( 222 | 292D719E1C930A8200CFC060 /* Base */, 223 | ); 224 | name = LaunchScreen.storyboard; 225 | sourceTree = ""; 226 | }; 227 | /* End PBXVariantGroup section */ 228 | 229 | /* Begin XCBuildConfiguration section */ 230 | 291128A41BADBFBD00C66827 /* Debug */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | ALWAYS_SEARCH_USER_PATHS = NO; 234 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 235 | CLANG_CXX_LIBRARY = "libc++"; 236 | CLANG_ENABLE_MODULES = YES; 237 | CLANG_ENABLE_OBJC_ARC = YES; 238 | CLANG_WARN_BOOL_CONVERSION = YES; 239 | CLANG_WARN_CONSTANT_CONVERSION = YES; 240 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 241 | CLANG_WARN_EMPTY_BODY = YES; 242 | CLANG_WARN_ENUM_CONVERSION = YES; 243 | CLANG_WARN_INFINITE_RECURSION = YES; 244 | CLANG_WARN_INT_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 247 | CLANG_WARN_UNREACHABLE_CODE = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 250 | COPY_PHASE_STRIP = NO; 251 | DEBUG_INFORMATION_FORMAT = dwarf; 252 | ENABLE_STRICT_OBJC_MSGSEND = YES; 253 | ENABLE_TESTABILITY = YES; 254 | GCC_C_LANGUAGE_STANDARD = gnu99; 255 | GCC_DYNAMIC_NO_PIC = NO; 256 | GCC_NO_COMMON_BLOCKS = YES; 257 | GCC_OPTIMIZATION_LEVEL = 0; 258 | GCC_PREPROCESSOR_DEFINITIONS = ( 259 | "DEBUG=1", 260 | "$(inherited)", 261 | ); 262 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 263 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 264 | GCC_WARN_UNDECLARED_SELECTOR = YES; 265 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 266 | GCC_WARN_UNUSED_FUNCTION = YES; 267 | GCC_WARN_UNUSED_VARIABLE = YES; 268 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 269 | MTL_ENABLE_DEBUG_INFO = YES; 270 | ONLY_ACTIVE_ARCH = YES; 271 | SDKROOT = iphoneos; 272 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 273 | TARGETED_DEVICE_FAMILY = "1,2"; 274 | }; 275 | name = Debug; 276 | }; 277 | 291128A51BADBFBD00C66827 /* Release */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ALWAYS_SEARCH_USER_PATHS = NO; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_WARN_BOOL_CONVERSION = YES; 286 | CLANG_WARN_CONSTANT_CONVERSION = YES; 287 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 288 | CLANG_WARN_EMPTY_BODY = YES; 289 | CLANG_WARN_ENUM_CONVERSION = YES; 290 | CLANG_WARN_INFINITE_RECURSION = YES; 291 | CLANG_WARN_INT_CONVERSION = YES; 292 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 293 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 297 | COPY_PHASE_STRIP = NO; 298 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 299 | ENABLE_NS_ASSERTIONS = NO; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | GCC_C_LANGUAGE_STANDARD = gnu99; 302 | GCC_NO_COMMON_BLOCKS = YES; 303 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 304 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 305 | GCC_WARN_UNDECLARED_SELECTOR = YES; 306 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 307 | GCC_WARN_UNUSED_FUNCTION = YES; 308 | GCC_WARN_UNUSED_VARIABLE = YES; 309 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 310 | MTL_ENABLE_DEBUG_INFO = NO; 311 | SDKROOT = iphoneos; 312 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 313 | TARGETED_DEVICE_FAMILY = "1,2"; 314 | VALIDATE_PRODUCT = YES; 315 | }; 316 | name = Release; 317 | }; 318 | 291128A71BADBFBD00C66827 /* Debug */ = { 319 | isa = XCBuildConfiguration; 320 | baseConfigurationReference = CC33DCD894C03515A6C49C21 /* Pods-WalkerDemo.debug.xcconfig */; 321 | buildSettings = { 322 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 323 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 324 | INFOPLIST_FILE = Walker/Info.plist; 325 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 326 | PRODUCT_BUNDLE_IDENTIFIER = no.hyper.Walker; 327 | PRODUCT_NAME = WalkerDemo; 328 | SWIFT_VERSION = 3.0; 329 | }; 330 | name = Debug; 331 | }; 332 | 291128A81BADBFBD00C66827 /* Release */ = { 333 | isa = XCBuildConfiguration; 334 | baseConfigurationReference = E928746C7CCBBA6A32B1A167 /* Pods-WalkerDemo.release.xcconfig */; 335 | buildSettings = { 336 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 337 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 338 | INFOPLIST_FILE = Walker/Info.plist; 339 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 340 | PRODUCT_BUNDLE_IDENTIFIER = no.hyper.Walker; 341 | PRODUCT_NAME = WalkerDemo; 342 | SWIFT_VERSION = 3.0; 343 | }; 344 | name = Release; 345 | }; 346 | /* End XCBuildConfiguration section */ 347 | 348 | /* Begin XCConfigurationList section */ 349 | 2911288F1BADBFBD00C66827 /* Build configuration list for PBXProject "Walker" */ = { 350 | isa = XCConfigurationList; 351 | buildConfigurations = ( 352 | 291128A41BADBFBD00C66827 /* Debug */, 353 | 291128A51BADBFBD00C66827 /* Release */, 354 | ); 355 | defaultConfigurationIsVisible = 0; 356 | defaultConfigurationName = Release; 357 | }; 358 | 291128A61BADBFBD00C66827 /* Build configuration list for PBXNativeTarget "WalkerDemo" */ = { 359 | isa = XCConfigurationList; 360 | buildConfigurations = ( 361 | 291128A71BADBFBD00C66827 /* Debug */, 362 | 291128A81BADBFBD00C66827 /* Release */, 363 | ); 364 | defaultConfigurationIsVisible = 0; 365 | defaultConfigurationName = Release; 366 | }; 367 | /* End XCConfigurationList section */ 368 | }; 369 | rootObject = 2911288C1BADBFBD00C66827 /* Project object */; 370 | } 371 | -------------------------------------------------------------------------------- /Demo/Walker/Walker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Walker/Walker.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/Walker/Walker/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | lazy var rootViewController: UIViewController = ViewController() 7 | var window: UIWindow? 8 | 9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 10 | window = UIWindow() 11 | window?.rootViewController = rootViewController 12 | window?.makeKeyAndVisible() 13 | 14 | return true 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "3x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "size" : "40x40", 25 | "idiom" : "iphone", 26 | "filename" : "Icon-40@3x.png", 27 | "scale" : "3x" 28 | }, 29 | { 30 | "size" : "57x57", 31 | "idiom" : "iphone", 32 | "filename" : "Icon.png", 33 | "scale" : "1x" 34 | }, 35 | { 36 | "size" : "57x57", 37 | "idiom" : "iphone", 38 | "filename" : "Icon@2x.png", 39 | "scale" : "2x" 40 | }, 41 | { 42 | "size" : "60x60", 43 | "idiom" : "iphone", 44 | "filename" : "Icon-60@2x.png", 45 | "scale" : "2x" 46 | }, 47 | { 48 | "size" : "60x60", 49 | "idiom" : "iphone", 50 | "filename" : "Icon-60@3x.png", 51 | "scale" : "3x" 52 | }, 53 | { 54 | "size" : "29x29", 55 | "idiom" : "ipad", 56 | "filename" : "Icon-Small.png", 57 | "scale" : "1x" 58 | }, 59 | { 60 | "idiom" : "ipad", 61 | "size" : "29x29", 62 | "scale" : "2x" 63 | }, 64 | { 65 | "size" : "40x40", 66 | "idiom" : "ipad", 67 | "filename" : "Icon-40.png", 68 | "scale" : "1x" 69 | }, 70 | { 71 | "size" : "40x40", 72 | "idiom" : "ipad", 73 | "filename" : "Icon-40@2x.png", 74 | "scale" : "2x" 75 | }, 76 | { 77 | "size" : "50x50", 78 | "idiom" : "ipad", 79 | "filename" : "Icon-Small-50.png", 80 | "scale" : "1x" 81 | }, 82 | { 83 | "size" : "50x50", 84 | "idiom" : "ipad", 85 | "filename" : "Icon-Small-50@2x.png", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "size" : "72x72", 90 | "idiom" : "ipad", 91 | "filename" : "Icon-72.png", 92 | "scale" : "1x" 93 | }, 94 | { 95 | "size" : "72x72", 96 | "idiom" : "ipad", 97 | "filename" : "Icon-72@2x.png", 98 | "scale" : "2x" 99 | }, 100 | { 101 | "size" : "76x76", 102 | "idiom" : "ipad", 103 | "filename" : "Icon-76.png", 104 | "scale" : "1x" 105 | }, 106 | { 107 | "size" : "76x76", 108 | "idiom" : "ipad", 109 | "filename" : "Icon-76@2x.png", 110 | "scale" : "2x" 111 | }, 112 | { 113 | "size" : "83.5x83.5", 114 | "idiom" : "ipad", 115 | "filename" : "Icon-83.5@2x.png", 116 | "scale" : "2x" 117 | }, 118 | { 119 | "size" : "24x24", 120 | "idiom" : "watch", 121 | "scale" : "2x", 122 | "role" : "notificationCenter", 123 | "subtype" : "38mm" 124 | }, 125 | { 126 | "size" : "27.5x27.5", 127 | "idiom" : "watch", 128 | "scale" : "2x", 129 | "role" : "notificationCenter", 130 | "subtype" : "42mm" 131 | }, 132 | { 133 | "size" : "29x29", 134 | "idiom" : "watch", 135 | "filename" : "Icon-Small@2x.png", 136 | "role" : "companionSettings", 137 | "scale" : "2x" 138 | }, 139 | { 140 | "size" : "29x29", 141 | "idiom" : "watch", 142 | "filename" : "Icon-Small@3x.png", 143 | "role" : "companionSettings", 144 | "scale" : "3x" 145 | }, 146 | { 147 | "size" : "40x40", 148 | "idiom" : "watch", 149 | "scale" : "2x", 150 | "role" : "appLauncher", 151 | "subtype" : "38mm" 152 | }, 153 | { 154 | "size" : "86x86", 155 | "idiom" : "watch", 156 | "scale" : "2x", 157 | "role" : "quickLook", 158 | "subtype" : "38mm" 159 | }, 160 | { 161 | "size" : "98x98", 162 | "idiom" : "watch", 163 | "scale" : "2x", 164 | "role" : "quickLook", 165 | "subtype" : "42mm" 166 | } 167 | ], 168 | "info" : { 169 | "version" : 1, 170 | "author" : "xcode" 171 | } 172 | } -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Demo/Walker/Walker/Assets.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /Demo/Walker/Walker/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 | 27 | 28 | -------------------------------------------------------------------------------- /Demo/Walker/Walker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Demo/Walker/Walker/ViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Walker 3 | 4 | class ViewController: UIViewController { 5 | 6 | struct Dimensions { 7 | static let viewSize: CGFloat = 80 8 | static let buttonHeight: CGFloat = 55 9 | static let buttonOffset: CGFloat = 120 10 | } 11 | 12 | struct Colors { 13 | static let mainColor = UIColor(red:0.04, green:0.57, blue:0.97, alpha:1) 14 | static let views = UIColor.white 15 | } 16 | 17 | lazy var animationButton: UIButton = { 18 | let button = UIButton() 19 | button.layer.cornerRadius = 1.5 20 | button.layer.borderColor = UIColor.white.cgColor 21 | button.layer.borderWidth = 1.5 22 | button.setTitle("Start animations".uppercased(), for: UIControlState()) 23 | button.setTitleColor(UIColor.white, for: UIControlState()) 24 | button.addTarget(self, action: #selector(ViewController.animationButtonDidPress(_:)), for: .touchUpInside) 25 | button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) 26 | 27 | return button 28 | }() 29 | 30 | let totalWidth = UIScreen.main.bounds.width 31 | let totalHeight = UIScreen.main.bounds.height 32 | var views: [UIView] = [] 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | 37 | view.backgroundColor = Colors.mainColor 38 | 39 | setupViews(5) 40 | setupFrames() 41 | 42 | views.forEach { view.addSubview($0) } 43 | view.addSubview(animationButton) 44 | } 45 | 46 | // MARK: - Actions 47 | 48 | func animationButtonDidPress(_ button: UIButton) { 49 | let finalValue = views.first?.frame.origin.x == 25 ? totalWidth - Dimensions.viewSize - 25 : 25 50 | 51 | for (index, view) in views.enumerated() { 52 | 53 | switch index { 54 | case 0: 55 | animate(view) { 56 | $0.x = finalValue 57 | } 58 | case 1: 59 | spring(view, spring: 200, friction: 10, mass: 10) { 60 | $0.x = finalValue 61 | } 62 | case 2: 63 | animate(view) { 64 | $0.x = finalValue 65 | }.chain(duration: 0.5) { 66 | $0.alpha = 0 67 | }.chain(duration: 0.5) { 68 | $0.alpha = 1 69 | } 70 | case 3: 71 | animate(view, duration: 1, curve: .bezier(1, 0.4, 1, 0.5)) { 72 | $0.x = finalValue 73 | } 74 | case 4: 75 | spring(view, delay: 0.5, spring: 800, friction: 10, mass: 10) { 76 | $0.x = finalValue 77 | } 78 | default: 79 | animate(view, curve: .easeInOut) { 80 | $0.x = finalValue 81 | } 82 | } 83 | } 84 | } 85 | 86 | // MARK: - Configuration 87 | 88 | func setupViews(_ count: Int) { 89 | for index in 0.. Copyright (c) 2015 Ramon Gilabert 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining 6 | > a copy of this software and associated documentation files (the 7 | > "Software"), to deal in the Software without restriction, including 8 | > without limitation the rights to use, copy, modify, merge, publish, 9 | > distribute, sublicense, and/or sell copies of the Software, and to 10 | > permit persons to whom the Software is furnished to do so, subject to 11 | > the following conditions: 12 | > 13 | > The above copyright notice and this permission notice shall be 14 | > included in all copies or substantial portions of the Software. 15 | > 16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Others/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Walker](https://github.com/RamonGilabert/Walker/blob/master/Resources/header-image.png) 2 | 3 | [![Carthage](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 4 | [![Version](https://img.shields.io/cocoapods/v/Walker.svg?style=flat)](http://cocoadocs.org/docsets/Walker) 5 | [![License](https://img.shields.io/cocoapods/l/Walker.svg?style=flat)](http://cocoadocs.org/docsets/Walker) 6 | [![Platform](https://img.shields.io/cocoapods/p/Walker.svg?style=flat)](http://cocoadocs.org/docsets/Walker) 7 | [![Documentation](https://img.shields.io/cocoapods/metrics/doc-percent/Walker.svg?style=flat)](http://cocoadocs.org/docsets/Walker) 8 | ![Swift](https://img.shields.io/badge/%20in-swift%203.0-orange.svg) 9 | 10 | ## The story 11 | 12 | Seeing the animations behind Paper, or the transitions behind Mail, being in a world of flat design and transitions, user interaction, app behavior and responsiveness are key, we need good and fast animations to delight our users in every tap, every scroll, every moment that they spend in our app. 13 | 14 | After lots of apps, designs and animations, after trying some of the most famous web frameworks and after I learnt FramerJS, which has one of the most beautiful springs I've seen. I'm building a collection of my knowledge, as I said when I was learning iOS development I would do, let me present **Walker** to you, an animation engine and library. 15 | 16 | **Walker** has a bohemian companion that wanders with him, **[Morgan](https://github.com/RamonGilabert/Morgan)**, a set of animations that will make your life easier when developing iOS apps. Note that Morgan is unfinished and always improving. 17 | 18 | ## Code 19 | 20 | **Walker** has different types of use cases and behaviors, you can either have a chain of animations with different blocks and callbacks, or reuse animations and apply them in different cases. 21 | 22 | ```swift 23 | animate(view) { 24 | $0.alpha = 1 25 | }.then { 26 | print("First animation done") 27 | }.chain { 28 | $0.width = 300 29 | }.finally { 30 | print("Animations done") 31 | } 32 | ``` 33 | 34 | Inside every animation there are different curves, the basic ones, which are Linear, Ease, EaseIn, EaseOut and EaseInOut, a custom Cubic Bezier and a Spring animation. 35 | 36 | #### Cubic Bezier 37 | 38 | Considering Linear, Ease, EaseIn, EaseOut and EaseInOut cubic animations, the following animation will just have the Bezier one, even though everything is called the same way. 39 | 40 | ```swift 41 | animate(view, curve: .Bezier(1, 0.4, 1, 0.4)) { 42 | $0.x = 100 43 | } 44 | ``` 45 | 46 | ![Bezier](https://github.com/RamonGilabert/Walker/blob/master/Resources/bezier.gif) 47 | 48 | #### Springs 49 | 50 | Springs are the most beautiful animations in the spectrum, taking inspiration of the curve used in FramerJS, you'll have a look alike feel that you are going to be able to configure like the following set. 51 | 52 | ```swift 53 | spring(view, spring: 200, friction: 10, mass: 10) { 54 | $0.x = 40 55 | } 56 | ``` 57 | 58 | ![Spring](https://github.com/RamonGilabert/Walker/blob/master/Resources/spring.gif) 59 | 60 | #### Chains 61 | 62 | As stated in the first example, you can chain animations, but not only animations with the same curve, every block has an independent status, so you'll be able to chain springs and bezier animations, being notified when everything finishes if you want. 63 | 64 | ```swift 65 | spring(view, spring: 200, friction: 10, mass: 10) { 66 | $0.x = 100 67 | }.chain { 68 | $0.x = 0 69 | } 70 | ``` 71 | 72 | ![Chain](https://github.com/RamonGilabert/Walker/blob/master/Resources/chain.gif) 73 | 74 | #### Create your own 75 | 76 | It wouldn't be a good animation engine if you couldn't reuse animations, there's a component inside the engine called Still, this one will talk to the background motor and will provide you with a `CAKeyframeAnimation`, just by calling this: 77 | 78 | ```swift 79 | let animation = Still.bezier(.PositionX, curve: .Bezier(1, 0.4, 1, 0.4)) 80 | ``` 81 | 82 | Still can have, as the engine above, Cubic Bezier and Spring animations inside, each one configured differently. Note also that this will provide a layer animation. 83 | 84 | Finally, this animation won't be tight to a final value or to any view, so you can reuse it across by distilling it: 85 | 86 | ```swift 87 | distill((animation: animation, final: 100), view: view) 88 | ``` 89 | 90 | Distill works with as many animations at a time as you want. 91 | 92 | ![Bezier](https://github.com/RamonGilabert/Walker/blob/master/Resources/bezier.gif) 93 | 94 | #### More questions? 95 | 96 | Have more questions and want to see more implementation in detail? We have a [demo](https://github.com/RamonGilabert/Walker/tree/master/Demo/Walker) for you. Inside it you'll find some different animations applied into different views, you can check the file right [here](https://github.com/RamonGilabert/Walker/blob/master/Demo/Walker/Walker/ViewController.swift#L51). If you still have something unclear, don't hesitate to [contact me](mailto:ramon.gilabert.llop@gmail.com) or [open an issue](https://github.com/RamonGilabert/Walker/issues). 97 | 98 | ## Installation 99 | 100 | **Walker** is available through [CocoaPods](http://cocoapods.org). To install 101 | it, simply add the following line to your Podfile: 102 | 103 | ```ruby 104 | pod 'Walker' 105 | ``` 106 | 107 | **Walker** is also available through [Carthage](https://github.com/Carthage/Carthage). To install just write into your Cartfile: 108 | 109 | ```ruby 110 | github 'RamonGilabert/Walker' 111 | ``` 112 | 113 | ## Upcoming features 114 | 115 | Check out the [ROADMAP](https://github.com/RamonGilabert/Walker/blob/master/ROADMAP.md) file to see the upcoming features that we are thinking to implement and don't hesitate to [open an issue](https://github.com/RamonGilabert/Walker/issues) or make a PR with a proposal in the roadmap. 116 | 117 | ## Author 118 | 119 | Ramon Gilabert with ♥️ 120 | 121 | ## Contribute 122 | 123 | We would love you to contribute to **Walker**, check the [CONTRIBUTING](https://github.com/RamonGilabert/Walker/blob/master/CONTRIBUTING.md) file for more info. 124 | 125 | ## License 126 | 127 | **Walker** is available under the MIT license. See the [LICENSE](https://github.com/RamonGilabert/Walker/blob/master/LICENSE.md) file for more info. 128 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Always walking 2 | 3 | This is the roadmap for Walker, there's always things to improve, things to do better, things to refactor. 4 | 5 | - [ ] Add more functionality in terms of Still animations. 6 | - [ ] Implement back spring reusable animations. 7 | - [ ] Improve the API to have just one method when Apple fixes the bug of the method signatures. 8 | - [ ] Always refactor. 9 | - [ ] Remove the copy of the code when Swift evolves. 10 | - [ ] Report bug of the chain thing that it doesn't recognize the signature. 11 | 12 | ## Completed 13 | 14 | - [x] Spring animation. 15 | - [x] Width, height and size animations. 16 | - [x] Change the name. 17 | - [x] Add Swift proposal to add arrays as a back for closures. 18 | - [x] Add animation options. 19 | - [x] Call animate two times or more, so add a queue. 20 | - [x] Be careful when you tap two animate multiple times. (Add a flag to lock the main methods). 21 | - [x] Multiple animate or spring blocks. 22 | - [x] Document the code. 23 | - [x] Document bake. 24 | - [x] Add an easy way to create animations, spring and bezier's. 25 | - [x] Cancel all animations. 26 | - [x] Apply bounce in the Spring. 27 | - [x] Finish the frame animation. 28 | -------------------------------------------------------------------------------- /Resources/Design.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Resources/Design.sketch -------------------------------------------------------------------------------- /Resources/bezier.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Resources/bezier.gif -------------------------------------------------------------------------------- /Resources/chain.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Resources/chain.gif -------------------------------------------------------------------------------- /Resources/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Resources/demo.gif -------------------------------------------------------------------------------- /Resources/header-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Resources/header-image.png -------------------------------------------------------------------------------- /Resources/spring.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Resources/spring.gif -------------------------------------------------------------------------------- /Source/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamonGilabert/Walker/ccfaa24212e5ef57bc40259c63db6ffea4d499b4/Source/.gitkeep -------------------------------------------------------------------------------- /Source/Background/Animation.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public struct Animation { 4 | 5 | public enum Curve { 6 | case linear 7 | case ease 8 | case easeIn 9 | case easeOut 10 | case easeInOut 11 | case bezier(Float, Float, Float, Float) 12 | } 13 | 14 | public enum Options { 15 | case reverse 16 | case `repeat`(Float) 17 | } 18 | 19 | public enum Spring { 20 | case spring 21 | case bounce 22 | } 23 | 24 | public enum Property: String { 25 | case PositionX = "position.x" 26 | case PositionY = "position.y" 27 | case Opacity = "opacity" 28 | case Origin = "position" 29 | case Width = "bounds.size.width" 30 | case Height = "bounds.size.height" 31 | case Size = "bounds.size" 32 | case Frame = "bounds" 33 | case CornerRadius = "cornerRadius" 34 | case Transform = "transform" 35 | } 36 | 37 | static func points(_ curve: Curve) -> [Float] { 38 | switch curve { 39 | case .linear: 40 | return [0, 0, 1, 1] 41 | case .ease: 42 | return [0.25, 0.1, 0.25, 1] 43 | case .easeIn: 44 | return [0.42, 0, 1, 1] 45 | case .easeOut: 46 | return [0, 0, 0.58, 1] 47 | case .easeInOut: 48 | return [0.42, 0, 0.58, 1] 49 | case let .bezier(x, y, z, p): 50 | return [x, y, z, p] 51 | } 52 | } 53 | 54 | static func propertyValue(_ property: Property, layer: CALayer) -> NSValue { 55 | switch property { 56 | case .PositionX: 57 | return layer.position.x as NSValue 58 | case .PositionY: 59 | return layer.position.y as NSValue 60 | case .Opacity: 61 | return layer.opacity as NSValue 62 | case .Origin: 63 | return NSValue(cgPoint: layer.position) 64 | case .Width: 65 | return layer.bounds.width as NSValue 66 | case .Height: 67 | return layer.bounds.height as NSValue 68 | case .Size: 69 | return NSValue(cgSize: layer.bounds.size) 70 | case .Frame: 71 | return NSValue(cgRect: layer.frame) 72 | case .CornerRadius: 73 | return layer.cornerRadius as NSValue 74 | case .Transform: 75 | return NSValue(caTransform3D: layer.transform) 76 | } 77 | } 78 | 79 | static func values(_ property: Property, to: NSValue, layer: CALayer) -> (finalValue: [CGFloat], initialValue: [CGFloat]) { 80 | switch property { 81 | case .PositionX: 82 | guard let to = to as? CGFloat else { return ([0], [layer.position.x]) } 83 | return ([to], [layer.position.x]) 84 | case .PositionY: 85 | guard let to = to as? CGFloat else { return ([0], [layer.position.x]) } 86 | return ([to], [layer.position.y]) 87 | case .Opacity: 88 | guard let to = to as? CGFloat else { return ([1], [1]) } 89 | return ([to], [CGFloat(layer.opacity)]) 90 | case .Origin: 91 | return ([to.cgPointValue.x, to.cgPointValue.y], [layer.position.x, layer.position.y]) 92 | case .Width: 93 | return ([to.cgSizeValue.width], [layer.bounds.width]) 94 | case .Height: 95 | return ([to.cgSizeValue.height], [layer.bounds.height]) 96 | case .Size: 97 | return ([to.cgSizeValue.width, to.cgSizeValue.height], [layer.bounds.width, layer.bounds.height]) 98 | case .Frame: 99 | return ([to.cgRectValue.origin.x, to.cgRectValue.origin.y, 100 | to.cgRectValue.size.width, to.cgRectValue.size.height], 101 | [layer.position.x, layer.position.y, layer.frame.size.width, layer.frame.size.height]) 102 | case .CornerRadius: 103 | return ([to as! CGFloat], [layer.cornerRadius]) 104 | case .Transform: 105 | return ([to.caTransform3DValue.m11, to.caTransform3DValue.m12, 106 | to.caTransform3DValue.m13, to.caTransform3DValue.m14, 107 | to.caTransform3DValue.m21, to.caTransform3DValue.m22, 108 | to.caTransform3DValue.m23, to.caTransform3DValue.m24, 109 | to.caTransform3DValue.m31, to.caTransform3DValue.m32, 110 | to.caTransform3DValue.m33, to.caTransform3DValue.m34, 111 | to.caTransform3DValue.m41, to.caTransform3DValue.m42, 112 | to.caTransform3DValue.m43, to.caTransform3DValue.m44], 113 | 114 | [layer.transform.m11, layer.transform.m12, layer.transform.m13, 115 | layer.transform.m14, layer.transform.m21, layer.transform.m22, 116 | layer.transform.m23, layer.transform.m24, layer.transform.m31, 117 | layer.transform.m32, layer.transform.m33, layer.transform.m34, 118 | layer.transform.m41, layer.transform.m42, layer.transform.m43, 119 | layer.transform.m44]) 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Source/Background/Distill.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class Distill { 4 | 5 | internal let springAnimationStep: CFTimeInterval = 0.001 6 | internal var spring: CGFloat = 300 7 | internal var friction: CGFloat = 30 8 | internal var mass: CGFloat = 6 9 | internal var tolerance: CGFloat = 0.00001 10 | internal var springEnded = false 11 | internal var springTiming: CFTimeInterval = 0 12 | 13 | // MARK: - Cubic bezier 14 | 15 | static func bezier(_ property: Animation.Property, bezierPoints: [Float], duration: TimeInterval, options: [Animation.Options]) -> CAKeyframeAnimation { 16 | let animation = CAKeyframeAnimation(keyPath: property.rawValue) 17 | animation.duration = duration 18 | animation.isRemovedOnCompletion = false 19 | animation.fillMode = kCAFillModeForwards 20 | animation.isAdditive = false 21 | animation.isCumulative = false 22 | animation.timingFunction = CAMediaTimingFunction(controlPoints: 23 | bezierPoints[0], bezierPoints[1], bezierPoints[2], bezierPoints[3]) 24 | 25 | options.forEach { option in 26 | switch option { 27 | case .reverse : 28 | animation.autoreverses = true 29 | case let .repeat(t): 30 | animation.repeatCount = t 31 | } 32 | } 33 | 34 | return animation 35 | } 36 | 37 | // MARK: - Spring 38 | 39 | static func spring(_ property: Animation.Property, type: Animation.Spring) -> CAKeyframeAnimation { 40 | let animation = CAKeyframeAnimation(keyPath: property.rawValue) 41 | animation.isRemovedOnCompletion = false 42 | animation.fillMode = kCAFillModeForwards 43 | 44 | return animation 45 | } 46 | 47 | func calculateSpring(_ property: Animation.Property, finalValue: NSValue, 48 | layer: CALayer, type: Animation.Spring, 49 | spring: (spring: CGFloat, friction: CGFloat, mass: CGFloat, tolerance: CGFloat)) -> [NSValue] { 50 | 51 | let initialArray = Animation.values(property, to: finalValue, layer: layer).initialValue 52 | let finalArray = Animation.values(property, to: finalValue, layer: layer).finalValue 53 | var distances: [CGFloat] = [] 54 | var increments: [CGFloat] = [] 55 | var proposedValues: [CGFloat] = [] 56 | var stepValues: [CGFloat] = [] 57 | var finalValues: [NSValue] = [] 58 | 59 | self.spring = spring.spring 60 | self.friction = spring.friction 61 | self.mass = spring.mass 62 | self.tolerance = spring.tolerance 63 | 64 | springEnded = false 65 | springTiming = 0 66 | 67 | for (index, element) in initialArray.enumerated() { 68 | distances.append(finalArray[index] - element) 69 | increments.append(abs(distances[index]) * tolerance) 70 | proposedValues.append(0) 71 | stepValues.append(0) 72 | } 73 | 74 | while !springEnded { 75 | springEnded = true 76 | for (index, element) in initialArray.enumerated() { 77 | guard element != 0 else { continue } 78 | proposedValues[index] = initialArray[index] + (distances[index] - springPosition(distances[index], time: springTiming, from: element)) 79 | 80 | if type == .bounce && proposedValues[index] >= finalArray[index] { 81 | proposedValues[index] = (finalArray[index] * 2) - proposedValues[index] 82 | } 83 | 84 | if springEnded { 85 | springEnded = springStatusEnded(stepValues[index], proposed: proposedValues[index], 86 | to: finalArray[index], increment: increments[index]) 87 | } 88 | } 89 | 90 | guard !springEnded else { break } 91 | 92 | var value = NSValue() 93 | stepValues = proposedValues 94 | 95 | switch property { 96 | case .PositionX, .PositionY, .Width, .Height, .CornerRadius, .Opacity: 97 | value = stepValues[0] as NSValue 98 | case .Origin: 99 | value = NSValue(cgPoint: CGPoint(x: stepValues[0], y: stepValues[1])) 100 | case .Size: 101 | value = NSValue(cgSize: CGSize(width: stepValues[0], height: stepValues[1])) 102 | case .Frame: 103 | value = NSValue(cgRect: CGRect(x: stepValues[0], y: stepValues[1], width: stepValues[2], height: stepValues[3])) 104 | case .Transform: 105 | var transform = CATransform3DIdentity 106 | transform.m11 = stepValues[0] 107 | transform.m12 = stepValues[1] 108 | transform.m13 = stepValues[2] 109 | transform.m14 = stepValues[3] 110 | transform.m21 = stepValues[4] 111 | transform.m22 = stepValues[5] 112 | transform.m23 = stepValues[6] 113 | transform.m24 = stepValues[7] 114 | transform.m31 = stepValues[8] 115 | transform.m32 = stepValues[9] 116 | transform.m33 = stepValues[10] 117 | transform.m34 = stepValues[11] 118 | transform.m41 = stepValues[12] 119 | transform.m42 = stepValues[13] 120 | transform.m43 = stepValues[14] 121 | transform.m44 = stepValues[15] 122 | value = NSValue(caTransform3D: transform) 123 | } 124 | 125 | finalValues.append(value) 126 | springTiming += springAnimationStep 127 | } 128 | 129 | return finalValues 130 | } 131 | 132 | fileprivate func springPosition(_ distance: CGFloat, time: CFTimeInterval, from: CGFloat) -> CGFloat { 133 | let gamma = friction / (2 * mass) 134 | let angularVelocity = sqrt((spring / mass) - pow(gamma, 2)) * 4 135 | let position = exp(-gamma * CGFloat(time) * 5) * distance * cos(angularVelocity * CGFloat(time)) 136 | 137 | return position 138 | } 139 | 140 | fileprivate func springStatusEnded(_ previous: CGFloat, proposed: CGFloat, to: CGFloat, increment: CGFloat) -> Bool { 141 | return abs(proposed - previous) <= increment && abs(previous - to) <= increment 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Source/Constructors/Bezier.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /** 4 | Animation starts a series of blocks of animations, it can have multiple parameters. 5 | 6 | - Parameter delay: The delay that this chain should wait to be triggered. 7 | - Parameter duration: The duration of the animation block. 8 | - Parameter curve: A curve from the Animation.Curve series, it defaults to .Linear. 9 | - Parameter options: A set of options, for now .Reverse or .Repeat. 10 | 11 | - Returns: A series of ingredients that you can configure with all the animatable properties. 12 | */ 13 | @discardableResult public func animate(_ view: UIView, delay: TimeInterval = 0, duration: TimeInterval = 0.35, 14 | curve: Animation.Curve = .linear, 15 | options: [Animation.Options] = [], 16 | animations: (Ingredient) -> Void) -> Distillery { 17 | 18 | let builder = constructor([view], delay, duration, curve, options) 19 | animations(builder.ingredient[0]) 20 | 21 | validate(builder.distillery) 22 | 23 | return builder.distillery 24 | } 25 | 26 | /** 27 | Animation starts a series of blocks of animations, it can have multiple parameters. 28 | 29 | - Parameter delay: The delay that this chain should wait to be triggered. 30 | - Parameter duration: The duration of the animation block. 31 | - Parameter curve: A curve from the Animation.Curve series, it defaults to .Linear. 32 | - Parameter options: A set of options, for now .Reverse or .Repeat. 33 | 34 | - Returns: A series of ingredients that you can configure with all the animatable properties. 35 | */ 36 | @discardableResult public func animate(_ firstView: UIView, _ secondView: UIView, 37 | delay: TimeInterval = 0, duration: TimeInterval = 0.35, 38 | curve: Animation.Curve = .linear, options: [Animation.Options] = [], 39 | animations: (Ingredient, Ingredient) -> Void) -> Distillery { 40 | 41 | let builder = constructor([firstView, secondView], delay, duration, curve, options) 42 | animations(builder.ingredient[0], builder.ingredient[1]) 43 | 44 | validate(builder.distillery) 45 | 46 | return builder.distillery 47 | } 48 | 49 | /** 50 | Animation starts a series of blocks of animations, it can have multiple parameters. 51 | 52 | - Parameter delay: The delay that this chain should wait to be triggered. 53 | - Parameter duration: The duration of the animation block. 54 | - Parameter curve: A curve from the Animation.Curve series, it defaults to .Linear. 55 | - Parameter options: A set of options, for now .Reverse or .Repeat. 56 | 57 | - Returns: A series of ingredients that you can configure with all the animatable properties. 58 | */ 59 | @discardableResult public func animate(_ firstView: UIView, _ secondView: UIView, _ thirdView: UIView, 60 | delay: TimeInterval = 0, duration: TimeInterval = 0.35, 61 | curve: Animation.Curve = .linear, options: [Animation.Options] = [], 62 | animations: (Ingredient, Ingredient, Ingredient) -> Void) -> Distillery { 63 | 64 | let builder = constructor([firstView, secondView, thirdView], delay, duration, curve, options) 65 | animations(builder.ingredient[0], builder.ingredient[1], builder.ingredient[2]) 66 | 67 | validate(builder.distillery) 68 | 69 | return builder.distillery 70 | } 71 | 72 | /** 73 | Animation starts a series of blocks of animations, it can have multiple parameters. 74 | 75 | - Parameter delay: The delay that this chain should wait to be triggered. 76 | - Parameter duration: The duration of the animation block. 77 | - Parameter curve: A curve from the Animation.Curve series, it defaults to .Linear. 78 | - Parameter options: A set of options, for now .Reverse or .Repeat. 79 | 80 | - Returns: A series of ingredients that you can configure with all the animatable properties. 81 | */ 82 | @discardableResult public func animate(_ firstView: UIView, _ secondView: UIView, _ thirdView: UIView, 83 | _ fourthView: UIView, duration: TimeInterval = 0.35, 84 | delay: TimeInterval = 0, curve: Animation.Curve = .linear, options: [Animation.Options] = [], 85 | animations: (Ingredient, Ingredient, Ingredient, Ingredient) -> Void) -> Distillery { 86 | 87 | let builder = constructor([firstView, secondView, thirdView, fourthView], delay, duration, curve, options) 88 | animations(builder.ingredient[0], builder.ingredient[1], builder.ingredient[2], builder.ingredient[3]) 89 | 90 | validate(builder.distillery) 91 | 92 | return builder.distillery 93 | } 94 | 95 | // MARK: - Private helpers 96 | 97 | private func constructor(_ views: [UIView], _ delay: TimeInterval, 98 | _ duration: TimeInterval, _ curve: Animation.Curve, _ options: [Animation.Options]) -> (ingredient: [Ingredient], distillery: Distillery) { 99 | 100 | let distillery = Distillery() 101 | var ingredients: [Ingredient] = [] 102 | 103 | views.forEach { 104 | ingredients.append(Ingredient(distillery: distillery, view: $0, duration: duration, curve: curve, options: options)) 105 | } 106 | 107 | distillery.delays.append(delay) 108 | distillery.ingredients = [ingredients] 109 | 110 | return (ingredient: ingredients, distillery: distillery) 111 | } 112 | 113 | private func validate(_ distillery: Distillery) { 114 | var shouldProceed = true 115 | distilleries.forEach { 116 | if let ingredients = $0.ingredients.first, let ingredient = ingredients.first , ingredient.finalValues.isEmpty { 117 | shouldProceed = false 118 | return 119 | } 120 | } 121 | 122 | distillery.shouldProceed = shouldProceed 123 | 124 | if shouldProceed { 125 | distilleries.append(distillery) 126 | distillery.animate() 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Source/Constructors/Chain.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension Distillery { 4 | 5 | // MARK: - Bezier chains 6 | 7 | /** 8 | Chain gets executed when the first animate blocks of animations are done. 9 | 10 | - Parameter delay: The delay that this chain should wait to be triggered. 11 | - Parameter duration: The duration of the animation. 12 | - Parameter curve: The animation curve. 13 | - Parameter options: A set of options, for now .Reverse or .Repeat. 14 | 15 | - Returns: A series of ingredients that you can configure with all the animatable properties. 16 | */ 17 | @discardableResult public func chain(delay: TimeInterval = 0, duration: TimeInterval = 0.5, 18 | curve: Animation.Curve = .linear, options: [Animation.Options] = [], 19 | animations: (Ingredient) -> Void) -> Distillery { 20 | 21 | animations(bezier(1, delay, duration, curve, options)[0]) 22 | 23 | return self 24 | } 25 | 26 | /** 27 | Chain gets executed when the first animate blocks of animations are done. 28 | 29 | - Parameter delay: The delay that this chain should wait to be triggered. 30 | - Parameter duration: The duration of the animation. 31 | - Parameter curve: The animation curve. 32 | - Parameter options: A set of options, for now .Reverse or .Repeat. 33 | 34 | - Returns: A series of ingredients that you can configure with all the animatable properties. 35 | */ 36 | @discardableResult public func chains(delay: TimeInterval = 0, duration: TimeInterval = 0.5, 37 | curve: Animation.Curve = .linear, options: [Animation.Options] = [], 38 | animations: (Ingredient, Ingredient) -> Void) -> Distillery { 39 | 40 | let ingredients = bezier(2, delay, duration, curve, options) 41 | animations(ingredients[0], ingredients[1]) 42 | 43 | return self 44 | } 45 | 46 | /** 47 | Chain gets executed when the first animate blocks of animations are done. 48 | 49 | - Parameter delay: The delay that this chain should wait to be triggered. 50 | - Parameter duration: The duration of the animation. 51 | - Parameter curve: The animation curve. 52 | - Parameter options: A set of options, for now .Reverse or .Repeat. 53 | 54 | - Returns: A series of ingredients that you can configure with all the animatable properties. 55 | */ 56 | @discardableResult public func chainTwo(delay: TimeInterval = 0, duration: TimeInterval = 0.5, 57 | curve: Animation.Curve = .linear, options: [Animation.Options] = [], 58 | animations: (Ingredient, Ingredient, Ingredient) -> Void) -> Distillery { 59 | 60 | let ingredients = bezier(3, delay, duration, curve, options) 61 | animations(ingredients[0], ingredients[1], ingredients[2]) 62 | 63 | return self 64 | } 65 | 66 | /** 67 | Chain gets executed when the first animate blocks of animations are done. 68 | 69 | - Parameter delay: The delay that this chain should wait to be triggered. 70 | - Parameter duration: The duration of the animation. 71 | - Parameter curve: The animation curve. 72 | - Parameter options: A set of options, for now .Reverse or .Repeat. 73 | 74 | - Returns: A series of ingredients that you can configure with all the animatable properties. 75 | */ 76 | @discardableResult public func chainsTwo(delay: TimeInterval = 0, duration: TimeInterval = 0.5, 77 | curve: Animation.Curve = .linear, options: [Animation.Options] = [], 78 | animations: (Ingredient, Ingredient, Ingredient, Ingredient) -> Void) -> Distillery { 79 | 80 | let ingredients = bezier(4, delay, duration, curve, options) 81 | animations(ingredients[0], ingredients[1], ingredients[2], ingredients[3]) 82 | 83 | return self 84 | } 85 | 86 | // MARK: - Spring chains 87 | 88 | /** 89 | Chain gets executed when the first animate blocks of animations are done. 90 | 91 | - Parameter delay: The delay that this chain should wait to be triggered. 92 | - Parameter spring: The value of the spring in the animation. 93 | - Parameter friction: The value of the friction that the layer will present. 94 | - Parameter mass: The value of the mass of the layer. 95 | - Parameter tolerance: The tolerance that will default to 0.0001. 96 | 97 | - Returns: A series of ingredients that you can configure with all the animatable properties. 98 | */ 99 | @discardableResult public func chain(delay: TimeInterval = 0, 100 | spring: CGFloat, friction: CGFloat, mass: CGFloat, tolerance: CGFloat = 0.0001, 101 | kind: Animation.Spring = .spring, animations: (Ingredient) -> Void) -> Distillery { 102 | 103 | animations(constructor(1, delay: delay, spring, friction, mass: mass, tolerance: tolerance, kind)[0]) 104 | 105 | return self 106 | } 107 | 108 | /** 109 | Chain gets executed when the first animate blocks of animations are done. 110 | 111 | - Parameter delay: The delay that this chain should wait to be triggered. 112 | - Parameter spring: The value of the spring in the animation. 113 | - Parameter friction: The value of the friction that the layer will present. 114 | - Parameter mass: The value of the mass of the layer. 115 | - Parameter tolerance: The tolerance that will default to 0.0001. 116 | 117 | - Returns: A series of ingredients that you can configure with all the animatable properties. 118 | */ 119 | @discardableResult public func chains(delay: TimeInterval = 0, spring: CGFloat, 120 | friction: CGFloat, mass: CGFloat, tolerance: CGFloat = 0.0001, 121 | kind: Animation.Spring = .spring, animations: (Ingredient, Ingredient) -> Void) -> Distillery { 122 | 123 | let ingredients = constructor(2, delay: delay, spring, friction, mass: mass, tolerance: tolerance, kind) 124 | animations(ingredients[0], ingredients[1]) 125 | 126 | return self 127 | } 128 | 129 | /** 130 | Chain gets executed when the first animate blocks of animations are done. 131 | 132 | - Parameter delay: The delay that this chain should wait to be triggered. 133 | - Parameter spring: The value of the spring in the animation. 134 | - Parameter friction: The value of the friction that the layer will present. 135 | - Parameter mass: The value of the mass of the layer. 136 | - Parameter tolerance: The tolerance that will default to 0.0001. 137 | 138 | - Returns: A series of ingredients that you can configure with all the animatable properties. 139 | */ 140 | @discardableResult public func chainTwo(delay: TimeInterval = 0, spring: CGFloat, friction: CGFloat, 141 | mass: CGFloat, tolerance: CGFloat = 0.0001, kind: Animation.Spring = .spring, 142 | animations: (Ingredient, Ingredient, Ingredient) -> Void) -> Distillery { 143 | 144 | let ingredients = constructor(3, delay: delay, spring, friction, mass: mass, tolerance: tolerance, kind) 145 | animations(ingredients[0], ingredients[1], ingredients[2]) 146 | 147 | return self 148 | } 149 | 150 | // MARK: - Private constructors 151 | 152 | fileprivate func bezier(_ value: Int, _ delay: TimeInterval, 153 | _ duration: TimeInterval, _ curve: Animation.Curve, _ options: [Animation.Options]) -> [Ingredient] { 154 | 155 | var animationIngredients: [Ingredient] = [] 156 | for index in 0.. [Ingredient] { 172 | 173 | var animationIngredients: [Ingredient] = [] 174 | for index in 0.. Void) -> Distillery { 16 | 17 | let builder = constructor([view], delay, spring, friction, mass, tolerance, kind) 18 | animations(builder.ingredients[0]) 19 | 20 | validate(builder.distillery) 21 | 22 | return builder.distillery 23 | } 24 | 25 | /** 26 | Spring starts a series of blocks of spring animations, it can have multiple parameters. 27 | 28 | - Parameter delay: The delay that this chain should wait to be triggered. 29 | - Parameter spring: The value of the spring in the animation. 30 | - Parameter friction: The value of the friction that the layer will present. 31 | - Parameter mass: The value of the mass of the layer. 32 | - Parameter tolerance: The tolerance that will default to 0.0001. 33 | 34 | - Returns: A series of ingredients that you can configure with all the animatable properties. 35 | */ 36 | @discardableResult public func spring(_ firstView: UIView, _ secondView: UIView, 37 | delay: TimeInterval = 0, spring: CGFloat, friction: CGFloat, 38 | mass: CGFloat, tolerance: CGFloat = 0.0001, kind: Animation.Spring = .spring, animations: (Ingredient, Ingredient) -> Void) -> Distillery { 39 | 40 | let builder = constructor([firstView, secondView], delay, spring, friction, mass, tolerance, kind) 41 | animations(builder.ingredients[0], builder.ingredients[1]) 42 | 43 | validate(builder.distillery) 44 | 45 | return builder.distillery 46 | } 47 | 48 | /** 49 | Spring starts a series of blocks of spring animations, it can have multiple parameters. 50 | 51 | - Parameter delay: The delay that this chain should wait to be triggered. 52 | - Parameter spring: The value of the spring in the animation. 53 | - Parameter friction: The value of the friction that the layer will present. 54 | - Parameter mass: The value of the mass of the layer. 55 | - Parameter tolerance: The tolerance that will default to 0.0001. 56 | 57 | - Returns: A series of ingredients that you can configure with all the animatable properties. 58 | */ 59 | @discardableResult public func spring(_ firstView: UIView, _ secondView: UIView, _ thirdView: UIView, 60 | delay: TimeInterval = 0, spring: CGFloat, friction: CGFloat, 61 | mass: CGFloat, tolerance: CGFloat = 0.0001, kind: Animation.Spring = .spring , animations: (Ingredient, Ingredient, Ingredient) -> Void) -> Distillery { 62 | 63 | let builder = constructor([firstView, secondView, thirdView], delay, spring, friction, mass, tolerance, kind) 64 | animations(builder.ingredients[0], builder.ingredients[1], builder.ingredients[2]) 65 | 66 | validate(builder.distillery) 67 | 68 | return builder.distillery 69 | } 70 | 71 | @discardableResult private func constructor(_ views: [UIView], _ delay: TimeInterval, _ spring: CGFloat, 72 | _ friction: CGFloat, _ mass: CGFloat, _ tolerance: CGFloat, _ calculation: Animation.Spring) -> (ingredients: [Ingredient], distillery: Distillery) { 73 | let distillery = Distillery() 74 | var ingredients: [Ingredient] = [] 75 | 76 | views.forEach { 77 | ingredients.append(Ingredient(distillery: distillery, view: $0, spring: spring, 78 | friction: friction, mass: mass, tolerance: tolerance, calculation: calculation)) 79 | } 80 | 81 | distillery.delays.append(delay) 82 | distillery.ingredients = [ingredients] 83 | 84 | return (ingredients: ingredients, distillery: distillery) 85 | } 86 | 87 | private func validate(_ distillery: Distillery) { 88 | var shouldProceed = true 89 | distilleries.forEach { 90 | if let ingredients = $0.ingredients.first, let ingredient = ingredients.first , ingredient.finalValues.isEmpty { 91 | shouldProceed = false 92 | return 93 | } 94 | } 95 | 96 | distillery.shouldProceed = shouldProceed 97 | 98 | if shouldProceed { 99 | distilleries.append(distillery) 100 | distillery.animate() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Source/Constructors/Still.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /** 4 | Distill performs all the animations added by the tuple. 5 | 6 | - Parameter sets: A touple with multiple animations and final values. 7 | - Parameter view: The view you want to apply the animation to. 8 | - Parameter key: An optional key to put in the animation. 9 | */ 10 | public func distill(_ sets: (animation: CAKeyframeAnimation, final: NSValue)..., view: UIView, key: String? = nil) { 11 | 12 | for set in sets { 13 | guard let keyPath = set.animation.keyPath, let property = Animation.Property(rawValue: keyPath), 14 | let presentedLayer = view.layer.presentation() else { break } 15 | 16 | if let _ = set.animation.timingFunction { 17 | set.animation.values = [Animation.propertyValue(property, layer: presentedLayer), set.final] 18 | } 19 | 20 | // TODO: Implement reusable springs. 21 | 22 | view.layer.add(set.animation, forKey: key) 23 | } 24 | } 25 | 26 | var stills: [Still] = [] 27 | 28 | public struct Still { 29 | 30 | /** 31 | Defines and returns an animation with some parameters. 32 | 33 | - Parameter property: The Animation.Property that you want to animate. 34 | - Parameter curve: The animation curve you want it to be, cannot be Spring. 35 | - Parameter duration: The duration of the animation, defaults to 0.5. 36 | - Parameter options: A set of options that can be .Reverse or .Repeat(). 37 | 38 | - Returns: A CAKeyframeAnimation you can modify. 39 | */ 40 | @discardableResult public static func bezier(_ property: Animation.Property, curve: Animation.Curve = .linear, 41 | duration: TimeInterval = 0.5, options: [Animation.Options] = []) -> CAKeyframeAnimation { 42 | 43 | return Distill.bezier(property, 44 | bezierPoints: Animation.points(curve), duration: duration, options: options) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Source/Distillery/Distillery.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | var distilleries: [Distillery] = [] 4 | 5 | public func closeDistilleries() { 6 | 7 | distilleries.forEach { $0.ingredients.forEach { $0.forEach { $0.view.layer.removeAllAnimations() } } } 8 | distilleries.removeAll() 9 | } 10 | 11 | open class Distillery: NSObject { 12 | 13 | var ingredients: [[Ingredient]] = [[]] 14 | var delays: [TimeInterval] = [] 15 | var closures: [(() -> Void)?] = [] 16 | var final: (() -> Void)? 17 | var shouldProceed = true 18 | 19 | /** 20 | Then gets called when the animation block above has ended. 21 | */ 22 | open func then(_ closure: @escaping () -> Void) -> Distillery { 23 | closures.append(closure) 24 | 25 | return self 26 | } 27 | 28 | /** 29 | Finally is the last method that gets called when the chain of animations is done. 30 | */ 31 | open func finally(_ closure: @escaping () -> Void) { 32 | final = closure 33 | } 34 | 35 | // MARK: - Animate 36 | 37 | func animate() { 38 | guard let delay = delays.first else { return } 39 | 40 | let time = DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 41 | DispatchQueue.main.asyncAfter(deadline: time) { 42 | guard let ingredient = self.ingredients.first else { return } 43 | 44 | for (_, ingredient) in ingredient.enumerated() { 45 | guard let presentedLayer = ingredient.view.layer.presentation() else { return } 46 | 47 | for (index, animation) in ingredient.animations.enumerated() { 48 | let property = ingredient.properties[index] 49 | 50 | if ingredient.kind == .bezier { 51 | animation.values?.insert(Animation.propertyValue(property, layer: presentedLayer), at: 0) 52 | } else if let value = ingredient.finalValues.first, let spring = ingredient.springs.first { 53 | let distill = Distill() 54 | 55 | animation.values = distill.calculateSpring(property, finalValue: value, 56 | layer: presentedLayer, type: ingredient.calculation, spring: spring) 57 | animation.duration = distill.springTiming 58 | 59 | ingredient.springs.removeFirst() 60 | } 61 | 62 | if !ingredient.finalValues.isEmpty { ingredient.finalValues.removeFirst() } 63 | ingredient.view.layer.add(animation, forKey: "animation-\(index)-\(self.description)") 64 | } 65 | } 66 | } 67 | } 68 | } 69 | 70 | extension Distillery: CAAnimationDelegate { 71 | 72 | // MARK: - Finish animation 73 | 74 | open func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 75 | guard var group = ingredients.first, let animation = anim as? CAKeyframeAnimation else { return } 76 | 77 | var index = 0 78 | var animationIndex = 0 79 | for (position, ingredient) in group.enumerated() { 80 | for (animationPosition, _) in ingredient.animations.enumerated() 81 | where ingredient.view.layer.animation(forKey: "animation-\(animationPosition)-\(self.description)") == animation { 82 | 83 | index = position 84 | animationIndex = animationPosition 85 | } 86 | } 87 | 88 | let ingredient = group[index] 89 | 90 | guard let layer = ingredient.view.layer.presentation() else { return } 91 | 92 | if ingredient.properties.contains(.Transform) { 93 | ingredient.view.layer.transform = layer.transform 94 | } 95 | 96 | if ingredient.properties.contains(.PositionX) 97 | || ingredient.properties.contains(.PositionY) 98 | || ingredient.properties.contains(.Origin) 99 | || ingredient.properties.contains(.Width) 100 | || ingredient.properties.contains(.Height) 101 | || ingredient.properties.contains(.Size) 102 | || ingredient.properties.contains(.Frame) { 103 | 104 | ingredient.view.layer.position = layer.position 105 | ingredient.view.layer.frame.size = layer.frame.size 106 | } 107 | 108 | ingredient.view.layer.cornerRadius = layer.cornerRadius 109 | ingredient.view.layer.opacity = layer.opacity 110 | 111 | ingredient.view.layer.removeAnimation(forKey: "animation-\(animationIndex)-\(self.description)") 112 | ingredient.animations.remove(at: animationIndex) 113 | ingredient.properties.remove(at: animationIndex) 114 | 115 | if ingredient.animations.isEmpty { 116 | group.remove(at: index) 117 | 118 | ingredients[0] = group 119 | } 120 | 121 | if group.isEmpty { 122 | ingredients.removeFirst() 123 | delays.removeFirst() 124 | animate() 125 | 126 | if let firstClosure = closures.first, let closure = firstClosure { 127 | closure() 128 | closures.removeFirst() 129 | } else if !closures.isEmpty { 130 | closures.removeFirst() 131 | } 132 | } 133 | 134 | if let final = final , ingredients.isEmpty { 135 | final() 136 | } 137 | 138 | if let index = distilleries.index(of: self) , ingredients.isEmpty { 139 | distilleries.remove(at: index) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Source/Distillery/Ingredient.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | open class Ingredient: Equatable { 4 | 5 | internal enum Kind { 6 | case bezier, spring 7 | } 8 | 9 | /** 10 | Changes the opacity of the layer. 11 | */ 12 | open var alpha: CGFloat { 13 | didSet { alpha(alpha) } 14 | } 15 | 16 | /** 17 | Changes the x position in the anchor point (0, 0). 18 | */ 19 | open var x: CGFloat { 20 | didSet { x(x) } 21 | } 22 | 23 | /** 24 | Changes the y position in the anchor point (0, 0). 25 | */ 26 | open var y: CGFloat { 27 | didSet { y(y) } 28 | } 29 | 30 | /** 31 | Changes the width of the layer. 32 | */ 33 | open var width: CGFloat { 34 | didSet { width(width) } 35 | } 36 | 37 | /** 38 | Changes the height of the layer. 39 | */ 40 | open var height: CGFloat { 41 | didSet { height(height) } 42 | } 43 | 44 | /** 45 | Changes the origin of the view in the anchor point (0, 0) 46 | */ 47 | open var origin: CGPoint { 48 | didSet { origin(origin.x, origin.y) } 49 | } 50 | 51 | /** 52 | Changes the size of the view. 53 | */ 54 | open var size: CGSize { 55 | didSet { size(size.width, size.height) } 56 | } 57 | 58 | /** 59 | Changes the frame of the view. 60 | */ 61 | open var frame: CGRect { 62 | didSet { frame(frame.origin.x, frame.origin.y, frame.width, frame.height) } 63 | } 64 | 65 | /** 66 | Changes the cornerRadius of the layer. 67 | */ 68 | open var radius: CGFloat { 69 | didSet { radius(radius) } 70 | } 71 | 72 | /** 73 | Apply a transform value, can be any `CGAffineTransform`. 74 | */ 75 | open var transform: CGAffineTransform { 76 | didSet { transform(transform) } 77 | } 78 | 79 | /** 80 | Apply a transform value, can be any `CATransform3D`. 81 | */ 82 | open var transform3D: CATransform3D { 83 | didSet { transform3D(transform3D) } 84 | } 85 | 86 | /** 87 | Changes the opacity of the layer. 88 | */ 89 | open func alpha(_ value: CGFloat) { 90 | animate(.Opacity, value as NSValue) 91 | } 92 | 93 | /** 94 | Changes the x position in the anchor point (0, 0). 95 | */ 96 | open func x(_ value: CGFloat) { 97 | animate(.PositionX, value + view.frame.width / 2 as NSValue) 98 | } 99 | 100 | /** 101 | Changes the y position in the anchor point (0, 0). 102 | */ 103 | open func y(_ value: CGFloat) { 104 | animate(.PositionY, value + view.frame.height / 2 as NSValue) 105 | } 106 | 107 | /** 108 | Changes the width of the layer. 109 | */ 110 | open func width(_ value: CGFloat) { 111 | animate(.Width, value as NSValue) 112 | animate(.PositionX, view.frame.origin.x + value / 2 as NSValue) 113 | } 114 | 115 | /** 116 | Changes the height of the layer. 117 | */ 118 | open func height(_ value: CGFloat) { 119 | animate(.Height, value as NSValue) 120 | animate(.PositionY, view.frame.origin.y + value / 2 as NSValue) 121 | } 122 | 123 | /** 124 | Changes the origin of the view in the anchor point (0, 0) 125 | */ 126 | open func origin(_ x: CGFloat, _ y: CGFloat) { 127 | animate(.Origin, NSValue(cgPoint: CGPoint(x: x + view.frame.width / 2, y: y + view.frame.height / 2))) 128 | } 129 | 130 | /** 131 | Changes the size of the view. 132 | */ 133 | open func size(_ width: CGFloat, _ height: CGFloat) { 134 | animate(.Size, NSValue(cgSize: CGSize(width: width, height: height))) 135 | animate(.Origin, NSValue(cgPoint: CGPoint(x: view.frame.origin.x + width / 2, y: view.frame.origin.y + height / 2))) 136 | } 137 | 138 | /** 139 | Changes the frame of the view. 140 | */ 141 | open func frame(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) { 142 | animate(.Size, NSValue(cgSize: CGSize(width: width, height: height))) 143 | animate(.Origin, NSValue(cgPoint: CGPoint(x: x + width / 2, y: y + height / 2))) 144 | } 145 | 146 | /** 147 | Changes the cornerRadius of the layer. 148 | */ 149 | open func radius(_ value: CGFloat) { 150 | animate(.CornerRadius, value as NSValue) 151 | } 152 | 153 | /** 154 | Apply a transform value, can be any `CGAffineTransform`. 155 | */ 156 | open func transform(_ value: CGAffineTransform) { 157 | animate(.Transform, NSValue(caTransform3D: CATransform3DMakeAffineTransform(value))) 158 | } 159 | 160 | /** 161 | Apply a transform value, can be any `CATransform3D`. 162 | */ 163 | open func transform3D(_ value: CATransform3D) { 164 | animate(.Transform, NSValue(caTransform3D: value)) 165 | } 166 | 167 | internal let view: UIView 168 | internal let duration: TimeInterval 169 | internal let curve: Animation.Curve 170 | internal let kind: Kind 171 | internal let calculation: Animation.Spring 172 | internal let spring: (spring: CGFloat, friction: CGFloat, mass: CGFloat, tolerance: CGFloat) 173 | internal let options: [Animation.Options] 174 | var animations: [CAKeyframeAnimation] = [] 175 | var properties: [Animation.Property] = [] 176 | var finalValues: [NSValue] = [] 177 | var springs: [(spring: CGFloat, friction: CGFloat, mass: CGFloat, tolerance: CGFloat)] = [] 178 | var distillery: Distillery 179 | 180 | init(distillery: Distillery, view: UIView, duration: TimeInterval, 181 | curve: Animation.Curve, options: [Animation.Options]) { 182 | 183 | self.distillery = distillery 184 | self.view = view 185 | self.duration = duration 186 | self.curve = curve 187 | self.kind = .bezier 188 | self.calculation = .spring 189 | self.options = options 190 | self.spring = (0, 0, 0, 0) 191 | self.alpha = view.alpha 192 | self.x = view.frame.origin.x 193 | self.y = view.frame.origin.y 194 | self.width = view.frame.width 195 | self.height = view.frame.height 196 | self.origin = view.frame.origin 197 | self.size = view.frame.size 198 | self.frame = view.frame 199 | self.radius = view.layer.cornerRadius 200 | self.transform = view.transform 201 | self.transform3D = view.layer.transform 202 | } 203 | 204 | init(distillery: Distillery, view: UIView, spring: CGFloat, friction: CGFloat, mass: CGFloat, 205 | tolerance: CGFloat, calculation: Animation.Spring, options: [Animation.Options] = []) { 206 | 207 | self.distillery = distillery 208 | self.view = view 209 | self.duration = 0 210 | self.curve = .linear 211 | self.kind = .spring 212 | self.calculation = calculation 213 | self.options = options 214 | self.spring = (spring, friction, mass, tolerance) 215 | self.alpha = view.alpha 216 | self.x = view.frame.origin.x 217 | self.y = view.frame.origin.y 218 | self.width = view.frame.width 219 | self.height = view.frame.height 220 | self.origin = view.frame.origin 221 | self.size = view.frame.size 222 | self.frame = view.frame 223 | self.radius = view.layer.cornerRadius 224 | self.transform = view.transform 225 | self.transform3D = view.layer.transform 226 | } 227 | 228 | func animate(_ property: Animation.Property, _ value: NSValue) { 229 | var animation = CAKeyframeAnimation() 230 | 231 | if kind == .bezier { 232 | animation = Distill.bezier(property, bezierPoints: Animation.points(curve), duration: duration, options: options) 233 | animation.values = [value] 234 | animation.delegate = distillery 235 | } else { 236 | animation = Distill.spring(property, type: .spring) 237 | animation.delegate = distillery 238 | } 239 | 240 | animations.append(animation) 241 | properties.append(property) 242 | finalValues.append(value) 243 | springs.append(spring) 244 | } 245 | } 246 | 247 | public func ==(lhs: Ingredient, rhs: Ingredient) -> Bool { 248 | return lhs.view == rhs.view && lhs.properties == rhs.properties && lhs.finalValues == rhs.finalValues 249 | } 250 | -------------------------------------------------------------------------------- /Walker.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Walker" 3 | s.summary = "A travel companion for your animations." 4 | s.version = "0.10.0" 5 | s.homepage = "https://github.com/RamonGilabert/Walker" 6 | s.license = 'MIT' 7 | s.author = { "Ramon Gilabert" => "ramon.gilabert.llop@gmail.com" } 8 | s.source = { :git => "https://github.com/RamonGilabert/Walker.git", :tag => s.version.to_s } 9 | s.social_media_url = 'https://twitter.com/RamonGilabert' 10 | s.platform = :ios, '8.0' 11 | s.requires_arc = true 12 | s.source_files = 'Source/**/*' 13 | s.frameworks = 'AVFoundation' 14 | end 15 | -------------------------------------------------------------------------------- /Walker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 292D71DE1C9493BC00CFC060 /* .gitkeep in Resources */ = {isa = PBXBuildFile; fileRef = 292D71D21C9493BC00CFC060 /* .gitkeep */; }; 11 | 292D71DF1C9493BC00CFC060 /* Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D71D41C9493BC00CFC060 /* Animation.swift */; }; 12 | 292D71E01C9493BC00CFC060 /* Distill.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D71D51C9493BC00CFC060 /* Distill.swift */; }; 13 | 292D71E11C9493BC00CFC060 /* Bezier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D71D71C9493BC00CFC060 /* Bezier.swift */; }; 14 | 292D71E21C9493BC00CFC060 /* Chain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D71D81C9493BC00CFC060 /* Chain.swift */; }; 15 | 292D71E31C9493BC00CFC060 /* Spring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D71D91C9493BC00CFC060 /* Spring.swift */; }; 16 | 292D71E41C9493BC00CFC060 /* Still.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D71DA1C9493BC00CFC060 /* Still.swift */; }; 17 | 292D71E51C9493BC00CFC060 /* Distillery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D71DC1C9493BC00CFC060 /* Distillery.swift */; }; 18 | 292D71E61C9493BC00CFC060 /* Ingredient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D71DD1C9493BC00CFC060 /* Ingredient.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 292D71C31C94930C00CFC060 /* Walker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Walker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 292D71D21C9493BC00CFC060 /* .gitkeep */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; 24 | 292D71D41C9493BC00CFC060 /* Animation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animation.swift; sourceTree = ""; }; 25 | 292D71D51C9493BC00CFC060 /* Distill.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Distill.swift; sourceTree = ""; }; 26 | 292D71D71C9493BC00CFC060 /* Bezier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bezier.swift; sourceTree = ""; }; 27 | 292D71D81C9493BC00CFC060 /* Chain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Chain.swift; sourceTree = ""; }; 28 | 292D71D91C9493BC00CFC060 /* Spring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Spring.swift; sourceTree = ""; }; 29 | 292D71DA1C9493BC00CFC060 /* Still.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Still.swift; sourceTree = ""; }; 30 | 292D71DC1C9493BC00CFC060 /* Distillery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Distillery.swift; sourceTree = ""; }; 31 | 292D71DD1C9493BC00CFC060 /* Ingredient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Ingredient.swift; sourceTree = ""; }; 32 | 29BB21A21D82B3BC00FA2D92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | 292D71BF1C94930C00CFC060 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | 292D71B91C94930C00CFC060 = { 47 | isa = PBXGroup; 48 | children = ( 49 | 29BB21A11D82B3BC00FA2D92 /* Others */, 50 | 292D71D11C9493BC00CFC060 /* Source */, 51 | 292D71C41C94930C00CFC060 /* Products */, 52 | ); 53 | sourceTree = ""; 54 | }; 55 | 292D71C41C94930C00CFC060 /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 292D71C31C94930C00CFC060 /* Walker.framework */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | 292D71D11C9493BC00CFC060 /* Source */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 292D71D21C9493BC00CFC060 /* .gitkeep */, 67 | 292D71D31C9493BC00CFC060 /* Background */, 68 | 292D71D61C9493BC00CFC060 /* Constructors */, 69 | 292D71DB1C9493BC00CFC060 /* Distillery */, 70 | ); 71 | path = Source; 72 | sourceTree = ""; 73 | }; 74 | 292D71D31C9493BC00CFC060 /* Background */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 292D71D41C9493BC00CFC060 /* Animation.swift */, 78 | 292D71D51C9493BC00CFC060 /* Distill.swift */, 79 | ); 80 | path = Background; 81 | sourceTree = ""; 82 | }; 83 | 292D71D61C9493BC00CFC060 /* Constructors */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 292D71D71C9493BC00CFC060 /* Bezier.swift */, 87 | 292D71D81C9493BC00CFC060 /* Chain.swift */, 88 | 292D71D91C9493BC00CFC060 /* Spring.swift */, 89 | 292D71DA1C9493BC00CFC060 /* Still.swift */, 90 | ); 91 | path = Constructors; 92 | sourceTree = ""; 93 | }; 94 | 292D71DB1C9493BC00CFC060 /* Distillery */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 292D71DC1C9493BC00CFC060 /* Distillery.swift */, 98 | 292D71DD1C9493BC00CFC060 /* Ingredient.swift */, 99 | ); 100 | path = Distillery; 101 | sourceTree = ""; 102 | }; 103 | 29BB21A11D82B3BC00FA2D92 /* Others */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 29BB21A21D82B3BC00FA2D92 /* Info.plist */, 107 | ); 108 | path = Others; 109 | sourceTree = ""; 110 | }; 111 | /* End PBXGroup section */ 112 | 113 | /* Begin PBXHeadersBuildPhase section */ 114 | 292D71C01C94930C00CFC060 /* Headers */ = { 115 | isa = PBXHeadersBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | ); 119 | runOnlyForDeploymentPostprocessing = 0; 120 | }; 121 | /* End PBXHeadersBuildPhase section */ 122 | 123 | /* Begin PBXNativeTarget section */ 124 | 292D71C21C94930C00CFC060 /* Walker */ = { 125 | isa = PBXNativeTarget; 126 | buildConfigurationList = 292D71CB1C94930C00CFC060 /* Build configuration list for PBXNativeTarget "Walker" */; 127 | buildPhases = ( 128 | 292D71BE1C94930C00CFC060 /* Sources */, 129 | 292D71BF1C94930C00CFC060 /* Frameworks */, 130 | 292D71C01C94930C00CFC060 /* Headers */, 131 | 292D71C11C94930C00CFC060 /* Resources */, 132 | ); 133 | buildRules = ( 134 | ); 135 | dependencies = ( 136 | ); 137 | name = Walker; 138 | productName = Walker; 139 | productReference = 292D71C31C94930C00CFC060 /* Walker.framework */; 140 | productType = "com.apple.product-type.framework"; 141 | }; 142 | /* End PBXNativeTarget section */ 143 | 144 | /* Begin PBXProject section */ 145 | 292D71BA1C94930C00CFC060 /* Project object */ = { 146 | isa = PBXProject; 147 | attributes = { 148 | LastUpgradeCheck = 0800; 149 | ORGANIZATIONNAME = BlackBox; 150 | TargetAttributes = { 151 | 292D71C21C94930C00CFC060 = { 152 | CreatedOnToolsVersion = 7.2.1; 153 | LastSwiftMigration = 0800; 154 | }; 155 | }; 156 | }; 157 | buildConfigurationList = 292D71BD1C94930C00CFC060 /* Build configuration list for PBXProject "Walker" */; 158 | compatibilityVersion = "Xcode 3.2"; 159 | developmentRegion = English; 160 | hasScannedForEncodings = 0; 161 | knownRegions = ( 162 | en, 163 | ); 164 | mainGroup = 292D71B91C94930C00CFC060; 165 | productRefGroup = 292D71C41C94930C00CFC060 /* Products */; 166 | projectDirPath = ""; 167 | projectRoot = ""; 168 | targets = ( 169 | 292D71C21C94930C00CFC060 /* Walker */, 170 | ); 171 | }; 172 | /* End PBXProject section */ 173 | 174 | /* Begin PBXResourcesBuildPhase section */ 175 | 292D71C11C94930C00CFC060 /* Resources */ = { 176 | isa = PBXResourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 292D71DE1C9493BC00CFC060 /* .gitkeep in Resources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXResourcesBuildPhase section */ 184 | 185 | /* Begin PBXSourcesBuildPhase section */ 186 | 292D71BE1C94930C00CFC060 /* Sources */ = { 187 | isa = PBXSourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | 292D71E11C9493BC00CFC060 /* Bezier.swift in Sources */, 191 | 292D71E31C9493BC00CFC060 /* Spring.swift in Sources */, 192 | 292D71E41C9493BC00CFC060 /* Still.swift in Sources */, 193 | 292D71E21C9493BC00CFC060 /* Chain.swift in Sources */, 194 | 292D71E61C9493BC00CFC060 /* Ingredient.swift in Sources */, 195 | 292D71E51C9493BC00CFC060 /* Distillery.swift in Sources */, 196 | 292D71DF1C9493BC00CFC060 /* Animation.swift in Sources */, 197 | 292D71E01C9493BC00CFC060 /* Distill.swift in Sources */, 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | }; 201 | /* End PBXSourcesBuildPhase section */ 202 | 203 | /* Begin XCBuildConfiguration section */ 204 | 292D71C91C94930C00CFC060 /* Debug */ = { 205 | isa = XCBuildConfiguration; 206 | buildSettings = { 207 | ALWAYS_SEARCH_USER_PATHS = NO; 208 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 209 | CLANG_CXX_LIBRARY = "libc++"; 210 | CLANG_ENABLE_MODULES = YES; 211 | CLANG_ENABLE_OBJC_ARC = YES; 212 | CLANG_WARN_BOOL_CONVERSION = YES; 213 | CLANG_WARN_CONSTANT_CONVERSION = YES; 214 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 215 | CLANG_WARN_EMPTY_BODY = YES; 216 | CLANG_WARN_ENUM_CONVERSION = YES; 217 | CLANG_WARN_INFINITE_RECURSION = YES; 218 | CLANG_WARN_INT_CONVERSION = YES; 219 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 220 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 221 | CLANG_WARN_UNREACHABLE_CODE = YES; 222 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 223 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 224 | COPY_PHASE_STRIP = NO; 225 | CURRENT_PROJECT_VERSION = 1; 226 | DEBUG_INFORMATION_FORMAT = dwarf; 227 | ENABLE_STRICT_OBJC_MSGSEND = YES; 228 | ENABLE_TESTABILITY = YES; 229 | GCC_C_LANGUAGE_STANDARD = gnu99; 230 | GCC_DYNAMIC_NO_PIC = NO; 231 | GCC_NO_COMMON_BLOCKS = YES; 232 | GCC_OPTIMIZATION_LEVEL = 0; 233 | GCC_PREPROCESSOR_DEFINITIONS = ( 234 | "DEBUG=1", 235 | "$(inherited)", 236 | ); 237 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 238 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 239 | GCC_WARN_UNDECLARED_SELECTOR = YES; 240 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 241 | GCC_WARN_UNUSED_FUNCTION = YES; 242 | GCC_WARN_UNUSED_VARIABLE = YES; 243 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 244 | MTL_ENABLE_DEBUG_INFO = YES; 245 | ONLY_ACTIVE_ARCH = YES; 246 | SDKROOT = iphoneos; 247 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 248 | SWIFT_VERSION = 3.0; 249 | TARGETED_DEVICE_FAMILY = "1,2"; 250 | VERSIONING_SYSTEM = "apple-generic"; 251 | VERSION_INFO_PREFIX = ""; 252 | }; 253 | name = Debug; 254 | }; 255 | 292D71CA1C94930C00CFC060 /* Release */ = { 256 | isa = XCBuildConfiguration; 257 | buildSettings = { 258 | ALWAYS_SEARCH_USER_PATHS = NO; 259 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 260 | CLANG_CXX_LIBRARY = "libc++"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_CONSTANT_CONVERSION = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 266 | CLANG_WARN_EMPTY_BODY = YES; 267 | CLANG_WARN_ENUM_CONVERSION = YES; 268 | CLANG_WARN_INFINITE_RECURSION = YES; 269 | CLANG_WARN_INT_CONVERSION = YES; 270 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 271 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 272 | CLANG_WARN_UNREACHABLE_CODE = YES; 273 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 274 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 275 | COPY_PHASE_STRIP = NO; 276 | CURRENT_PROJECT_VERSION = 1; 277 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 278 | ENABLE_NS_ASSERTIONS = NO; 279 | ENABLE_STRICT_OBJC_MSGSEND = YES; 280 | GCC_C_LANGUAGE_STANDARD = gnu99; 281 | GCC_NO_COMMON_BLOCKS = YES; 282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 284 | GCC_WARN_UNDECLARED_SELECTOR = YES; 285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 286 | GCC_WARN_UNUSED_FUNCTION = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 289 | MTL_ENABLE_DEBUG_INFO = NO; 290 | SDKROOT = iphoneos; 291 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 292 | SWIFT_VERSION = 3.0; 293 | TARGETED_DEVICE_FAMILY = "1,2"; 294 | VALIDATE_PRODUCT = YES; 295 | VERSIONING_SYSTEM = "apple-generic"; 296 | VERSION_INFO_PREFIX = ""; 297 | }; 298 | name = Release; 299 | }; 300 | 292D71CC1C94930C00CFC060 /* Debug */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 304 | DEFINES_MODULE = YES; 305 | DYLIB_COMPATIBILITY_VERSION = 1; 306 | DYLIB_CURRENT_VERSION = 1; 307 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 308 | INFOPLIST_FILE = "$(SRCROOT)/Others/Info.plist"; 309 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 310 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 311 | PRODUCT_BUNDLE_IDENTIFIER = com.RamonGilabert.Walker; 312 | PRODUCT_NAME = "$(TARGET_NAME)"; 313 | SKIP_INSTALL = YES; 314 | SWIFT_VERSION = 3.0; 315 | }; 316 | name = Debug; 317 | }; 318 | 292D71CD1C94930C00CFC060 /* Release */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 322 | DEFINES_MODULE = YES; 323 | DYLIB_COMPATIBILITY_VERSION = 1; 324 | DYLIB_CURRENT_VERSION = 1; 325 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 326 | INFOPLIST_FILE = "$(SRCROOT)/Others/Info.plist"; 327 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 328 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 329 | PRODUCT_BUNDLE_IDENTIFIER = com.RamonGilabert.Walker; 330 | PRODUCT_NAME = "$(TARGET_NAME)"; 331 | SKIP_INSTALL = YES; 332 | SWIFT_VERSION = 3.0; 333 | }; 334 | name = Release; 335 | }; 336 | /* End XCBuildConfiguration section */ 337 | 338 | /* Begin XCConfigurationList section */ 339 | 292D71BD1C94930C00CFC060 /* Build configuration list for PBXProject "Walker" */ = { 340 | isa = XCConfigurationList; 341 | buildConfigurations = ( 342 | 292D71C91C94930C00CFC060 /* Debug */, 343 | 292D71CA1C94930C00CFC060 /* Release */, 344 | ); 345 | defaultConfigurationIsVisible = 0; 346 | defaultConfigurationName = Release; 347 | }; 348 | 292D71CB1C94930C00CFC060 /* Build configuration list for PBXNativeTarget "Walker" */ = { 349 | isa = XCConfigurationList; 350 | buildConfigurations = ( 351 | 292D71CC1C94930C00CFC060 /* Debug */, 352 | 292D71CD1C94930C00CFC060 /* Release */, 353 | ); 354 | defaultConfigurationIsVisible = 0; 355 | defaultConfigurationName = Release; 356 | }; 357 | /* End XCConfigurationList section */ 358 | }; 359 | rootObject = 292D71BA1C94930C00CFC060 /* Project object */; 360 | } 361 | -------------------------------------------------------------------------------- /Walker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Walker.xcodeproj/xcshareddata/xcschemes/Walker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | --------------------------------------------------------------------------------