├── .gitignore ├── .swift-version ├── Example ├── .gitignore ├── Cartfile ├── Cartfile.private ├── Cartfile.resolved ├── Docs │ ├── if-intro.gif │ ├── open-source-ifttt.png │ ├── razzledazzle-demo.gif │ └── razzledazzlebanner.jpg ├── Example │ ├── RazzleDazzleDemo.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ ├── RazzleDazzleDemo │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ ├── BigCloud.imageset │ │ │ │ ├── BigCloud.png │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Dazzle.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── dazzle.png │ │ │ ├── IFTTTCloud.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── IFTTTCloud.png │ │ │ ├── IFTTTPresents.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── iftttpresents.png │ │ │ ├── LittleCloud.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── littleCloud.png │ │ │ ├── MusicNotes.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── MusicNotes.png │ │ │ ├── MusicStand.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── MusicStand.png │ │ │ ├── Page2Text.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── text2.png │ │ │ ├── Page3Text.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── text3.png │ │ │ ├── Plane.imageset │ │ │ │ ├── Airplane.png │ │ │ │ └── Contents.json │ │ │ ├── Razzle.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── razzle.png │ │ │ ├── Star.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Star.png │ │ │ └── Sun.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Sun.png │ │ ├── Info.plist │ │ └── ViewController.swift │ └── RazzleDazzleDemoTests │ │ ├── Info.plist │ │ └── RazzleDazzleDemoTests.swift ├── RazzleDazzle.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── RazzleDazzle.xcscheme ├── RazzleDazzle.xcworkspace │ └── contents.xcworkspacedata └── RazzleDazzleTests │ ├── Info.plist │ ├── RAZAlphaAnimationSpec.swift │ ├── RAZAnimationSpec.swift │ ├── RAZColorAnimationSpec.swift │ ├── RAZEasingFunctionSpec.swift │ ├── RAZFilmstripSpec.swift │ ├── RAZInterpolatableSpec.swift │ ├── RAZRotationAnimationSpec.swift │ ├── RAZScaleAnimationSpec.swift │ └── RAZTranslationAnimationSpec.swift ├── LICENSE ├── README.md ├── RazzleDazzle.podspec └── Source ├── AlphaAnimation.swift ├── Animatable.swift ├── AnimatedPagingScrollViewController.swift ├── Animation.swift ├── Animator.swift ├── BackgroundColorAnimation.swift ├── ConstraintConstantAnimation.swift ├── ConstraintMultiplierAnimation.swift ├── CornerRadiusAnimation.swift ├── CubicBezier.swift ├── EasingFunction.swift ├── Filmstrip.swift ├── HideAnimation.swift ├── Info.plist ├── Interpolatable.swift ├── LabelTextColorAnimation.swift ├── LayerFillColorAnimation.swift ├── LayerStrokeColorAnimation.swift ├── LayerStrokeEndAnimation.swift ├── LayerStrokeStartAnimation.swift ├── PathPositionAnimation.swift ├── RazzleDazzle.h ├── RotationAnimation.swift ├── ScaleAnimation.swift ├── ScrollViewPageConstraintAnimation.swift ├── TranslationAnimation.swift └── UIView+Transform.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 -------------------------------------------------------------------------------- /Example/.gitignore: -------------------------------------------------------------------------------- 1 | Carthage/ 2 | -------------------------------------------------------------------------------- /Example/Cartfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Cartfile -------------------------------------------------------------------------------- /Example/Cartfile.private: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" "v5.0.0" 2 | github "Quick/Quick" "v0.10.0" 3 | -------------------------------------------------------------------------------- /Example/Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" "v5.0.0" 2 | github "Quick/Quick" "v0.10.0" 3 | -------------------------------------------------------------------------------- /Example/Docs/if-intro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Docs/if-intro.gif -------------------------------------------------------------------------------- /Example/Docs/open-source-ifttt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Docs/open-source-ifttt.png -------------------------------------------------------------------------------- /Example/Docs/razzledazzle-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Docs/razzledazzle-demo.gif -------------------------------------------------------------------------------- /Example/Docs/razzledazzlebanner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Docs/razzledazzlebanner.jpg -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FA0895751B3F1A37002460BB /* RazzleDazzle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA0895741B3F1A37002460BB /* RazzleDazzle.framework */; }; 11 | FA0895761B3F1A37002460BB /* RazzleDazzle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FA0895741B3F1A37002460BB /* RazzleDazzle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | FA6AC6DD1B3DF6690053688A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA6AC6DB1B3DF6690053688A /* LaunchScreen.storyboard */; }; 13 | FAD1C74E1B2F57D60053D880 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD1C74D1B2F57D60053D880 /* AppDelegate.swift */; }; 14 | FAD1C7501B2F57D60053D880 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD1C74F1B2F57D60053D880 /* ViewController.swift */; }; 15 | FAD1C7531B2F57D60053D880 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FAD1C7511B2F57D60053D880 /* Main.storyboard */; }; 16 | FAD1C7551B2F57D60053D880 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FAD1C7541B2F57D60053D880 /* Images.xcassets */; }; 17 | FAD1C7641B2F57D60053D880 /* RazzleDazzleDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD1C7631B2F57D60053D880 /* RazzleDazzleDemoTests.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | FAD1C75E1B2F57D60053D880 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = FAD1C7401B2F57D60053D880 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = FAD1C7471B2F57D60053D880; 26 | remoteInfo = RazzleDazzleDemo; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXCopyFilesBuildPhase section */ 31 | FA0895771B3F1A37002460BB /* Embed Frameworks */ = { 32 | isa = PBXCopyFilesBuildPhase; 33 | buildActionMask = 2147483647; 34 | dstPath = ""; 35 | dstSubfolderSpec = 10; 36 | files = ( 37 | FA0895761B3F1A37002460BB /* RazzleDazzle.framework in Embed Frameworks */, 38 | ); 39 | name = "Embed Frameworks"; 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXCopyFilesBuildPhase section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | FA0895741B3F1A37002460BB /* RazzleDazzle.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RazzleDazzle.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | FA6AC6DC1B3DF6690053688A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | FAD1C7481B2F57D60053D880 /* RazzleDazzleDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RazzleDazzleDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | FAD1C74C1B2F57D60053D880 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | FAD1C74D1B2F57D60053D880 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | FAD1C74F1B2F57D60053D880 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 51 | FAD1C7521B2F57D60053D880 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 52 | FAD1C7541B2F57D60053D880 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 53 | FAD1C75D1B2F57D60053D880 /* RazzleDazzleDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RazzleDazzleDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | FAD1C7621B2F57D60053D880 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | FAD1C7631B2F57D60053D880 /* RazzleDazzleDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RazzleDazzleDemoTests.swift; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | FAD1C7451B2F57D60053D880 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | FA0895751B3F1A37002460BB /* RazzleDazzle.framework in Frameworks */, 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | FAD1C75A1B2F57D60053D880 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | FAD1C73F1B2F57D60053D880 = { 78 | isa = PBXGroup; 79 | children = ( 80 | FA0895741B3F1A37002460BB /* RazzleDazzle.framework */, 81 | FAD1C74A1B2F57D60053D880 /* RazzleDazzleDemo */, 82 | FAD1C7601B2F57D60053D880 /* RazzleDazzleDemoTests */, 83 | FAD1C7491B2F57D60053D880 /* Products */, 84 | ); 85 | sourceTree = ""; 86 | }; 87 | FAD1C7491B2F57D60053D880 /* Products */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | FAD1C7481B2F57D60053D880 /* RazzleDazzleDemo.app */, 91 | FAD1C75D1B2F57D60053D880 /* RazzleDazzleDemoTests.xctest */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | FAD1C74A1B2F57D60053D880 /* RazzleDazzleDemo */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | FAD1C74D1B2F57D60053D880 /* AppDelegate.swift */, 100 | FAD1C74F1B2F57D60053D880 /* ViewController.swift */, 101 | FAD1C7511B2F57D60053D880 /* Main.storyboard */, 102 | FAD1C7541B2F57D60053D880 /* Images.xcassets */, 103 | FAD1C74B1B2F57D60053D880 /* Supporting Files */, 104 | ); 105 | path = RazzleDazzleDemo; 106 | sourceTree = ""; 107 | }; 108 | FAD1C74B1B2F57D60053D880 /* Supporting Files */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | FAD1C74C1B2F57D60053D880 /* Info.plist */, 112 | FA6AC6DB1B3DF6690053688A /* LaunchScreen.storyboard */, 113 | ); 114 | name = "Supporting Files"; 115 | sourceTree = ""; 116 | }; 117 | FAD1C7601B2F57D60053D880 /* RazzleDazzleDemoTests */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | FAD1C7631B2F57D60053D880 /* RazzleDazzleDemoTests.swift */, 121 | FAD1C7611B2F57D60053D880 /* Supporting Files */, 122 | ); 123 | path = RazzleDazzleDemoTests; 124 | sourceTree = ""; 125 | }; 126 | FAD1C7611B2F57D60053D880 /* Supporting Files */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | FAD1C7621B2F57D60053D880 /* Info.plist */, 130 | ); 131 | name = "Supporting Files"; 132 | sourceTree = ""; 133 | }; 134 | /* End PBXGroup section */ 135 | 136 | /* Begin PBXNativeTarget section */ 137 | FAD1C7471B2F57D60053D880 /* RazzleDazzleDemo */ = { 138 | isa = PBXNativeTarget; 139 | buildConfigurationList = FAD1C7671B2F57D60053D880 /* Build configuration list for PBXNativeTarget "RazzleDazzleDemo" */; 140 | buildPhases = ( 141 | FAD1C7441B2F57D60053D880 /* Sources */, 142 | FAD1C7451B2F57D60053D880 /* Frameworks */, 143 | FAD1C7461B2F57D60053D880 /* Resources */, 144 | FA0895771B3F1A37002460BB /* Embed Frameworks */, 145 | ); 146 | buildRules = ( 147 | ); 148 | dependencies = ( 149 | ); 150 | name = RazzleDazzleDemo; 151 | productName = RazzleDazzleDemo; 152 | productReference = FAD1C7481B2F57D60053D880 /* RazzleDazzleDemo.app */; 153 | productType = "com.apple.product-type.application"; 154 | }; 155 | FAD1C75C1B2F57D60053D880 /* RazzleDazzleDemoTests */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = FAD1C76A1B2F57D60053D880 /* Build configuration list for PBXNativeTarget "RazzleDazzleDemoTests" */; 158 | buildPhases = ( 159 | FAD1C7591B2F57D60053D880 /* Sources */, 160 | FAD1C75A1B2F57D60053D880 /* Frameworks */, 161 | FAD1C75B1B2F57D60053D880 /* Resources */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | FAD1C75F1B2F57D60053D880 /* PBXTargetDependency */, 167 | ); 168 | name = RazzleDazzleDemoTests; 169 | productName = RazzleDazzleDemoTests; 170 | productReference = FAD1C75D1B2F57D60053D880 /* RazzleDazzleDemoTests.xctest */; 171 | productType = "com.apple.product-type.bundle.unit-test"; 172 | }; 173 | /* End PBXNativeTarget section */ 174 | 175 | /* Begin PBXProject section */ 176 | FAD1C7401B2F57D60053D880 /* Project object */ = { 177 | isa = PBXProject; 178 | attributes = { 179 | LastSwiftUpdateCheck = 0700; 180 | LastUpgradeCheck = 0800; 181 | ORGANIZATIONNAME = IFTTT; 182 | TargetAttributes = { 183 | FAD1C7471B2F57D60053D880 = { 184 | CreatedOnToolsVersion = 6.3.2; 185 | DevelopmentTeamName = "Raphael Cruzeiro (Personal Team)"; 186 | LastSwiftMigration = 0800; 187 | }; 188 | FAD1C75C1B2F57D60053D880 = { 189 | CreatedOnToolsVersion = 6.3.2; 190 | LastSwiftMigration = 0800; 191 | TestTargetID = FAD1C7471B2F57D60053D880; 192 | }; 193 | }; 194 | }; 195 | buildConfigurationList = FAD1C7431B2F57D60053D880 /* Build configuration list for PBXProject "RazzleDazzleDemo" */; 196 | compatibilityVersion = "Xcode 3.2"; 197 | developmentRegion = English; 198 | hasScannedForEncodings = 0; 199 | knownRegions = ( 200 | en, 201 | Base, 202 | ); 203 | mainGroup = FAD1C73F1B2F57D60053D880; 204 | productRefGroup = FAD1C7491B2F57D60053D880 /* Products */; 205 | projectDirPath = ""; 206 | projectRoot = ""; 207 | targets = ( 208 | FAD1C7471B2F57D60053D880 /* RazzleDazzleDemo */, 209 | FAD1C75C1B2F57D60053D880 /* RazzleDazzleDemoTests */, 210 | ); 211 | }; 212 | /* End PBXProject section */ 213 | 214 | /* Begin PBXResourcesBuildPhase section */ 215 | FAD1C7461B2F57D60053D880 /* Resources */ = { 216 | isa = PBXResourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | FA6AC6DD1B3DF6690053688A /* LaunchScreen.storyboard in Resources */, 220 | FAD1C7531B2F57D60053D880 /* Main.storyboard in Resources */, 221 | FAD1C7551B2F57D60053D880 /* Images.xcassets in Resources */, 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | FAD1C75B1B2F57D60053D880 /* Resources */ = { 226 | isa = PBXResourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | }; 232 | /* End PBXResourcesBuildPhase section */ 233 | 234 | /* Begin PBXSourcesBuildPhase section */ 235 | FAD1C7441B2F57D60053D880 /* Sources */ = { 236 | isa = PBXSourcesBuildPhase; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | FAD1C7501B2F57D60053D880 /* ViewController.swift in Sources */, 240 | FAD1C74E1B2F57D60053D880 /* AppDelegate.swift in Sources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | FAD1C7591B2F57D60053D880 /* Sources */ = { 245 | isa = PBXSourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | FAD1C7641B2F57D60053D880 /* RazzleDazzleDemoTests.swift in Sources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXSourcesBuildPhase section */ 253 | 254 | /* Begin PBXTargetDependency section */ 255 | FAD1C75F1B2F57D60053D880 /* PBXTargetDependency */ = { 256 | isa = PBXTargetDependency; 257 | target = FAD1C7471B2F57D60053D880 /* RazzleDazzleDemo */; 258 | targetProxy = FAD1C75E1B2F57D60053D880 /* PBXContainerItemProxy */; 259 | }; 260 | /* End PBXTargetDependency section */ 261 | 262 | /* Begin PBXVariantGroup section */ 263 | FA6AC6DB1B3DF6690053688A /* LaunchScreen.storyboard */ = { 264 | isa = PBXVariantGroup; 265 | children = ( 266 | FA6AC6DC1B3DF6690053688A /* Base */, 267 | ); 268 | name = LaunchScreen.storyboard; 269 | sourceTree = ""; 270 | }; 271 | FAD1C7511B2F57D60053D880 /* Main.storyboard */ = { 272 | isa = PBXVariantGroup; 273 | children = ( 274 | FAD1C7521B2F57D60053D880 /* Base */, 275 | ); 276 | name = Main.storyboard; 277 | sourceTree = ""; 278 | }; 279 | /* End PBXVariantGroup section */ 280 | 281 | /* Begin XCBuildConfiguration section */ 282 | FAD1C7651B2F57D60053D880 /* Debug */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | ALWAYS_SEARCH_USER_PATHS = NO; 286 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 287 | CLANG_CXX_LIBRARY = "libc++"; 288 | CLANG_ENABLE_MODULES = YES; 289 | CLANG_ENABLE_OBJC_ARC = YES; 290 | CLANG_WARN_BOOL_CONVERSION = YES; 291 | CLANG_WARN_CONSTANT_CONVERSION = YES; 292 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 293 | CLANG_WARN_EMPTY_BODY = YES; 294 | CLANG_WARN_ENUM_CONVERSION = YES; 295 | CLANG_WARN_INFINITE_RECURSION = YES; 296 | CLANG_WARN_INT_CONVERSION = YES; 297 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 298 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 299 | CLANG_WARN_UNREACHABLE_CODE = YES; 300 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 301 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 302 | COPY_PHASE_STRIP = NO; 303 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 304 | ENABLE_STRICT_OBJC_MSGSEND = YES; 305 | ENABLE_TESTABILITY = YES; 306 | GCC_C_LANGUAGE_STANDARD = gnu99; 307 | GCC_DYNAMIC_NO_PIC = NO; 308 | GCC_NO_COMMON_BLOCKS = YES; 309 | GCC_OPTIMIZATION_LEVEL = 0; 310 | GCC_PREPROCESSOR_DEFINITIONS = ( 311 | "DEBUG=1", 312 | "$(inherited)", 313 | ); 314 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 315 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 316 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 317 | GCC_WARN_UNDECLARED_SELECTOR = YES; 318 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 319 | GCC_WARN_UNUSED_FUNCTION = YES; 320 | GCC_WARN_UNUSED_VARIABLE = YES; 321 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 322 | MTL_ENABLE_DEBUG_INFO = YES; 323 | ONLY_ACTIVE_ARCH = YES; 324 | SDKROOT = iphoneos; 325 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 326 | }; 327 | name = Debug; 328 | }; 329 | FAD1C7661B2F57D60053D880 /* Release */ = { 330 | isa = XCBuildConfiguration; 331 | buildSettings = { 332 | ALWAYS_SEARCH_USER_PATHS = NO; 333 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 334 | CLANG_CXX_LIBRARY = "libc++"; 335 | CLANG_ENABLE_MODULES = YES; 336 | CLANG_ENABLE_OBJC_ARC = YES; 337 | CLANG_WARN_BOOL_CONVERSION = YES; 338 | CLANG_WARN_CONSTANT_CONVERSION = YES; 339 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 340 | CLANG_WARN_EMPTY_BODY = YES; 341 | CLANG_WARN_ENUM_CONVERSION = YES; 342 | CLANG_WARN_INFINITE_RECURSION = YES; 343 | CLANG_WARN_INT_CONVERSION = YES; 344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 351 | ENABLE_NS_ASSERTIONS = NO; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu99; 354 | GCC_NO_COMMON_BLOCKS = YES; 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 362 | MTL_ENABLE_DEBUG_INFO = NO; 363 | SDKROOT = iphoneos; 364 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 365 | VALIDATE_PRODUCT = YES; 366 | }; 367 | name = Release; 368 | }; 369 | FAD1C7681B2F57D60053D880 /* Debug */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 373 | CODE_SIGN_IDENTITY = "iPhone Developer"; 374 | INFOPLIST_FILE = RazzleDazzleDemo/Info.plist; 375 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 376 | PRODUCT_BUNDLE_IDENTIFIER = "ifttt.$(PRODUCT_NAME:rfc1034identifier)"; 377 | PRODUCT_NAME = "$(TARGET_NAME)"; 378 | SWIFT_VERSION = 3.0; 379 | TARGETED_DEVICE_FAMILY = "1,2"; 380 | }; 381 | name = Debug; 382 | }; 383 | FAD1C7691B2F57D60053D880 /* Release */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 387 | CODE_SIGN_IDENTITY = "iPhone Developer"; 388 | INFOPLIST_FILE = RazzleDazzleDemo/Info.plist; 389 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 390 | PRODUCT_BUNDLE_IDENTIFIER = "ifttt.$(PRODUCT_NAME:rfc1034identifier)"; 391 | PRODUCT_NAME = "$(TARGET_NAME)"; 392 | SWIFT_VERSION = 3.0; 393 | TARGETED_DEVICE_FAMILY = "1,2"; 394 | }; 395 | name = Release; 396 | }; 397 | FAD1C76B1B2F57D60053D880 /* Debug */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | BUNDLE_LOADER = "$(TEST_HOST)"; 401 | FRAMEWORK_SEARCH_PATHS = ( 402 | "$(SDKROOT)/Developer/Library/Frameworks", 403 | "$(inherited)", 404 | ); 405 | GCC_PREPROCESSOR_DEFINITIONS = ( 406 | "DEBUG=1", 407 | "$(inherited)", 408 | ); 409 | INFOPLIST_FILE = RazzleDazzleDemoTests/Info.plist; 410 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 411 | PRODUCT_BUNDLE_IDENTIFIER = "ifttt.$(PRODUCT_NAME:rfc1034identifier)"; 412 | PRODUCT_NAME = "$(TARGET_NAME)"; 413 | SWIFT_VERSION = 3.0; 414 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RazzleDazzleDemo.app/RazzleDazzleDemo"; 415 | }; 416 | name = Debug; 417 | }; 418 | FAD1C76C1B2F57D60053D880 /* Release */ = { 419 | isa = XCBuildConfiguration; 420 | buildSettings = { 421 | BUNDLE_LOADER = "$(TEST_HOST)"; 422 | FRAMEWORK_SEARCH_PATHS = ( 423 | "$(SDKROOT)/Developer/Library/Frameworks", 424 | "$(inherited)", 425 | ); 426 | INFOPLIST_FILE = RazzleDazzleDemoTests/Info.plist; 427 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 428 | PRODUCT_BUNDLE_IDENTIFIER = "ifttt.$(PRODUCT_NAME:rfc1034identifier)"; 429 | PRODUCT_NAME = "$(TARGET_NAME)"; 430 | SWIFT_VERSION = 3.0; 431 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RazzleDazzleDemo.app/RazzleDazzleDemo"; 432 | }; 433 | name = Release; 434 | }; 435 | /* End XCBuildConfiguration section */ 436 | 437 | /* Begin XCConfigurationList section */ 438 | FAD1C7431B2F57D60053D880 /* Build configuration list for PBXProject "RazzleDazzleDemo" */ = { 439 | isa = XCConfigurationList; 440 | buildConfigurations = ( 441 | FAD1C7651B2F57D60053D880 /* Debug */, 442 | FAD1C7661B2F57D60053D880 /* Release */, 443 | ); 444 | defaultConfigurationIsVisible = 0; 445 | defaultConfigurationName = Release; 446 | }; 447 | FAD1C7671B2F57D60053D880 /* Build configuration list for PBXNativeTarget "RazzleDazzleDemo" */ = { 448 | isa = XCConfigurationList; 449 | buildConfigurations = ( 450 | FAD1C7681B2F57D60053D880 /* Debug */, 451 | FAD1C7691B2F57D60053D880 /* Release */, 452 | ); 453 | defaultConfigurationIsVisible = 0; 454 | defaultConfigurationName = Release; 455 | }; 456 | FAD1C76A1B2F57D60053D880 /* Build configuration list for PBXNativeTarget "RazzleDazzleDemoTests" */ = { 457 | isa = XCConfigurationList; 458 | buildConfigurations = ( 459 | FAD1C76B1B2F57D60053D880 /* Debug */, 460 | FAD1C76C1B2F57D60053D880 /* Release */, 461 | ); 462 | defaultConfigurationIsVisible = 0; 463 | defaultConfigurationName = Release; 464 | }; 465 | /* End XCConfigurationList section */ 466 | }; 467 | rootObject = FAD1C7401B2F57D60053D880 /* Project object */; 468 | } 469 | -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RazzleDazzleDemo 4 | // 5 | // Created by Laura Skelton on 6/15/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/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 | -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "60x60", 21 | "scale" : "3x" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/BigCloud.imageset/BigCloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/BigCloud.imageset/BigCloud.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/BigCloud.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "BigCloud.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Dazzle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "dazzle.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Dazzle.imageset/dazzle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/Dazzle.imageset/dazzle.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/IFTTTCloud.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "IFTTTCloud.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/IFTTTCloud.imageset/IFTTTCloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/IFTTTCloud.imageset/IFTTTCloud.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/IFTTTPresents.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "iftttpresents.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/IFTTTPresents.imageset/iftttpresents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/IFTTTPresents.imageset/iftttpresents.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/LittleCloud.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "littleCloud.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/LittleCloud.imageset/littleCloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/LittleCloud.imageset/littleCloud.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/MusicNotes.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "MusicNotes.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/MusicNotes.imageset/MusicNotes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/MusicNotes.imageset/MusicNotes.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/MusicStand.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "MusicStand.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/MusicStand.imageset/MusicStand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/MusicStand.imageset/MusicStand.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Page2Text.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "text2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Page2Text.imageset/text2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/Page2Text.imageset/text2.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Page3Text.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "text3.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Page3Text.imageset/text3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/Page3Text.imageset/text3.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Plane.imageset/Airplane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/Plane.imageset/Airplane.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Plane.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Airplane.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Razzle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "razzle.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Razzle.imageset/razzle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/Razzle.imageset/razzle.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Star.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Star.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Star.imageset/Star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/Star.imageset/Star.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Sun.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Sun.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/Images.xcassets/Sun.imageset/Sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/RazzleDazzle/830956a4d292018dd146af2bf3a4afcc230f13ad/Example/Example/RazzleDazzleDemo/Images.xcassets/Sun.imageset/Sun.png -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/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 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarHidden 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | UIInterfaceOrientationPortraitUpsideDown 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UIViewControllerBasedStatusBarAppearance 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // ViewController.swift 4 | // RazzleDazzleDemo 5 | // 6 | // Created by Laura Skelton on 6/15/15. 7 | // Copyright (c) 2015 IFTTT. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | import RazzleDazzle 12 | 13 | class ViewController: AnimatedPagingScrollViewController { 14 | private let star = UIImageView(image: UIImage(named: "Star")) 15 | private let iftttPresents = UIImageView(image: UIImage(named: "IFTTTPresents")) 16 | private let razzle = UIImageView(image: UIImage(named: "Razzle")) 17 | private let dazzle = UIImageView(image: UIImage(named: "Dazzle")) 18 | 19 | private let musicStand = UIImageView(image: UIImage(named: "MusicStand")) 20 | private let musicNotes = UIImageView(image: UIImage(named: "MusicNotes")) 21 | private let plane = UIImageView(image: UIImage(named: "Plane")) 22 | private var planePathLayer = CAShapeLayer() 23 | private let planePathView = UIView() 24 | 25 | private let bigCloud = UIImageView(image: UIImage(named: "BigCloud")) 26 | private let littleCloud = UIImageView(image: UIImage(named: "LittleCloud")) 27 | private let sun = UIImageView(image: UIImage(named: "Sun")) 28 | private let iftttCloud = UIImageView(image: UIImage(named: "IFTTTCloud")) 29 | 30 | private let page2Text = UIImageView(image: UIImage(named: "Page2Text")) 31 | private let page3Text = UIImageView(image: UIImage(named: "Page3Text")) 32 | 33 | private var airplaneFlyingAnimation : PathPositionAnimation? 34 | 35 | override func numberOfPages() -> Int { 36 | // Tell the scroll view how many pages it has 37 | return 4 38 | } 39 | 40 | override public func viewDidLoad() { 41 | super.viewDidLoad() 42 | configureViews() 43 | configureAnimations() 44 | UIApplication.shared.setStatusBarHidden(true, with: .fade) 45 | } 46 | 47 | override public func viewWillAppear(_ animated: Bool) { 48 | scaleAirplanePathToSize(scrollView.frame.size) 49 | } 50 | 51 | override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 52 | super.viewWillTransition(to: size, with: coordinator) 53 | coordinator.animate(alongsideTransition: {context in 54 | self.scaleAirplanePathToSize(size) 55 | }, completion: nil) 56 | } 57 | 58 | private func configureViews () { 59 | // Add each of the views to the contentView 60 | contentView.addSubview(iftttPresents) 61 | contentView.addSubview(star) 62 | contentView.addSubview(razzle) 63 | contentView.addSubview(dazzle) 64 | contentView.addSubview(planePathView) 65 | contentView.addSubview(musicStand) 66 | contentView.addSubview(musicNotes) 67 | contentView.addSubview(bigCloud) 68 | contentView.addSubview(littleCloud) 69 | contentView.addSubview(sun) 70 | contentView.addSubview(iftttCloud) 71 | contentView.addSubview(page2Text) 72 | contentView.addSubview(page3Text) 73 | } 74 | 75 | private func configureAnimations() { 76 | // Configure all of the animations 77 | configureScrollView() 78 | configureIFTTTPresents() 79 | configureStar() 80 | configureRazzleDazzleLabels() 81 | configureMusicStand() 82 | configurePageText() 83 | configureClouds() 84 | configureSun() 85 | configureAirplane() 86 | animateCurrentFrame() 87 | } 88 | 89 | private func configureScrollView() { 90 | // Let's change the background color of the scroll view from dark gray to light gray to blue 91 | let backgroundColorAnimation = BackgroundColorAnimation(view: scrollView) 92 | backgroundColorAnimation[0] = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1) 93 | backgroundColorAnimation[0.5] = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) 94 | backgroundColorAnimation[0.99] = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) 95 | backgroundColorAnimation[1] = UIColor(red: 0.14, green: 0.8, blue: 1, alpha: 1) 96 | animator.addAnimation(backgroundColorAnimation) 97 | } 98 | 99 | private func configureIFTTTPresents() { 100 | // Keep IFTTTPresents centered on pages 0 and 1, offset 20 pixels down from the top of the view 101 | NSLayoutConstraint(item: iftttPresents, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 20).isActive = true 102 | keepView(iftttPresents, onPages: [0,1]) 103 | 104 | // Hide IFTTTPresents when we get to page 1 105 | let iftttPresentsHideAnimation = HideAnimation(view: iftttPresents, hideAt: 1) 106 | animator.addAnimation(iftttPresentsHideAnimation) 107 | } 108 | 109 | private func configureStar() { 110 | // Center the star on the page, and keep it centered on pages 0 and 1 111 | let width = NSLayoutConstraint(item: star, attribute: .width, relatedBy: .lessThanOrEqual, toItem: scrollView, attribute: .width, multiplier: 1.3, constant: 0) 112 | let height = NSLayoutConstraint(item: star, attribute: .height, relatedBy: .lessThanOrEqual, toItem: scrollView, attribute: .height, multiplier: 1, constant: 0) 113 | let top = NSLayoutConstraint(item: star, attribute: .top, relatedBy: .greaterThanOrEqual, toItem: scrollView, attribute: .top, multiplier: 1, constant: 30) 114 | let aspect = NSLayoutConstraint(item: star, attribute: .height, relatedBy: .equal, toItem: star, attribute: .width, multiplier: 1, constant: 0) 115 | let centerY = NSLayoutConstraint(item: star, attribute: .centerY, relatedBy: .equal, toItem: scrollView, attribute: .centerY, multiplier: 1, constant: 0) 116 | NSLayoutConstraint.activate([width, height, top, aspect, centerY]) 117 | keepView(star, onPages: [0,1]) 118 | 119 | // Scale up the star to 7 times its original size between pages 0 and 1, with a quadratic Ease In easing function 120 | let starScaleAnimation = ScaleAnimation(view: star) 121 | starScaleAnimation.addKeyframe(0, value: 1, easing: EasingFunctionEaseInQuad) 122 | starScaleAnimation[1] = 10 123 | animator.addAnimation(starScaleAnimation) 124 | 125 | // Hide the star when we get to page 1 126 | let starHideAnimation = HideAnimation(view: star, hideAt: 1) 127 | animator.addAnimation(starHideAnimation) 128 | } 129 | 130 | private func configureRazzleDazzleLabels() { 131 | // Set the size constraints on Razzle and Dazzle 132 | let razzleWidth = NSLayoutConstraint(item: razzle, attribute: .width, relatedBy: .equal, toItem: star, attribute: .width, multiplier: 0.6, constant: 0) 133 | let razzleHeight = NSLayoutConstraint(item: razzle, attribute: .height, relatedBy: .equal, toItem: razzle, attribute: .width, multiplier: 218.0 / 576.0, constant: 0) 134 | let dazzleWidth = NSLayoutConstraint(item: dazzle, attribute: .width, relatedBy: .equal, toItem: star, attribute: .width, multiplier: 0.6, constant: 0) 135 | let dazzleHeight = NSLayoutConstraint(item: dazzle, attribute: .height, relatedBy: .equal, toItem: dazzle, attribute: .width, multiplier: 386.0 / 588.0, constant: 0) 136 | NSLayoutConstraint.activate([razzleWidth, razzleHeight, dazzleWidth, dazzleHeight]) 137 | 138 | // Create the vertical position constraints for Razzle and Dazzle 139 | let razzleVerticalConstraint = NSLayoutConstraint(item: razzle, attribute: .centerY, relatedBy: .equal, toItem: star, attribute: .centerY, multiplier: 1, constant: 0) 140 | let dazzleVerticalConstraint = NSLayoutConstraint(item: dazzle, attribute: .centerY, relatedBy: .equal, toItem: star, attribute: .centerY, multiplier: 1, constant: 0) 141 | NSLayoutConstraint.activate([razzleVerticalConstraint, dazzleVerticalConstraint]) 142 | 143 | // Animate the vertical position of Razzle to go from 30 pixels above the center of the view to 200 pixels above, between pages 0 and 1 144 | let razzleVerticalAnimation = ConstraintConstantAnimation(superview: scrollView, constraint: razzleVerticalConstraint) 145 | razzleVerticalAnimation[0] = -30 146 | razzleVerticalAnimation[1] = -200 147 | animator.addAnimation(razzleVerticalAnimation) 148 | 149 | // Animate the vertical position of Razzle to go from 80 pixels below the center of the view to 260 pixels below, between pages 0 and 1 150 | let dazzleVerticalAnimation = ConstraintConstantAnimation(superview: scrollView, constraint: dazzleVerticalConstraint) 151 | dazzleVerticalAnimation[0] = 80 152 | dazzleVerticalAnimation[1] = 260 153 | animator.addAnimation(dazzleVerticalAnimation) 154 | 155 | // Center Razzle and Dazzle horizontally on the first page of the scroll view 156 | keepView(razzle, onPage: 0) 157 | keepView(dazzle, onPage: 0) 158 | 159 | // Rotate Razzle 100 degrees counter-clockwise between pages 0 and 1 160 | let razzleRotationAnimation = RotationAnimation(view: razzle) 161 | razzleRotationAnimation[0] = 0 162 | razzleRotationAnimation[1] = 100 163 | animator.addAnimation(razzleRotationAnimation) 164 | 165 | // Rotate Dazzle 100 degrees counter-clockwise between pages 0 and 1 166 | let dazzleRotationAnimation = RotationAnimation(view: dazzle) 167 | dazzleRotationAnimation[0] = 0 168 | dazzleRotationAnimation[1] = 100 169 | animator.addAnimation(dazzleRotationAnimation) 170 | } 171 | 172 | private func configureMusicStand() { 173 | // Create the vertical position constraint for the music stand 174 | let musicStandVerticalConstraint = NSLayoutConstraint(item: musicStand, attribute: .bottom, relatedBy: .greaterThanOrEqual, toItem: scrollView, attribute: .bottom, multiplier: 1, constant: 0) 175 | let standTop = NSLayoutConstraint(item: musicStand, attribute: .top, relatedBy: .greaterThanOrEqual, toItem: scrollView, attribute: .top, multiplier: 1, constant: 40) 176 | let standWidth = NSLayoutConstraint(item: musicStand, attribute: .width, relatedBy: .lessThanOrEqual, toItem: scrollView, attribute: .width, multiplier: 1, constant: 0) 177 | let standAspect = NSLayoutConstraint(item: musicStand, attribute: .height, relatedBy: .equal, toItem: musicStand, attribute: .width, multiplier: 1184.0 / 750.0, constant: 0) 178 | let standHeight = NSLayoutConstraint(item: musicStand, attribute: .height, relatedBy: .lessThanOrEqual, toItem: scrollView, attribute: .height, multiplier: 1, constant: 0) 179 | NSLayoutConstraint.activate([musicStandVerticalConstraint, standTop, standWidth, standAspect, standHeight]) 180 | 181 | // Keep the right side of the music stand at the right edge of pages 1 and 2 182 | keepView(musicStand, onPages: [1,2], withAttribute: .right) 183 | 184 | // Animate the music stand's bottom offset from the view to go from being offset 0 times to 1 time the height of the contentView between pages 1 and 2, 185 | // with a cubic Ease Out easing function 186 | let musicStandVerticalAnimation = ConstraintMultiplierAnimation(superview: scrollView, constraint: musicStandVerticalConstraint, attribute: .height, referenceView: contentView) 187 | musicStandVerticalAnimation.addKeyframe(1, value: 0, easing: EasingFunctionEaseOutCubic) 188 | musicStandVerticalAnimation[2] = 1 189 | animator.addAnimation(musicStandVerticalAnimation) 190 | 191 | // Lay out the music notes 192 | let notesWidth = NSLayoutConstraint(item: musicNotes, attribute: .width, relatedBy: .equal, toItem: musicStand, attribute: .width, multiplier: 1, constant: 0) 193 | let notesHeight = NSLayoutConstraint(item: musicNotes, attribute: .height, relatedBy: .equal, toItem: musicStand, attribute: .height, multiplier: 1, constant: 0) 194 | let notesCenterY = NSLayoutConstraint(item: musicNotes, attribute: .centerY, relatedBy: .equal, toItem: musicStand, attribute: .centerY, multiplier: 1, constant: 0) 195 | NSLayoutConstraint.activate([notesWidth, notesHeight, notesCenterY]) 196 | 197 | // Move the music notes in quickly from the right when we change from page 0.5 to 1, and keep them centered on pages 1 and 2 198 | keepView(musicNotes, onPages: [2, 1, 2], atTimes: [0.5, 1, 2], withAttribute: .right) 199 | } 200 | 201 | private func configurePageText() { 202 | // Keep the page2Text slightly above center on page 2 203 | NSLayoutConstraint(item: page2Text, attribute: .centerY, relatedBy: .equal, toItem: scrollView, attribute: .centerY, multiplier: 0.95, constant: 0).isActive = true 204 | keepView(page2Text, onPage: 2) 205 | 206 | // Keep the page3Text centered on page 3 207 | NSLayoutConstraint(item: page3Text, attribute: .centerY, relatedBy: .equal, toItem: scrollView, attribute: .centerY, multiplier: 1, constant: 0).isActive = true 208 | keepView(page3Text, onPage: 3) 209 | } 210 | 211 | private func configureClouds() { 212 | // Lay out the big cloud 213 | let bigCloudVerticalConstraint = NSLayoutConstraint(item: bigCloud, attribute: .centerY, relatedBy: .equal, toItem: scrollView, attribute: .top, multiplier: 1, constant: 0) 214 | let bigCloudWidth = NSLayoutConstraint(item: bigCloud, attribute: .width, relatedBy: .lessThanOrEqual, toItem: scrollView, attribute: .width, multiplier: 0.78, constant: 0) 215 | let bigCloudHeight = NSLayoutConstraint(item: bigCloud, attribute: .height, relatedBy: .lessThanOrEqual, toItem: scrollView, attribute: .height, multiplier: 0.2, constant: 0) 216 | let bigCloudAspect = NSLayoutConstraint(item: bigCloud, attribute: .height, relatedBy: .equal, toItem: bigCloud, attribute: .width, multiplier: 0.45, constant: 0) 217 | NSLayoutConstraint.activate([bigCloudVerticalConstraint, bigCloudWidth, bigCloudHeight, bigCloudAspect]) 218 | 219 | // Keep the big cloud slightly to the right on pages 1 and 2, and zoom it out to the left between pages 2 and 3 220 | keepView(bigCloud, onPages: [1.35, 2.35, 1.8], atTimes: [1, 2, 3]) 221 | 222 | // Move the big cloud from above the view down to near the top of the view between pages 1 and 2 223 | let bigCloudVerticalAnimation = ConstraintMultiplierAnimation(superview: scrollView, constraint: bigCloudVerticalConstraint, attribute: .height, referenceView: scrollView) 224 | bigCloudVerticalAnimation[1] = -0.2 225 | bigCloudVerticalAnimation[2] = 0.2 226 | animator.addAnimation(bigCloudVerticalAnimation) 227 | 228 | // Lay out the little cloud 229 | let littleCloudBottom = NSLayoutConstraint(item: littleCloud, attribute: .bottom, relatedBy: .equal, toItem: bigCloud, attribute: .top, multiplier: 1, constant: 20) 230 | let littleCloudWidth = NSLayoutConstraint(item: littleCloud, attribute: .width, relatedBy: .equal, toItem: bigCloud, attribute: .height, multiplier: 1, constant: 0) 231 | let littleCloudAspect = NSLayoutConstraint(item: littleCloud, attribute: .height, relatedBy: .equal, toItem: littleCloud, attribute: .width, multiplier: 0.5, constant: 0) 232 | NSLayoutConstraint.activate([littleCloudBottom, littleCloudWidth, littleCloudAspect]) 233 | 234 | // Keep the little cloud slightly to the left on pages 1 and 2 235 | keepView(littleCloud, onPages: [0.75, 1.75], atTimes: [1, 2]) 236 | 237 | // Lay out the IFTTT cloud on the bottom half of the view 238 | NSLayoutConstraint(item: iftttCloud, attribute: .centerY, relatedBy: .equal, toItem: scrollView, attribute: .centerY, multiplier: 1.5, constant: 0).isActive = true 239 | 240 | // Zoom in the IFTTT cloud from the right between pages 2 and 3 241 | keepView(iftttCloud, onPages: [3.5, 3], atTimes: [2, 3]) 242 | } 243 | 244 | private func configureSun() { 245 | let sunVerticalConstraint = NSLayoutConstraint(item: sun, attribute: .centerY, relatedBy: .equal, toItem: scrollView, attribute: .top, multiplier: 1, constant: 0) 246 | sunVerticalConstraint.isActive = true 247 | 248 | // Move the sun from the right side to the left side between pages 2.5 and 3 249 | keepView(sun, onPages: [2.8, 2.6], atTimes: [2.5, 3]) 250 | 251 | // Move the sun from above the view offscreen to near the top of the view 252 | let sunVerticalAnimation = ConstraintConstantAnimation(superview: scrollView, constraint: sunVerticalConstraint) 253 | sunVerticalAnimation[2] = -200 254 | sunVerticalAnimation[3] = 20 255 | animator.addAnimation(sunVerticalAnimation) 256 | } 257 | 258 | private func configureAirplane() { 259 | // Set up the view that contains the airplane view and its dashed line path view 260 | planePathLayer = airplanePathLayer() 261 | planePathView.layer.addSublayer(planePathLayer) 262 | planePathView.addSubview(plane) 263 | planePathView.translatesAutoresizingMaskIntoConstraints = false 264 | plane.translatesAutoresizingMaskIntoConstraints = false 265 | let planeBottom = NSLayoutConstraint(item: plane, attribute: .bottom, relatedBy: .equal, toItem: planePathView, attribute: .centerY, multiplier: 1, constant: 0) 266 | let planeRight = NSLayoutConstraint(item: plane, attribute: .right, relatedBy: .equal, toItem: planePathView, attribute: .centerX, multiplier: 1, constant: 0) 267 | NSLayoutConstraint.activate([planeBottom, planeRight]) 268 | 269 | let planePathBottom = NSLayoutConstraint(item: planePathView, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1, constant: 40) 270 | let planePathWidth = NSLayoutConstraint(item: planePathView, attribute: .width, relatedBy: .equal, toItem: plane, attribute: .width, multiplier: 1, constant: 0) 271 | let planePathHeight = NSLayoutConstraint(item: planePathView, attribute: .height, relatedBy: .equal, toItem: plane, attribute: .height, multiplier: 1, constant: 0) 272 | NSLayoutConstraint.activate([planePathBottom, planePathWidth, planePathHeight]) 273 | 274 | // Keep the left edge of the planePathView at the center of pages 1 and 2 275 | keepView(planePathView, onPages: [1.5, 2.5], atTimes: [1, 2], withAttribute: .left) 276 | 277 | // Fly the plane along the path 278 | airplaneFlyingAnimation = PathPositionAnimation(view: plane, path: planePathLayer.path) 279 | airplaneFlyingAnimation![1] = 0 280 | airplaneFlyingAnimation![2] = 1 281 | animator.addAnimation(airplaneFlyingAnimation!) 282 | 283 | // Change the stroke end of the dashed line airplane path to match the plane's current position 284 | let planePathAnimation = LayerStrokeEndAnimation(layer: planePathLayer) 285 | planePathAnimation[1] = 0 286 | planePathAnimation[2] = 1 287 | animator.addAnimation(planePathAnimation) 288 | 289 | // Fade the plane path view in after page 1 and fade it out again after page 2.5 290 | let planeAlphaAnimation = AlphaAnimation(view: planePathView) 291 | planeAlphaAnimation[1.06] = 0 292 | planeAlphaAnimation[1.08] = 1 293 | planeAlphaAnimation[2.5] = 1 294 | planeAlphaAnimation[3] = 0 295 | animator.addAnimation(planeAlphaAnimation) 296 | } 297 | 298 | private func airplanePath() -> CGPath { 299 | // Create a bezier path for the airplane to fly along 300 | let airplanePath = UIBezierPath() 301 | airplanePath.move(to: CGPoint(x: 120, y: 20)) 302 | airplanePath.addCurve(to: CGPoint(x: 40, y: -130), controlPoint1: CGPoint(x: 120, y: 20), controlPoint2: CGPoint(x: 140, y: -50)) 303 | airplanePath.addCurve(to: CGPoint(x: 30, y: -430), controlPoint1: CGPoint(x: -60, y: -210), controlPoint2: CGPoint(x: -320, y: -430)) 304 | airplanePath.addCurve(to: CGPoint(x: -210, y: -190), controlPoint1: CGPoint(x: 320, y: -430), controlPoint2: CGPoint(x: 130, y: -190)) 305 | return airplanePath.cgPath 306 | } 307 | 308 | private func airplanePathLayer() -> CAShapeLayer { 309 | // Create a shape layer to draw the airplane's path 310 | let shapeLayer = CAShapeLayer() 311 | shapeLayer.path = airplanePath() 312 | shapeLayer.strokeColor = UIColor.white.cgColor 313 | shapeLayer.lineDashPattern = [20, 20] 314 | shapeLayer.lineWidth = 4 315 | shapeLayer.miterLimit = 4 316 | shapeLayer.fillColor = nil 317 | shapeLayer.fillRule = kCAFillRuleEvenOdd 318 | return shapeLayer 319 | } 320 | 321 | private func scaleAirplanePathToSize(_ pageSize: CGSize) { 322 | // Scale the airplane path to the given page size 323 | let scaleSize = CGSize( 324 | width: pageSize.width / 375.0, 325 | height: pageSize.height / 667.0) 326 | 327 | var scaleTransform = CGAffineTransform(scaleX: scaleSize.width, y: scaleSize.height) 328 | let scaledPath = airplanePath().copy(using: &scaleTransform) 329 | planePathLayer.path = scaledPath 330 | 331 | if let planeAnimation = airplaneFlyingAnimation { 332 | planeAnimation.path = scaledPath 333 | } 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemoTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Example/RazzleDazzleDemoTests/RazzleDazzleDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RazzleDazzleDemoTests.swift 3 | // RazzleDazzleDemoTests 4 | // 5 | // Created by Laura Skelton on 6/15/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class RazzleDazzleDemoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Example/RazzleDazzle.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FA0895731B3F188E002460BB /* HideAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0895721B3F188E002460BB /* HideAnimation.swift */; }; 11 | FA340D681B3B4F78003F2CD8 /* Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA340D671B3B4F78003F2CD8 /* Animation.swift */; }; 12 | FA340D6C1B3B5D9E003F2CD8 /* LayerFillColorAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA340D6B1B3B5D9E003F2CD8 /* LayerFillColorAnimation.swift */; }; 13 | FA340D6E1B3B5E0B003F2CD8 /* LayerStrokeStartAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA340D6D1B3B5E0B003F2CD8 /* LayerStrokeStartAnimation.swift */; }; 14 | FA340D701B3B5E54003F2CD8 /* LayerStrokeEndAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA340D6F1B3B5E54003F2CD8 /* LayerStrokeEndAnimation.swift */; }; 15 | FA340D721B3B5E9B003F2CD8 /* LayerStrokeColorAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA340D711B3B5E9B003F2CD8 /* LayerStrokeColorAnimation.swift */; }; 16 | FA340D741B3B5EF5003F2CD8 /* LabelTextColorAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA340D731B3B5EF5003F2CD8 /* LabelTextColorAnimation.swift */; }; 17 | FA42E8671B2FDD200050795E /* ConstraintMultiplierAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA42E8661B2FDD200050795E /* ConstraintMultiplierAnimation.swift */; }; 18 | FA4D85E01B2F6D900051770C /* ConstraintConstantAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4D85DF1B2F6D900051770C /* ConstraintConstantAnimation.swift */; }; 19 | FA59914A1B433F2600C0FBA9 /* PathPositionAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5991491B433F2600C0FBA9 /* PathPositionAnimation.swift */; }; 20 | FA5BC1CD1B3147AE004DB083 /* RAZFilmstripSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5BC1CC1B3147AE004DB083 /* RAZFilmstripSpec.swift */; }; 21 | FA5BC1D01B314DCA004DB083 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA5BC1CE1B314DCA004DB083 /* Nimble.framework */; }; 22 | FA5BC1D11B314DCA004DB083 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA5BC1CF1B314DCA004DB083 /* Quick.framework */; }; 23 | FA5BC1D31B314DF2004DB083 /* Nimble.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = FA5BC1CE1B314DCA004DB083 /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 24 | FA5BC1D41B314DF6004DB083 /* Quick.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = FA5BC1CF1B314DCA004DB083 /* Quick.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 25 | FA5BC1D61B314EAF004DB083 /* RAZEasingFunctionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5BC1D51B314EAF004DB083 /* RAZEasingFunctionSpec.swift */; }; 26 | FA5BC1D81B315347004DB083 /* RAZInterpolatableSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5BC1D71B315347004DB083 /* RAZInterpolatableSpec.swift */; }; 27 | FA5C96D11B2F759000BBA5DE /* CornerRadiusAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5C96D01B2F759000BBA5DE /* CornerRadiusAnimation.swift */; }; 28 | FA625DD21B32065000836DB2 /* RAZColorAnimationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA625DD11B32065000836DB2 /* RAZColorAnimationSpec.swift */; }; 29 | FA625DD41B32078000836DB2 /* RAZScaleAnimationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA625DD31B32078000836DB2 /* RAZScaleAnimationSpec.swift */; }; 30 | FA625DD61B320F1D00836DB2 /* RAZRotationAnimationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA625DD51B320F1D00836DB2 /* RAZRotationAnimationSpec.swift */; }; 31 | FA625DD81B320FA000836DB2 /* RAZTranslationAnimationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA625DD71B320FA000836DB2 /* RAZTranslationAnimationSpec.swift */; }; 32 | FA625DDA1B32116900836DB2 /* RAZAnimationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA625DD91B32116900836DB2 /* RAZAnimationSpec.swift */; }; 33 | FA71FBB01B2FB24E00C49C16 /* EasingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA71FBAF1B2FB24E00C49C16 /* EasingFunction.swift */; }; 34 | FA71FBB21B2FB48F00C49C16 /* CubicBezier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA71FBB11B2FB48F00C49C16 /* CubicBezier.swift */; }; 35 | FA8ABF101B31F2FC00902AB9 /* RAZAlphaAnimationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8ABF0F1B31F2FC00902AB9 /* RAZAlphaAnimationSpec.swift */; }; 36 | FAA00CFB1B30A695003F2872 /* AnimatedPagingScrollViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAA00CFA1B30A695003F2872 /* AnimatedPagingScrollViewController.swift */; }; 37 | FAA00CFD1B30A6B2003F2872 /* ScrollViewPageConstraintAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAA00CFC1B30A6B2003F2872 /* ScrollViewPageConstraintAnimation.swift */; }; 38 | FACD9C631B2A554100AC1614 /* RazzleDazzle.h in Headers */ = {isa = PBXBuildFile; fileRef = FACD9C621B2A554100AC1614 /* RazzleDazzle.h */; settings = {ATTRIBUTES = (Public, ); }; }; 39 | FACD9C691B2A554100AC1614 /* RazzleDazzle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FACD9C5D1B2A554100AC1614 /* RazzleDazzle.framework */; }; 40 | FACD9C861B2AABD000AC1614 /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACD9C851B2AABD000AC1614 /* Animator.swift */; }; 41 | FACD9C8A1B2CB54200AC1614 /* AlphaAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACD9C891B2CB54200AC1614 /* AlphaAnimation.swift */; }; 42 | FACD9C901B2CE91900AC1614 /* BackgroundColorAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACD9C8F1B2CE91900AC1614 /* BackgroundColorAnimation.swift */; }; 43 | FACD9C941B2D090C00AC1614 /* UIView+Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACD9C931B2D090C00AC1614 /* UIView+Transform.swift */; }; 44 | FACD9C961B2D0A6000AC1614 /* ScaleAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACD9C951B2D0A6000AC1614 /* ScaleAnimation.swift */; }; 45 | FACD9C981B2D0DD200AC1614 /* RotationAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACD9C971B2D0DD200AC1614 /* RotationAnimation.swift */; }; 46 | FACD9C9C1B2E113C00AC1614 /* Interpolatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACD9C9B1B2E113C00AC1614 /* Interpolatable.swift */; }; 47 | FACD9C9E1B2E2F3500AC1614 /* Animatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACD9C9D1B2E2F3500AC1614 /* Animatable.swift */; }; 48 | FACD9CA01B2E3FC000AC1614 /* Filmstrip.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACD9C9F1B2E3FC000AC1614 /* Filmstrip.swift */; }; 49 | FACD9CA41B2E74DE00AC1614 /* TranslationAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACD9CA31B2E74DE00AC1614 /* TranslationAnimation.swift */; }; 50 | /* End PBXBuildFile section */ 51 | 52 | /* Begin PBXContainerItemProxy section */ 53 | FACD9C6A1B2A554100AC1614 /* PBXContainerItemProxy */ = { 54 | isa = PBXContainerItemProxy; 55 | containerPortal = FACD9C541B2A554100AC1614 /* Project object */; 56 | proxyType = 1; 57 | remoteGlobalIDString = FACD9C5C1B2A554100AC1614; 58 | remoteInfo = RazzleDazzle; 59 | }; 60 | /* End PBXContainerItemProxy section */ 61 | 62 | /* Begin PBXCopyFilesBuildPhase section */ 63 | FA5BC1D21B314DDB004DB083 /* CopyFiles */ = { 64 | isa = PBXCopyFilesBuildPhase; 65 | buildActionMask = 2147483647; 66 | dstPath = ""; 67 | dstSubfolderSpec = 10; 68 | files = ( 69 | FA5BC1D41B314DF6004DB083 /* Quick.framework in CopyFiles */, 70 | FA5BC1D31B314DF2004DB083 /* Nimble.framework in CopyFiles */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXCopyFilesBuildPhase section */ 75 | 76 | /* Begin PBXFileReference section */ 77 | FA0895721B3F188E002460BB /* HideAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HideAnimation.swift; sourceTree = ""; }; 78 | FA340D671B3B4F78003F2CD8 /* Animation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animation.swift; sourceTree = ""; }; 79 | FA340D6B1B3B5D9E003F2CD8 /* LayerFillColorAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayerFillColorAnimation.swift; sourceTree = ""; }; 80 | FA340D6D1B3B5E0B003F2CD8 /* LayerStrokeStartAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayerStrokeStartAnimation.swift; sourceTree = ""; }; 81 | FA340D6F1B3B5E54003F2CD8 /* LayerStrokeEndAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayerStrokeEndAnimation.swift; sourceTree = ""; }; 82 | FA340D711B3B5E9B003F2CD8 /* LayerStrokeColorAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayerStrokeColorAnimation.swift; sourceTree = ""; }; 83 | FA340D731B3B5EF5003F2CD8 /* LabelTextColorAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelTextColorAnimation.swift; sourceTree = ""; }; 84 | FA42E8661B2FDD200050795E /* ConstraintMultiplierAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstraintMultiplierAnimation.swift; sourceTree = ""; }; 85 | FA4D85DF1B2F6D900051770C /* ConstraintConstantAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstraintConstantAnimation.swift; sourceTree = ""; }; 86 | FA5991491B433F2600C0FBA9 /* PathPositionAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PathPositionAnimation.swift; sourceTree = ""; }; 87 | FA5BC1CC1B3147AE004DB083 /* RAZFilmstripSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RAZFilmstripSpec.swift; sourceTree = ""; }; 88 | FA5BC1CE1B314DCA004DB083 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/iOS/Nimble.framework; sourceTree = ""; }; 89 | FA5BC1CF1B314DCA004DB083 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/iOS/Quick.framework; sourceTree = ""; }; 90 | FA5BC1D51B314EAF004DB083 /* RAZEasingFunctionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RAZEasingFunctionSpec.swift; sourceTree = ""; }; 91 | FA5BC1D71B315347004DB083 /* RAZInterpolatableSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RAZInterpolatableSpec.swift; sourceTree = ""; }; 92 | FA5C96D01B2F759000BBA5DE /* CornerRadiusAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CornerRadiusAnimation.swift; sourceTree = ""; }; 93 | FA625DD11B32065000836DB2 /* RAZColorAnimationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RAZColorAnimationSpec.swift; sourceTree = ""; }; 94 | FA625DD31B32078000836DB2 /* RAZScaleAnimationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RAZScaleAnimationSpec.swift; sourceTree = ""; }; 95 | FA625DD51B320F1D00836DB2 /* RAZRotationAnimationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RAZRotationAnimationSpec.swift; sourceTree = ""; }; 96 | FA625DD71B320FA000836DB2 /* RAZTranslationAnimationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RAZTranslationAnimationSpec.swift; sourceTree = ""; }; 97 | FA625DD91B32116900836DB2 /* RAZAnimationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RAZAnimationSpec.swift; sourceTree = ""; }; 98 | FA71FBAF1B2FB24E00C49C16 /* EasingFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EasingFunction.swift; sourceTree = ""; }; 99 | FA71FBB11B2FB48F00C49C16 /* CubicBezier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CubicBezier.swift; sourceTree = ""; }; 100 | FA8ABF0F1B31F2FC00902AB9 /* RAZAlphaAnimationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RAZAlphaAnimationSpec.swift; path = RazzleDazzleTests/RAZAlphaAnimationSpec.swift; sourceTree = SOURCE_ROOT; }; 101 | FAA00CFA1B30A695003F2872 /* AnimatedPagingScrollViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AnimatedPagingScrollViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 102 | FAA00CFC1B30A6B2003F2872 /* ScrollViewPageConstraintAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewPageConstraintAnimation.swift; sourceTree = ""; }; 103 | FACD9C5D1B2A554100AC1614 /* RazzleDazzle.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RazzleDazzle.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 104 | FACD9C611B2A554100AC1614 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 105 | FACD9C621B2A554100AC1614 /* RazzleDazzle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RazzleDazzle.h; sourceTree = ""; }; 106 | FACD9C681B2A554100AC1614 /* RazzleDazzleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RazzleDazzleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 107 | FACD9C6E1B2A554100AC1614 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 108 | FACD9C851B2AABD000AC1614 /* Animator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animator.swift; sourceTree = ""; }; 109 | FACD9C891B2CB54200AC1614 /* AlphaAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AlphaAnimation.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 110 | FACD9C8F1B2CE91900AC1614 /* BackgroundColorAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundColorAnimation.swift; sourceTree = ""; }; 111 | FACD9C931B2D090C00AC1614 /* UIView+Transform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Transform.swift"; sourceTree = ""; }; 112 | FACD9C951B2D0A6000AC1614 /* ScaleAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaleAnimation.swift; sourceTree = ""; }; 113 | FACD9C971B2D0DD200AC1614 /* RotationAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RotationAnimation.swift; sourceTree = ""; }; 114 | FACD9C9B1B2E113C00AC1614 /* Interpolatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Interpolatable.swift; sourceTree = ""; }; 115 | FACD9C9D1B2E2F3500AC1614 /* Animatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Animatable.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 116 | FACD9C9F1B2E3FC000AC1614 /* Filmstrip.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Filmstrip.swift; sourceTree = ""; }; 117 | FACD9CA31B2E74DE00AC1614 /* TranslationAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationAnimation.swift; sourceTree = ""; }; 118 | /* End PBXFileReference section */ 119 | 120 | /* Begin PBXFrameworksBuildPhase section */ 121 | FACD9C591B2A554100AC1614 /* Frameworks */ = { 122 | isa = PBXFrameworksBuildPhase; 123 | buildActionMask = 2147483647; 124 | files = ( 125 | ); 126 | runOnlyForDeploymentPostprocessing = 0; 127 | }; 128 | FACD9C651B2A554100AC1614 /* Frameworks */ = { 129 | isa = PBXFrameworksBuildPhase; 130 | buildActionMask = 2147483647; 131 | files = ( 132 | FA5BC1D01B314DCA004DB083 /* Nimble.framework in Frameworks */, 133 | FA5BC1D11B314DCA004DB083 /* Quick.framework in Frameworks */, 134 | FACD9C691B2A554100AC1614 /* RazzleDazzle.framework in Frameworks */, 135 | ); 136 | runOnlyForDeploymentPostprocessing = 0; 137 | }; 138 | /* End PBXFrameworksBuildPhase section */ 139 | 140 | /* Begin PBXGroup section */ 141 | FACD9C531B2A554100AC1614 = { 142 | isa = PBXGroup; 143 | children = ( 144 | FA5BC1CE1B314DCA004DB083 /* Nimble.framework */, 145 | FA5BC1CF1B314DCA004DB083 /* Quick.framework */, 146 | FACD9C5F1B2A554100AC1614 /* Source */, 147 | FACD9C6C1B2A554100AC1614 /* RazzleDazzleTests */, 148 | FACD9C5E1B2A554100AC1614 /* Products */, 149 | ); 150 | sourceTree = ""; 151 | }; 152 | FACD9C5E1B2A554100AC1614 /* Products */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | FACD9C5D1B2A554100AC1614 /* RazzleDazzle.framework */, 156 | FACD9C681B2A554100AC1614 /* RazzleDazzleTests.xctest */, 157 | ); 158 | name = Products; 159 | sourceTree = ""; 160 | }; 161 | FACD9C5F1B2A554100AC1614 /* Source */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | FACD9C621B2A554100AC1614 /* RazzleDazzle.h */, 165 | FAA00CFA1B30A695003F2872 /* AnimatedPagingScrollViewController.swift */, 166 | FACD9C851B2AABD000AC1614 /* Animator.swift */, 167 | FACD9C9F1B2E3FC000AC1614 /* Filmstrip.swift */, 168 | FACD9C9B1B2E113C00AC1614 /* Interpolatable.swift */, 169 | FACD9C9D1B2E2F3500AC1614 /* Animatable.swift */, 170 | FACD9C891B2CB54200AC1614 /* AlphaAnimation.swift */, 171 | FA0895721B3F188E002460BB /* HideAnimation.swift */, 172 | FA340D671B3B4F78003F2CD8 /* Animation.swift */, 173 | FA5991491B433F2600C0FBA9 /* PathPositionAnimation.swift */, 174 | FA340D6B1B3B5D9E003F2CD8 /* LayerFillColorAnimation.swift */, 175 | FA340D6D1B3B5E0B003F2CD8 /* LayerStrokeStartAnimation.swift */, 176 | FA340D6F1B3B5E54003F2CD8 /* LayerStrokeEndAnimation.swift */, 177 | FA340D711B3B5E9B003F2CD8 /* LayerStrokeColorAnimation.swift */, 178 | FACD9C8F1B2CE91900AC1614 /* BackgroundColorAnimation.swift */, 179 | FA340D731B3B5EF5003F2CD8 /* LabelTextColorAnimation.swift */, 180 | FACD9C951B2D0A6000AC1614 /* ScaleAnimation.swift */, 181 | FACD9C971B2D0DD200AC1614 /* RotationAnimation.swift */, 182 | FACD9CA31B2E74DE00AC1614 /* TranslationAnimation.swift */, 183 | FA5C96D01B2F759000BBA5DE /* CornerRadiusAnimation.swift */, 184 | FA4D85DF1B2F6D900051770C /* ConstraintConstantAnimation.swift */, 185 | FA42E8661B2FDD200050795E /* ConstraintMultiplierAnimation.swift */, 186 | FAA00CFC1B30A6B2003F2872 /* ScrollViewPageConstraintAnimation.swift */, 187 | FA71FBAF1B2FB24E00C49C16 /* EasingFunction.swift */, 188 | FA71FBB11B2FB48F00C49C16 /* CubicBezier.swift */, 189 | FACD9C931B2D090C00AC1614 /* UIView+Transform.swift */, 190 | FACD9C601B2A554100AC1614 /* Supporting Files */, 191 | ); 192 | name = Source; 193 | path = ../Source; 194 | sourceTree = ""; 195 | }; 196 | FACD9C601B2A554100AC1614 /* Supporting Files */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | FACD9C611B2A554100AC1614 /* Info.plist */, 200 | ); 201 | name = "Supporting Files"; 202 | sourceTree = ""; 203 | }; 204 | FACD9C6C1B2A554100AC1614 /* RazzleDazzleTests */ = { 205 | isa = PBXGroup; 206 | children = ( 207 | FA5BC1CC1B3147AE004DB083 /* RAZFilmstripSpec.swift */, 208 | FA5BC1D51B314EAF004DB083 /* RAZEasingFunctionSpec.swift */, 209 | FA5BC1D71B315347004DB083 /* RAZInterpolatableSpec.swift */, 210 | FA8ABF0F1B31F2FC00902AB9 /* RAZAlphaAnimationSpec.swift */, 211 | FA625DD11B32065000836DB2 /* RAZColorAnimationSpec.swift */, 212 | FA625DD31B32078000836DB2 /* RAZScaleAnimationSpec.swift */, 213 | FA625DD51B320F1D00836DB2 /* RAZRotationAnimationSpec.swift */, 214 | FA625DD71B320FA000836DB2 /* RAZTranslationAnimationSpec.swift */, 215 | FA625DD91B32116900836DB2 /* RAZAnimationSpec.swift */, 216 | FACD9C6D1B2A554100AC1614 /* Supporting Files */, 217 | ); 218 | path = RazzleDazzleTests; 219 | sourceTree = ""; 220 | }; 221 | FACD9C6D1B2A554100AC1614 /* Supporting Files */ = { 222 | isa = PBXGroup; 223 | children = ( 224 | FACD9C6E1B2A554100AC1614 /* Info.plist */, 225 | ); 226 | name = "Supporting Files"; 227 | sourceTree = ""; 228 | }; 229 | /* End PBXGroup section */ 230 | 231 | /* Begin PBXHeadersBuildPhase section */ 232 | FACD9C5A1B2A554100AC1614 /* Headers */ = { 233 | isa = PBXHeadersBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | FACD9C631B2A554100AC1614 /* RazzleDazzle.h in Headers */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | /* End PBXHeadersBuildPhase section */ 241 | 242 | /* Begin PBXNativeTarget section */ 243 | FACD9C5C1B2A554100AC1614 /* RazzleDazzle */ = { 244 | isa = PBXNativeTarget; 245 | buildConfigurationList = FACD9C731B2A554100AC1614 /* Build configuration list for PBXNativeTarget "RazzleDazzle" */; 246 | buildPhases = ( 247 | FACD9C581B2A554100AC1614 /* Sources */, 248 | FACD9C591B2A554100AC1614 /* Frameworks */, 249 | FACD9C5A1B2A554100AC1614 /* Headers */, 250 | FACD9C5B1B2A554100AC1614 /* Resources */, 251 | ); 252 | buildRules = ( 253 | ); 254 | dependencies = ( 255 | ); 256 | name = RazzleDazzle; 257 | productName = RazzleDazzle; 258 | productReference = FACD9C5D1B2A554100AC1614 /* RazzleDazzle.framework */; 259 | productType = "com.apple.product-type.framework"; 260 | }; 261 | FACD9C671B2A554100AC1614 /* RazzleDazzleTests */ = { 262 | isa = PBXNativeTarget; 263 | buildConfigurationList = FACD9C761B2A554100AC1614 /* Build configuration list for PBXNativeTarget "RazzleDazzleTests" */; 264 | buildPhases = ( 265 | FACD9C641B2A554100AC1614 /* Sources */, 266 | FACD9C651B2A554100AC1614 /* Frameworks */, 267 | FACD9C661B2A554100AC1614 /* Resources */, 268 | FA5BC1D21B314DDB004DB083 /* CopyFiles */, 269 | ); 270 | buildRules = ( 271 | ); 272 | dependencies = ( 273 | FACD9C6B1B2A554100AC1614 /* PBXTargetDependency */, 274 | ); 275 | name = RazzleDazzleTests; 276 | productName = RazzleDazzleTests; 277 | productReference = FACD9C681B2A554100AC1614 /* RazzleDazzleTests.xctest */; 278 | productType = "com.apple.product-type.bundle.unit-test"; 279 | }; 280 | /* End PBXNativeTarget section */ 281 | 282 | /* Begin PBXProject section */ 283 | FACD9C541B2A554100AC1614 /* Project object */ = { 284 | isa = PBXProject; 285 | attributes = { 286 | LastSwiftUpdateCheck = 0700; 287 | LastUpgradeCheck = 0800; 288 | ORGANIZATIONNAME = IFTTT; 289 | TargetAttributes = { 290 | FACD9C5C1B2A554100AC1614 = { 291 | CreatedOnToolsVersion = 6.3.1; 292 | LastSwiftMigration = 0800; 293 | ProvisioningStyle = Automatic; 294 | }; 295 | FACD9C671B2A554100AC1614 = { 296 | CreatedOnToolsVersion = 6.3.1; 297 | DevelopmentTeamName = "Raphael Cruzeiro (Personal Team)"; 298 | LastSwiftMigration = 0800; 299 | }; 300 | }; 301 | }; 302 | buildConfigurationList = FACD9C571B2A554100AC1614 /* Build configuration list for PBXProject "RazzleDazzle" */; 303 | compatibilityVersion = "Xcode 3.2"; 304 | developmentRegion = English; 305 | hasScannedForEncodings = 0; 306 | knownRegions = ( 307 | en, 308 | ); 309 | mainGroup = FACD9C531B2A554100AC1614; 310 | productRefGroup = FACD9C5E1B2A554100AC1614 /* Products */; 311 | projectDirPath = ""; 312 | projectRoot = ""; 313 | targets = ( 314 | FACD9C5C1B2A554100AC1614 /* RazzleDazzle */, 315 | FACD9C671B2A554100AC1614 /* RazzleDazzleTests */, 316 | ); 317 | }; 318 | /* End PBXProject section */ 319 | 320 | /* Begin PBXResourcesBuildPhase section */ 321 | FACD9C5B1B2A554100AC1614 /* Resources */ = { 322 | isa = PBXResourcesBuildPhase; 323 | buildActionMask = 2147483647; 324 | files = ( 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | FACD9C661B2A554100AC1614 /* Resources */ = { 329 | isa = PBXResourcesBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | ); 333 | runOnlyForDeploymentPostprocessing = 0; 334 | }; 335 | /* End PBXResourcesBuildPhase section */ 336 | 337 | /* Begin PBXSourcesBuildPhase section */ 338 | FACD9C581B2A554100AC1614 /* Sources */ = { 339 | isa = PBXSourcesBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | FACD9C961B2D0A6000AC1614 /* ScaleAnimation.swift in Sources */, 343 | FACD9C861B2AABD000AC1614 /* Animator.swift in Sources */, 344 | FACD9C941B2D090C00AC1614 /* UIView+Transform.swift in Sources */, 345 | FA5C96D11B2F759000BBA5DE /* CornerRadiusAnimation.swift in Sources */, 346 | FACD9C981B2D0DD200AC1614 /* RotationAnimation.swift in Sources */, 347 | FACD9C9C1B2E113C00AC1614 /* Interpolatable.swift in Sources */, 348 | FA59914A1B433F2600C0FBA9 /* PathPositionAnimation.swift in Sources */, 349 | FACD9C9E1B2E2F3500AC1614 /* Animatable.swift in Sources */, 350 | FA4D85E01B2F6D900051770C /* ConstraintConstantAnimation.swift in Sources */, 351 | FA0895731B3F188E002460BB /* HideAnimation.swift in Sources */, 352 | FA71FBB01B2FB24E00C49C16 /* EasingFunction.swift in Sources */, 353 | FA42E8671B2FDD200050795E /* ConstraintMultiplierAnimation.swift in Sources */, 354 | FACD9CA41B2E74DE00AC1614 /* TranslationAnimation.swift in Sources */, 355 | FA340D701B3B5E54003F2CD8 /* LayerStrokeEndAnimation.swift in Sources */, 356 | FA71FBB21B2FB48F00C49C16 /* CubicBezier.swift in Sources */, 357 | FA340D741B3B5EF5003F2CD8 /* LabelTextColorAnimation.swift in Sources */, 358 | FACD9C8A1B2CB54200AC1614 /* AlphaAnimation.swift in Sources */, 359 | FA340D721B3B5E9B003F2CD8 /* LayerStrokeColorAnimation.swift in Sources */, 360 | FA340D681B3B4F78003F2CD8 /* Animation.swift in Sources */, 361 | FA340D6C1B3B5D9E003F2CD8 /* LayerFillColorAnimation.swift in Sources */, 362 | FA340D6E1B3B5E0B003F2CD8 /* LayerStrokeStartAnimation.swift in Sources */, 363 | FACD9CA01B2E3FC000AC1614 /* Filmstrip.swift in Sources */, 364 | FACD9C901B2CE91900AC1614 /* BackgroundColorAnimation.swift in Sources */, 365 | FAA00CFD1B30A6B2003F2872 /* ScrollViewPageConstraintAnimation.swift in Sources */, 366 | FAA00CFB1B30A695003F2872 /* AnimatedPagingScrollViewController.swift in Sources */, 367 | ); 368 | runOnlyForDeploymentPostprocessing = 0; 369 | }; 370 | FACD9C641B2A554100AC1614 /* Sources */ = { 371 | isa = PBXSourcesBuildPhase; 372 | buildActionMask = 2147483647; 373 | files = ( 374 | FA8ABF101B31F2FC00902AB9 /* RAZAlphaAnimationSpec.swift in Sources */, 375 | FA625DDA1B32116900836DB2 /* RAZAnimationSpec.swift in Sources */, 376 | FA625DD81B320FA000836DB2 /* RAZTranslationAnimationSpec.swift in Sources */, 377 | FA5BC1D61B314EAF004DB083 /* RAZEasingFunctionSpec.swift in Sources */, 378 | FA625DD41B32078000836DB2 /* RAZScaleAnimationSpec.swift in Sources */, 379 | FA625DD21B32065000836DB2 /* RAZColorAnimationSpec.swift in Sources */, 380 | FA5BC1D81B315347004DB083 /* RAZInterpolatableSpec.swift in Sources */, 381 | FA625DD61B320F1D00836DB2 /* RAZRotationAnimationSpec.swift in Sources */, 382 | FA5BC1CD1B3147AE004DB083 /* RAZFilmstripSpec.swift in Sources */, 383 | ); 384 | runOnlyForDeploymentPostprocessing = 0; 385 | }; 386 | /* End PBXSourcesBuildPhase section */ 387 | 388 | /* Begin PBXTargetDependency section */ 389 | FACD9C6B1B2A554100AC1614 /* PBXTargetDependency */ = { 390 | isa = PBXTargetDependency; 391 | target = FACD9C5C1B2A554100AC1614 /* RazzleDazzle */; 392 | targetProxy = FACD9C6A1B2A554100AC1614 /* PBXContainerItemProxy */; 393 | }; 394 | /* End PBXTargetDependency section */ 395 | 396 | /* Begin XCBuildConfiguration section */ 397 | FACD9C711B2A554100AC1614 /* Debug */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | ALWAYS_SEARCH_USER_PATHS = NO; 401 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 402 | CLANG_CXX_LIBRARY = "libc++"; 403 | CLANG_ENABLE_MODULES = YES; 404 | CLANG_ENABLE_OBJC_ARC = YES; 405 | CLANG_WARN_BOOL_CONVERSION = YES; 406 | CLANG_WARN_CONSTANT_CONVERSION = YES; 407 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 408 | CLANG_WARN_EMPTY_BODY = YES; 409 | CLANG_WARN_ENUM_CONVERSION = YES; 410 | CLANG_WARN_INFINITE_RECURSION = YES; 411 | CLANG_WARN_INT_CONVERSION = YES; 412 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 413 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 414 | CLANG_WARN_UNREACHABLE_CODE = YES; 415 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 416 | COPY_PHASE_STRIP = NO; 417 | CURRENT_PROJECT_VERSION = 1; 418 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 419 | ENABLE_STRICT_OBJC_MSGSEND = YES; 420 | ENABLE_TESTABILITY = YES; 421 | GCC_C_LANGUAGE_STANDARD = gnu99; 422 | GCC_DYNAMIC_NO_PIC = NO; 423 | GCC_NO_COMMON_BLOCKS = YES; 424 | GCC_OPTIMIZATION_LEVEL = 0; 425 | GCC_PREPROCESSOR_DEFINITIONS = ( 426 | "DEBUG=1", 427 | "$(inherited)", 428 | ); 429 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 430 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 431 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 432 | GCC_WARN_UNDECLARED_SELECTOR = YES; 433 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 434 | GCC_WARN_UNUSED_FUNCTION = YES; 435 | GCC_WARN_UNUSED_VARIABLE = YES; 436 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 437 | MTL_ENABLE_DEBUG_INFO = YES; 438 | ONLY_ACTIVE_ARCH = YES; 439 | SDKROOT = iphoneos; 440 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 441 | TARGETED_DEVICE_FAMILY = "1,2"; 442 | VERSIONING_SYSTEM = "apple-generic"; 443 | VERSION_INFO_PREFIX = ""; 444 | }; 445 | name = Debug; 446 | }; 447 | FACD9C721B2A554100AC1614 /* Release */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | ALWAYS_SEARCH_USER_PATHS = NO; 451 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 452 | CLANG_CXX_LIBRARY = "libc++"; 453 | CLANG_ENABLE_MODULES = YES; 454 | CLANG_ENABLE_OBJC_ARC = YES; 455 | CLANG_WARN_BOOL_CONVERSION = YES; 456 | CLANG_WARN_CONSTANT_CONVERSION = YES; 457 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 458 | CLANG_WARN_EMPTY_BODY = YES; 459 | CLANG_WARN_ENUM_CONVERSION = YES; 460 | CLANG_WARN_INFINITE_RECURSION = YES; 461 | CLANG_WARN_INT_CONVERSION = YES; 462 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 463 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 464 | CLANG_WARN_UNREACHABLE_CODE = YES; 465 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 466 | COPY_PHASE_STRIP = YES; 467 | CURRENT_PROJECT_VERSION = 1; 468 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 469 | ENABLE_NS_ASSERTIONS = NO; 470 | ENABLE_STRICT_OBJC_MSGSEND = YES; 471 | GCC_C_LANGUAGE_STANDARD = gnu99; 472 | GCC_NO_COMMON_BLOCKS = YES; 473 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 474 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 475 | GCC_WARN_UNDECLARED_SELECTOR = YES; 476 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 477 | GCC_WARN_UNUSED_FUNCTION = YES; 478 | GCC_WARN_UNUSED_VARIABLE = YES; 479 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 480 | MTL_ENABLE_DEBUG_INFO = NO; 481 | SDKROOT = iphoneos; 482 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 483 | TARGETED_DEVICE_FAMILY = "1,2"; 484 | VALIDATE_PRODUCT = YES; 485 | VERSIONING_SYSTEM = "apple-generic"; 486 | VERSION_INFO_PREFIX = ""; 487 | }; 488 | name = Release; 489 | }; 490 | FACD9C741B2A554100AC1614 /* Debug */ = { 491 | isa = XCBuildConfiguration; 492 | buildSettings = { 493 | APPLICATION_EXTENSION_API_ONLY = YES; 494 | CLANG_ENABLE_MODULES = YES; 495 | CODE_SIGN_IDENTITY = "iPhone Developer"; 496 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 497 | DEFINES_MODULE = YES; 498 | DYLIB_COMPATIBILITY_VERSION = 1; 499 | DYLIB_CURRENT_VERSION = 1; 500 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 501 | FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/iOS"; 502 | INFOPLIST_FILE = "$(SRCROOT)/../Source/Info.plist"; 503 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 504 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 505 | PRODUCT_BUNDLE_IDENTIFIER = "ifttt.$(PRODUCT_NAME:rfc1034identifier)"; 506 | PRODUCT_NAME = "$(TARGET_NAME)"; 507 | SKIP_INSTALL = YES; 508 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 509 | SWIFT_VERSION = 3.0; 510 | }; 511 | name = Debug; 512 | }; 513 | FACD9C751B2A554100AC1614 /* Release */ = { 514 | isa = XCBuildConfiguration; 515 | buildSettings = { 516 | APPLICATION_EXTENSION_API_ONLY = YES; 517 | CLANG_ENABLE_MODULES = YES; 518 | CODE_SIGN_IDENTITY = "iPhone Developer"; 519 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 520 | DEFINES_MODULE = YES; 521 | DYLIB_COMPATIBILITY_VERSION = 1; 522 | DYLIB_CURRENT_VERSION = 1; 523 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 524 | FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/iOS"; 525 | INFOPLIST_FILE = "$(SRCROOT)/../Source/Info.plist"; 526 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 527 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 528 | PRODUCT_BUNDLE_IDENTIFIER = "ifttt.$(PRODUCT_NAME:rfc1034identifier)"; 529 | PRODUCT_NAME = "$(TARGET_NAME)"; 530 | SKIP_INSTALL = YES; 531 | SWIFT_VERSION = 3.0; 532 | }; 533 | name = Release; 534 | }; 535 | FACD9C771B2A554100AC1614 /* Debug */ = { 536 | isa = XCBuildConfiguration; 537 | buildSettings = { 538 | CODE_SIGN_IDENTITY = "iPhone Developer"; 539 | FRAMEWORK_SEARCH_PATHS = ( 540 | "$(inherited)", 541 | "$(PROJECT_DIR)/Carthage/Build/iOS", 542 | ); 543 | GCC_PREPROCESSOR_DEFINITIONS = ( 544 | "DEBUG=1", 545 | "$(inherited)", 546 | ); 547 | INFOPLIST_FILE = RazzleDazzleTests/Info.plist; 548 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 549 | PRODUCT_BUNDLE_IDENTIFIER = "ifttt.$(PRODUCT_NAME:rfc1034identifier)"; 550 | PRODUCT_NAME = "$(TARGET_NAME)"; 551 | SWIFT_VERSION = 3.0; 552 | }; 553 | name = Debug; 554 | }; 555 | FACD9C781B2A554100AC1614 /* Release */ = { 556 | isa = XCBuildConfiguration; 557 | buildSettings = { 558 | CODE_SIGN_IDENTITY = "iPhone Developer"; 559 | FRAMEWORK_SEARCH_PATHS = ( 560 | "$(inherited)", 561 | "$(PROJECT_DIR)/Carthage/Build/iOS", 562 | ); 563 | INFOPLIST_FILE = RazzleDazzleTests/Info.plist; 564 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 565 | PRODUCT_BUNDLE_IDENTIFIER = "ifttt.$(PRODUCT_NAME:rfc1034identifier)"; 566 | PRODUCT_NAME = "$(TARGET_NAME)"; 567 | SWIFT_VERSION = 3.0; 568 | }; 569 | name = Release; 570 | }; 571 | /* End XCBuildConfiguration section */ 572 | 573 | /* Begin XCConfigurationList section */ 574 | FACD9C571B2A554100AC1614 /* Build configuration list for PBXProject "RazzleDazzle" */ = { 575 | isa = XCConfigurationList; 576 | buildConfigurations = ( 577 | FACD9C711B2A554100AC1614 /* Debug */, 578 | FACD9C721B2A554100AC1614 /* Release */, 579 | ); 580 | defaultConfigurationIsVisible = 0; 581 | defaultConfigurationName = Release; 582 | }; 583 | FACD9C731B2A554100AC1614 /* Build configuration list for PBXNativeTarget "RazzleDazzle" */ = { 584 | isa = XCConfigurationList; 585 | buildConfigurations = ( 586 | FACD9C741B2A554100AC1614 /* Debug */, 587 | FACD9C751B2A554100AC1614 /* Release */, 588 | ); 589 | defaultConfigurationIsVisible = 0; 590 | defaultConfigurationName = Release; 591 | }; 592 | FACD9C761B2A554100AC1614 /* Build configuration list for PBXNativeTarget "RazzleDazzleTests" */ = { 593 | isa = XCConfigurationList; 594 | buildConfigurations = ( 595 | FACD9C771B2A554100AC1614 /* Debug */, 596 | FACD9C781B2A554100AC1614 /* Release */, 597 | ); 598 | defaultConfigurationIsVisible = 0; 599 | defaultConfigurationName = Release; 600 | }; 601 | /* End XCConfigurationList section */ 602 | }; 603 | rootObject = FACD9C541B2A554100AC1614 /* Project object */; 604 | } 605 | -------------------------------------------------------------------------------- /Example/RazzleDazzle.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/RazzleDazzle.xcodeproj/xcshareddata/xcschemes/RazzleDazzle.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 98 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/RazzleDazzle.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/RazzleDazzleTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/RazzleDazzleTests/RAZAlphaAnimationSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphaAnimationSpec.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/17/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import RazzleDazzle 10 | 11 | import Nimble 12 | import Quick 13 | 14 | class AlphaAnimationSpec: QuickSpec { 15 | override func spec() { 16 | var view: UIView! 17 | var animation: AlphaAnimation! 18 | 19 | beforeEach { 20 | view = UIView() 21 | animation = AlphaAnimation(view: view) 22 | } 23 | describe("AlphaAnimation") { 24 | it("should add and retrieve keyframes") { 25 | animation[2] = 0.3 26 | expect(animation[2]).to(equal(0.3)) 27 | } 28 | it("should add and retrieve negative keyframes") { 29 | animation[-2] = 0.3 30 | expect(animation[-2]).to(equal(0.3)) 31 | } 32 | it("should add and retrieve multiple keyframes") { 33 | animation[-2] = 0.3 34 | animation[2] = 0.6 35 | expect(animation[-2]).to(equal(0.3)) 36 | expect(animation[2]).to(equal(0.6)) 37 | } 38 | it("should interpolate between keyframes") { 39 | animation[1] = 0.3 40 | animation[3] = 0.5 41 | expect(animation[2]).to(equal(0.4)) 42 | } 43 | it("should return the first value for times before the start time") { 44 | animation[2] = 0.3 45 | animation[4] = 0.5 46 | expect(animation[1]).to(equal(0.3)) 47 | expect(animation[0]).to(equal(0.3)) 48 | } 49 | it("should return the last value for times after the end time") { 50 | animation[2] = 0.3 51 | animation[4] = 0.5 52 | expect(animation[6]).to(equal(0.5)) 53 | expect(animation[10]).to(equal(0.5)) 54 | } 55 | it("should set a easing function") { 56 | animation.addKeyframe(1, value: 0.1, easing: EasingFunctionEaseInQuad) 57 | animation.addKeyframe(2, value: 0.2) 58 | expect(animation[1.5]).to(equal(((0.5 * 0.5) * 0.1) + 0.1)) 59 | } 60 | it("should use the easing function from the starting keyframe") { 61 | animation.addKeyframe(1, value: 0.1, easing: EasingFunctionEaseInQuad) 62 | animation.addKeyframe(2, value: 0.2, easing: EasingFunctionEaseOutBounce) 63 | expect(animation[1.5]).to(equal(((0.5 * 0.5) * 0.1) + 0.1)) 64 | } 65 | it("should use linear time for keyframes with no easing function") { 66 | animation.addKeyframe(1, value: 0.1, easing: EasingFunctionEaseInBounce) 67 | animation.addKeyframe(2, value: 0.2) 68 | animation.addKeyframe(3, value: 0.3) 69 | expect(animation[2.5]).to(equal(0.25)) 70 | } 71 | it("should apply changes to the view's alpha") { 72 | animation[1] = 0.3 73 | animation[3] = 0.5 74 | animation.animate(1) 75 | expect(view.alpha).to(beCloseTo(0.3)) 76 | animation.animate(2) 77 | expect(view.alpha).to(beCloseTo(0.4)) 78 | animation.animate(3) 79 | expect(view.alpha).to(beCloseTo(0.5)) 80 | } 81 | it("should do nothing if no keyframes have been set") { 82 | animation.animate(5) 83 | expect(view.alpha).to(equal(1)) 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Example/RazzleDazzleTests/RAZAnimationSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationSpec.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/17/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import RazzleDazzle 10 | 11 | import Nimble 12 | import Quick 13 | 14 | class AnimationSpec: QuickSpec { 15 | override func spec() { 16 | var view: UIView! 17 | 18 | beforeEach { 19 | view = UIView() 20 | } 21 | describe("Animations should apply all types of animation to the same view correctly") { 22 | var mixedTransform : CGAffineTransform! 23 | 24 | beforeEach { 25 | let animator = Animator() 26 | let alphaAnimation = AlphaAnimation(view: view) 27 | let colorAnimation = BackgroundColorAnimation(view: view) 28 | let scaleAnimation = ScaleAnimation(view: view) 29 | let rotationAnimation = RotationAnimation(view: view) 30 | let translationAnimation = TranslationAnimation(view: view) 31 | 32 | alphaAnimation[2] = 0.5 33 | colorAnimation[2] = UIColor.red 34 | scaleAnimation[2] = 3 35 | rotationAnimation[2] = 90 36 | translationAnimation[2] = CGPoint(x: 5, y: 15) 37 | 38 | animator.addAnimation(alphaAnimation) 39 | animator.addAnimation(colorAnimation) 40 | animator.addAnimation(scaleAnimation) 41 | animator.addAnimation(rotationAnimation) 42 | animator.addAnimation(translationAnimation) 43 | 44 | let scaleTransform = CGAffineTransform(scaleX: 3, y: 3) 45 | let rotationTransform = CGAffineTransform(rotationAngle: 90 * CGFloat(M_PI / -180.0)) 46 | let translationTransform = CGAffineTransform(translationX: 5, y: 15) 47 | mixedTransform = translationTransform.concatenating(scaleTransform.concatenating(rotationTransform)) 48 | 49 | animator.animate(2) 50 | } 51 | it("should apply the correct alpha to the view") { 52 | expect(view.alpha).to(beCloseTo(0.5)) 53 | } 54 | it("should apply the correct background color to the view") { 55 | expect(view.backgroundColor).to(equal(UIColor.red)) 56 | } 57 | it("should apply the correct transform to the view") { 58 | expect(view.transform == mixedTransform).to(beTruthy()) 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Example/RazzleDazzleTests/RAZColorAnimationSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorAnimationSpec.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/17/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import RazzleDazzle 10 | 11 | import Nimble 12 | import Quick 13 | 14 | class BackgroundColorAnimationSpec: QuickSpec { 15 | override func spec() { 16 | var view: UIView! 17 | var animation: BackgroundColorAnimation! 18 | 19 | beforeEach { 20 | view = UIView() 21 | view.backgroundColor = UIColor.white 22 | animation = BackgroundColorAnimation(view: view) 23 | } 24 | describe("ColorAnimation") { 25 | it("should add and retrieve keyframes") { 26 | animation[2] = UIColor.red 27 | expect(animation[2]).to(equal(UIColor.red)) 28 | } 29 | it("should add and retrieve negative keyframes") { 30 | animation[-2] = UIColor.red 31 | expect(animation[-2]).to(equal(UIColor.red)) 32 | } 33 | it("should add and retrieve multiple keyframes") { 34 | animation[-2] = UIColor.red 35 | animation[2] = UIColor.blue 36 | expect(animation[-2]).to(equal(UIColor.red)) 37 | expect(animation[2]).to(equal(UIColor.blue)) 38 | } 39 | it("should return the first value for times before the start time") { 40 | animation[2] = UIColor.red 41 | animation[4] = UIColor.blue 42 | expect(animation[1]).to(equal(UIColor.red)) 43 | expect(animation[0]).to(equal(UIColor.red)) 44 | } 45 | it("should return the last value for times after the end time") { 46 | animation[2] = UIColor.red 47 | animation[4] = UIColor.blue 48 | expect(animation[6]).to(equal(UIColor.blue)) 49 | expect(animation[10]).to(equal(UIColor.blue)) 50 | } 51 | it("should apply changes to the view's background color") { 52 | animation[1] = UIColor.red 53 | animation[3] = UIColor.blue 54 | animation.animate(1) 55 | expect(view.backgroundColor).to(equal(UIColor.red)) 56 | animation.animate(3) 57 | expect(view.backgroundColor).to(equal(UIColor.blue)) 58 | } 59 | it("should do nothing if no keyframes have been set") { 60 | animation.animate(5) 61 | expect(view.backgroundColor).to(equal(UIColor.white)) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Example/RazzleDazzleTests/RAZEasingFunctionSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasingFunctionSpec.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/16/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import RazzleDazzle 10 | 11 | import Nimble 12 | import Quick 13 | 14 | class EasingFunctionSpec: QuickSpec { 15 | override func spec() { 16 | describe("EasingFunctionLinear") { 17 | it("should return t") { 18 | expect(EasingFunctionLinear(1)).to(equal(1)) 19 | expect(EasingFunctionLinear(0.5)).to(equal(0.5)) 20 | expect(EasingFunctionLinear(0.25)).to(equal(0.25)) 21 | } 22 | } 23 | describe("EasingFunctionEaseInQuad") { 24 | it("should return t * t") { 25 | expect(EasingFunctionEaseInQuad(1)).to(equal(1)) 26 | expect(EasingFunctionEaseInQuad(0.5)).to(equal(0.25)) 27 | expect(EasingFunctionEaseInQuad(0.25)).to(equal(0.0625)) 28 | } 29 | } 30 | describe("EasingFunctionEaseInCubic") { 31 | it("should return t * t * t") { 32 | expect(EasingFunctionEaseInCubic(1)).to(equal(1)) 33 | expect(EasingFunctionEaseInCubic(0.5)).to(equal(0.125)) 34 | expect(EasingFunctionEaseInCubic(0.25)).to(equal(0.015625)) 35 | } 36 | } 37 | describe("EasingFunctionEaseOutQuad") { 38 | it("should return between 0 and 1") { 39 | expect(EasingFunctionEaseOutQuad(1)).to(equal(1)) 40 | expect(EasingFunctionEaseOutQuad(0.5)).to(beGreaterThan(0)) 41 | expect(EasingFunctionEaseOutQuad(0.5)).to(beLessThan(1)) 42 | expect(EasingFunctionEaseOutQuad(0)).to(equal(0)) 43 | } 44 | } 45 | describe("EasingFunctionEaseOutCubic") { 46 | it("should return between 0 and 1") { 47 | expect(EasingFunctionEaseOutCubic(1)).to(equal(1)) 48 | expect(EasingFunctionEaseOutCubic(0.5)).to(beGreaterThan(0)) 49 | expect(EasingFunctionEaseOutCubic(0.5)).to(beLessThan(1)) 50 | expect(EasingFunctionEaseOutCubic(0)).to(equal(0)) 51 | } 52 | } 53 | describe("EasingFunctionEaseInOutCubic") { 54 | it("should return between 0 and 1") { 55 | expect(EasingFunctionEaseInOutCubic(1)).to(equal(1)) 56 | expect(EasingFunctionEaseInOutCubic(0.5)).to(beGreaterThan(0)) 57 | expect(EasingFunctionEaseInOutCubic(0.5)).to(beLessThan(1)) 58 | expect(EasingFunctionEaseInOutCubic(0)).to(equal(0)) 59 | } 60 | } 61 | describe("EasingFunctionEaseInBounce") { 62 | it("should return between 0 and 1") { 63 | expect(EasingFunctionEaseInBounce(1)).to(equal(1)) 64 | expect(EasingFunctionEaseInBounce(0.5)).to(beGreaterThan(0)) 65 | expect(EasingFunctionEaseInBounce(0.5)).to(beLessThan(1)) 66 | expect(EasingFunctionEaseInBounce(0)).to(equal(0)) 67 | } 68 | } 69 | describe("EasingFunctionEaseOutBounce") { 70 | it("should return between 0 and 1") { 71 | expect(EasingFunctionEaseOutBounce(1)).to(equal(1)) 72 | expect(EasingFunctionEaseOutBounce(0.5)).to(beGreaterThan(0)) 73 | expect(EasingFunctionEaseOutBounce(0.5)).to(beLessThan(1)) 74 | expect(EasingFunctionEaseOutBounce(0)).to(equal(0)) 75 | } 76 | } 77 | describe("CubicBezier") { 78 | it("should return between 0 and 1") { 79 | let cubicBezierCurve = CubicBezier(0.17, 0.67, 0.83, 0.67) 80 | expect(cubicBezierCurve(1)).to(beGreaterThan(0.99)) 81 | expect(cubicBezierCurve(1)).to(beLessThan(1.01)) 82 | expect(cubicBezierCurve(0.5)).to(beGreaterThan(0)) 83 | expect(cubicBezierCurve(0.5)).to(beLessThan(1)) 84 | expect(cubicBezierCurve(0)).to(beLessThan(0.01)) 85 | expect(cubicBezierCurve(0)).to(beGreaterThan(-0.01)) 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Example/RazzleDazzleTests/RAZFilmstripSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilmstripSpec.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/16/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import RazzleDazzle 10 | 11 | import Nimble 12 | import Quick 13 | 14 | class FilmstripSpec: QuickSpec { 15 | override func spec() { 16 | var floatFilmstrip: Filmstrip! 17 | var colorFilmstrip: Filmstrip! 18 | 19 | beforeEach { 20 | floatFilmstrip = Filmstrip() 21 | colorFilmstrip = Filmstrip() 22 | } 23 | 24 | describe("Filmstrip") { 25 | it("should start empty") { 26 | expect(floatFilmstrip.isEmpty).to(beTruthy()) 27 | expect(colorFilmstrip.isEmpty).to(beTruthy()) 28 | } 29 | it("should set and get a float") { 30 | floatFilmstrip[2] = 3 31 | expect(floatFilmstrip[2]).to(equal(3)) 32 | } 33 | it("should set and get a color") { 34 | colorFilmstrip[2] = UIColor.red 35 | expect(colorFilmstrip[2]).to(equal(UIColor.red)) 36 | } 37 | it("should set and get multiple floats") { 38 | floatFilmstrip[2] = 3 39 | floatFilmstrip[5] = 8 40 | expect(floatFilmstrip[2]).to(equal(3)) 41 | expect(floatFilmstrip[5]).to(equal(8)) 42 | } 43 | it("should set and get multiple colors") { 44 | colorFilmstrip[2] = UIColor.red 45 | colorFilmstrip[5] = UIColor.blue 46 | expect(colorFilmstrip[2]).to(equal(UIColor.red)) 47 | expect(colorFilmstrip[5]).to(equal(UIColor.blue)) 48 | } 49 | it("should interpolate between values") { 50 | floatFilmstrip[2] = 3 51 | floatFilmstrip[4] = 5 52 | expect(floatFilmstrip[3]).to(equal(4)) 53 | } 54 | it("should return the first value for times before the start time") { 55 | floatFilmstrip[2] = 3 56 | floatFilmstrip[4] = 5 57 | expect(floatFilmstrip[1]).to(equal(3)) 58 | expect(floatFilmstrip[0]).to(equal(3)) 59 | } 60 | it("should return the first value for times before the start time") { 61 | colorFilmstrip[2] = UIColor.red 62 | colorFilmstrip[5] = UIColor.blue 63 | expect(colorFilmstrip[1]).to(equal(UIColor.red)) 64 | expect(colorFilmstrip[0]).to(equal(UIColor.red)) 65 | } 66 | it("should return the last value for times after the end time") { 67 | floatFilmstrip[2] = 3 68 | floatFilmstrip[4] = 5 69 | expect(floatFilmstrip[6]).to(equal(5)) 70 | expect(floatFilmstrip[10]).to(equal(5)) 71 | } 72 | it("should return the last value for times after the end time") { 73 | colorFilmstrip[2] = UIColor.red 74 | colorFilmstrip[5] = UIColor.blue 75 | expect(colorFilmstrip[6]).to(equal(UIColor.blue)) 76 | expect(colorFilmstrip[10]).to(equal(UIColor.blue)) 77 | } 78 | it("should work for negative times") { 79 | floatFilmstrip[-2] = 3 80 | floatFilmstrip[4] = 5 81 | expect(floatFilmstrip[-5]).to(equal(3)) 82 | expect(floatFilmstrip[-2]).to(equal(3)) 83 | expect(floatFilmstrip[4]).to(equal(5)) 84 | } 85 | it("should work for negative times") { 86 | colorFilmstrip[-2] = UIColor.red 87 | colorFilmstrip[4] = UIColor.blue 88 | expect(colorFilmstrip[-5]).to(equal(UIColor.red)) 89 | expect(colorFilmstrip[-2]).to(equal(UIColor.red)) 90 | expect(colorFilmstrip[4]).to(equal(UIColor.blue)) 91 | } 92 | it("should set a timing curve") { 93 | floatFilmstrip.setValue(10, atTime: 1, easing: EasingFunctionEaseInQuad) 94 | floatFilmstrip.setValue(20, atTime: 2) 95 | expect(floatFilmstrip.valueAtTime(1.5)).to(equal(((0.5 * 0.5) * 10.0) + 10.0)) 96 | } 97 | it("should use the timing curve from the starting keyframe") { 98 | floatFilmstrip.setValue(10, atTime: 1, easing: EasingFunctionEaseInQuad) 99 | floatFilmstrip.setValue(20, atTime: 2, easing: EasingFunctionEaseInBounce) 100 | expect(floatFilmstrip.valueAtTime(1.5)).to(equal(((0.5 * 0.5) * 10.0) + 10.0)) 101 | } 102 | it("should use linear time for keyframes with no timing curve") { 103 | floatFilmstrip.setValue(10, atTime: 1, easing: EasingFunctionEaseInBounce) 104 | floatFilmstrip.setValue(20, atTime: 2) 105 | floatFilmstrip.setValue(30, atTime: 3) 106 | expect(floatFilmstrip.valueAtTime(2.5)).to(equal(25)) 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Example/RazzleDazzleTests/RAZInterpolatableSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InterpolatableSpec.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/17/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import RazzleDazzle 10 | 11 | import Nimble 12 | import Quick 13 | 14 | class InterpolatableSpec: QuickSpec { 15 | override func spec() { 16 | describe("Interpolatable-CGFloat") { 17 | it("should interpolate floats") { 18 | let middleFloat = CGFloat.interpolateFrom(1, to: 3, withProgress: 0.5) 19 | expect(middleFloat).to(equal(2)) 20 | } 21 | it("should interpolate floats with less than halfway progress") { 22 | let middleFloat = CGFloat.interpolateFrom(1, to: 2, withProgress: 0.25) 23 | expect(middleFloat).to(equal(1.25)) 24 | } 25 | it("should interpolate floats with greater than halfway progress") { 26 | let middleFloat = CGFloat.interpolateFrom(1, to: 2, withProgress: 0.75) 27 | expect(middleFloat).to(equal(1.75)) 28 | } 29 | it("should interpolate floats with no progress") { 30 | let middleFloat = CGFloat.interpolateFrom(1, to: 2, withProgress: 0) 31 | expect(middleFloat).to(equal(1)) 32 | } 33 | it("should interpolate floats with full progress") { 34 | let middleFloat = CGFloat.interpolateFrom(1, to: 2, withProgress: 1) 35 | expect(middleFloat).to(equal(2)) 36 | } 37 | } 38 | describe("Interpolatable-CGPoint") { 39 | it("should interpolate points") { 40 | let middlePoint = CGPoint.interpolateFrom(CGPoint(x: 1, y: 2), to: CGPoint(x: 3, y: 6), withProgress: 0.5) 41 | expect(middlePoint).to(equal(CGPoint(x: 2, y: 4))) 42 | } 43 | } 44 | describe("Interpolatable-CGSize") { 45 | it("should interpolate sizes") { 46 | let middleSize = CGSize.interpolateFrom(CGSize(width: 1, height: 2), to: CGSize(width: 3, height: 6), withProgress: 0.5) 47 | expect(middleSize).to(equal(CGSize(width: 2, height: 4))) 48 | } 49 | } 50 | describe("Interpolatable-CGRect") { 51 | it("should interpolate rects") { 52 | let middleRect = CGRect.interpolateFrom(CGRect(x: 1, y: 2, width: 10, height: 20), to: CGRect(x: 3, y: 6, width: 30, height: 60), withProgress: 0.5) 53 | expect(middleRect).to(equal(CGRect(x: 2, y: 4, width: 20, height: 40))) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Example/RazzleDazzleTests/RAZRotationAnimationSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RotationAnimationSpec.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/17/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import RazzleDazzle 10 | 11 | import Nimble 12 | import Quick 13 | 14 | class RotationAnimationSpec: QuickSpec { 15 | override func spec() { 16 | var view: UIView! 17 | var animation: RotationAnimation! 18 | 19 | beforeEach { 20 | view = UIView() 21 | animation = RotationAnimation(view: view) 22 | } 23 | describe("RotationAnimation") { 24 | it("should add and retrieve keyframes") { 25 | animation[2] = 5 26 | expect(animation[2]).to(equal(5)) 27 | } 28 | it("should add and retrieve negative keyframes") { 29 | animation[-2] = 5 30 | expect(animation[-2]).to(equal(5)) 31 | } 32 | it("should add and retrieve multiple keyframes") { 33 | animation[-2] = 3 34 | animation[2] = 5 35 | expect(animation[-2]).to(equal(3)) 36 | expect(animation[2]).to(equal(5)) 37 | } 38 | it("should return the first value for times before the start time") { 39 | animation[2] = 3 40 | animation[4] = 5 41 | expect(animation[1]).to(equal(3)) 42 | expect(animation[0]).to(equal(3)) 43 | } 44 | it("should return the last value for times after the end time") { 45 | animation[2] = 3 46 | animation[4] = 5 47 | expect(animation[6]).to(equal(5)) 48 | expect(animation[10]).to(equal(5)) 49 | } 50 | it("should apply changes to the view's rotation transform") { 51 | animation[1] = 3 52 | animation[3] = 5 53 | let radiansOne = 3 * CGFloat(M_PI / -180.0) 54 | let radiansThree = 5 * CGFloat(M_PI / -180.0) 55 | animation.animate(1) 56 | expect(view.transform == CGAffineTransform(rotationAngle: radiansOne)).to(beTruthy()) 57 | animation.animate(3) 58 | expect(view.transform == CGAffineTransform(rotationAngle: radiansThree)).to(beTruthy()) 59 | } 60 | it("should do nothing if no keyframes have been set") { 61 | animation.animate(5) 62 | expect(view.transform == CGAffineTransform.identity).to(beTruthy()) 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Example/RazzleDazzleTests/RAZScaleAnimationSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScaleAnimationSpec.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/17/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import RazzleDazzle 10 | 11 | import Nimble 12 | import Quick 13 | 14 | class ScaleAnimationSpec: QuickSpec { 15 | override func spec() { 16 | var view: UIView! 17 | var animation: ScaleAnimation! 18 | 19 | beforeEach { 20 | view = UIView() 21 | animation = ScaleAnimation(view: view) 22 | } 23 | describe("ScaleAnimation") { 24 | it("should add and retrieve keyframes") { 25 | animation[2] = 5 26 | expect(animation[2]).to(equal(5)) 27 | } 28 | it("should add and retrieve negative keyframes") { 29 | animation[-2] = 5 30 | expect(animation[-2]).to(equal(5)) 31 | } 32 | it("should add and retrieve multiple keyframes") { 33 | animation[-2] = 3 34 | animation[2] = 5 35 | expect(animation[-2]).to(equal(3)) 36 | expect(animation[2]).to(equal(5)) 37 | } 38 | it("should return the first value for times before the start time") { 39 | animation[2] = 3 40 | animation[4] = 5 41 | expect(animation[1]).to(equal(3)) 42 | expect(animation[0]).to(equal(3)) 43 | } 44 | it("should return the last value for times after the end time") { 45 | animation[2] = 3 46 | animation[4] = 5 47 | expect(animation[6]).to(equal(5)) 48 | expect(animation[10]).to(equal(5)) 49 | } 50 | it("should apply changes to the view's scale transform") { 51 | animation[1] = 3 52 | animation[3] = 5 53 | animation.animate(1) 54 | expect(view.transform == CGAffineTransform(scaleX: 3, y: 3)).to(beTruthy()) 55 | animation.animate(3) 56 | expect(view.transform == CGAffineTransform(scaleX: 5, y: 5)).to(beTruthy()) 57 | } 58 | it("should do nothing if no keyframes have been set") { 59 | animation.animate(5) 60 | expect(view.transform == CGAffineTransform.identity).to(beTruthy()) 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Example/RazzleDazzleTests/RAZTranslationAnimationSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TranslationAnimationSpec.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/17/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import RazzleDazzle 10 | 11 | import Nimble 12 | import Quick 13 | 14 | class TranslationAnimationSpec: QuickSpec { 15 | override func spec() { 16 | var view: UIView! 17 | var animation: TranslationAnimation! 18 | 19 | beforeEach { 20 | view = UIView() 21 | animation = TranslationAnimation(view: view) 22 | } 23 | describe("TranslationAnimation") { 24 | it("should add and retrieve keyframes") { 25 | animation[2] = CGPoint(x: 1, y: 2) 26 | expect(animation[2].equalTo(CGPoint(x: 1, y: 2))).to(beTruthy()) 27 | } 28 | it("should add and retrieve negative keyframes") { 29 | animation[-2] = CGPoint(x: 1, y: 2) 30 | expect(animation[-2].equalTo(CGPoint(x: 1, y: 2))).to(beTruthy()) 31 | } 32 | it("should add and retrieve multiple keyframes") { 33 | animation[-2] = CGPoint(x: 1, y: 2) 34 | animation[2] = CGPoint(x: 3, y: 4) 35 | expect(animation[-2].equalTo(CGPoint(x: 1, y: 2))).to(beTruthy()) 36 | expect(animation[2].equalTo(CGPoint(x: 3, y: 4))).to(beTruthy()) 37 | } 38 | it("should return the first value for times before the start time") { 39 | animation[2] = CGPoint(x: 1, y: 2) 40 | animation[4] = CGPoint(x: 3, y: 4) 41 | expect(animation[1].equalTo(CGPoint(x: 1, y: 2))).to(beTruthy()) 42 | expect(animation[0].equalTo(CGPoint(x: 1, y: 2))).to(beTruthy()) 43 | } 44 | it("should return the last value for times after the end time") { 45 | animation[2] = CGPoint(x: 1, y: 2) 46 | animation[4] = CGPoint(x: 3, y: 4) 47 | expect(animation[5].equalTo(CGPoint(x: 3, y: 4))).to(beTruthy()) 48 | expect(animation[6].equalTo(CGPoint(x: 3, y: 4))).to(beTruthy()) 49 | } 50 | it("should apply changes to the view's translation transform") { 51 | animation[2] = CGPoint(x: 1, y: 2) 52 | animation[4] = CGPoint(x: 3, y: 4) 53 | animation.animate(2) 54 | expect(view.transform == CGAffineTransform(translationX: 1, y: 2)).to(beTruthy()) 55 | animation.animate(4) 56 | expect(view.transform == CGAffineTransform(translationX: 3, y: 4)).to(beTruthy()) 57 | } 58 | it("should do nothing if no keyframes have been set") { 59 | animation.animate(5) 60 | expect(view.transform == CGAffineTransform.identity).to(beTruthy()) 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 IFTTT Inc 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Open Source at IFTTT](http://ifttt.github.io/images/open-source-ifttt.svg)](http://ifttt.github.io) 2 | 3 | ![RazzleDazzle](./Example/Docs/razzledazzlebanner.jpg) 4 | 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![CocoaPods Version](https://img.shields.io/cocoapods/v/RazzleDazzle.svg)](http://cocoadocs.org/docsets/RazzleDazzle) 6 | 7 | `RazzleDazzle` is a simple AutoLayout-friendly keyframe animation framework for iOS, written in Swift. Perfect for scrolling app intros. 8 | 9 | ![RazzleDazzle](./Example/Docs/razzledazzle-demo.gif) 10 | 11 | `RazzleDazzle ` grew from [`JazzHands`](https://github.com/IFTTT/JazzHands), an Objective-C scrolling keyframe animations library by IFTTT. 12 | 13 | [`JazzHands`](https://github.com/IFTTT/JazzHands) is used extensively in [IF and DO for iPhone and iPad](https://ifttt.com/products), most famously in the app intro. 14 | 15 | ## What's `RazzleDazzle` for? 16 | ### Scrolling App Intro Animations 17 | `RazzleDazzle` is the easiest way to add scrollview-powered animations to the app intro of your Swift app. If you're adding a scrolling intro to your Objective-C app, check out [`JazzHands`](https://github.com/IFTTT/JazzHands)! 18 | 19 | For some examples of how [`JazzHands`](https://github.com/IFTTT/JazzHands) and `RazzleDazzle` can be used in practice, check out the intros of both [IF and DO for iPhone and iPad](https://ifttt.com/products), as well as the scrolling animations of the buttons in the DO apps by IFTTT. 20 | 21 | ### Easy Paging Scrollview Layouts in an AutoLayout World 22 | `RazzleDazzle`'s `keep(view: onPage:)` function of the `AnimatedPagingScrollViewController` is a super easy way to lay out a paging scroll view that does what you expect it to when your app is rotated or used in the new split-screen iPad views of iOS9, a notoriously tricky aspect of getting your apps fully AutoLayout-ready. `RazzleDazzle` sets up an AutoLayout-friendly paging scroll view controller for you, and all you need to do to make your layout respond properly to any view size changes is tell `RazzleDazzle` which page you'd like things on. 23 | 24 | As a bonus, because it's built on top of the animations library, you can even tell `RazzleDazzle` that you'd like one of your views to show up on multiple pages while other views scroll past, with a single call to `keep(view: onPages:)`. 25 | 26 | ## Installation 27 | 28 | ### Carthage 29 | 30 | `RazzleDazzle` is available through [Carthage](https://github.com/Carthage/Carthage). To install 31 | it, simply add the following line to your `Cartfile`: 32 | 33 | ``` 34 | github "IFTTT/RazzleDazzle" 35 | ``` 36 | 37 | ### CocoaPods 38 | 39 | `RazzleDazzle` is also available through [CocoaPods](http://cocoapods.org). To install 40 | it, simply add the following line to your `Podfile`: 41 | 42 | ```ruby 43 | pod "RazzleDazzle" 44 | ``` 45 | Because `RazzleDazzle` is written in Swift, be sure to add `use_frameworks!` at the top of your Podfile. 46 | 47 | ```ruby 48 | source 'https://github.com/CocoaPods/Specs.git' 49 | platform :ios, '8.0' 50 | use_frameworks! 51 | 52 | pod 'RazzleDazzle' 53 | ``` 54 | 55 | ## Demo App 56 | 57 | Open `Example/RazzleDazzle.xcworkspace` and run `RazzleDazzleDemo` to see a simple demonstration of moving, scaling, fading, and transforming views in a scrolling app intro. 58 | 59 | ## Usage 60 | 61 | ### Animated Paging Scroll Views 62 | First, import `RazzleDazzle` into your view controller, and subclass `AnimatedPagingScrollViewController`. 63 | 64 | ```swift 65 | import RazzleDazzle 66 | 67 | class ViewController: AnimatedPagingScrollViewController { 68 | ``` 69 | 70 | Tell the paging scroll view controller how many pages it should have. 71 | 72 | ```swift 73 | override func numberOfPages() -> Int { 74 | return 4 75 | } 76 | ``` 77 | 78 | Add any views you want to animate to the scrollview's `contentView` on `viewDidLoad`. 79 | 80 | ```swift 81 | override func viewDidLoad() { 82 | super.viewDidLoad() 83 | contentView.addSubview(firstLabel) 84 | } 85 | ``` 86 | 87 | Add your desired vertical position and size constraints to your views. 88 | 89 | ```swift 90 | contentView.addConstraint(NSLayoutConstraint(item: firstLabel, attribute: .CenterY, relatedBy: .Equal, toItem: contentView, attribute: .CenterY, multiplier: 1, constant: 0)) 91 | ``` 92 | 93 | Tell the animated paging scroll view controller to keep the view on the page you want it to stay on. 94 | 95 | ```swift 96 | keepView(firstLabel, onPage: 1) 97 | ``` 98 | 99 | You can even tell the animated paging scroll view controller to keep the view still on more than one page, while other views scroll past it. 100 | 101 | ```swift 102 | keepView(firstLabel, onPages: [1,2]) 103 | ``` 104 | 105 | Or offset the view's center from the page's center: 106 | 107 | ```swift 108 | keepView(firstLabel, onPage: 1.25) 109 | ``` 110 | 111 | Just make sure that if you're using any of the `keepView` functions that you don't set an x-position `NSLayoutConstraint` on the view, as it will conflict with the animated x-position constraints generated by `RazzleDazzle`. 112 | 113 | ### RazzleDazzle Animations 114 | Generally, creating animations in `RazzleDazzle` works similarly to creating animations in `JazzHands`. First, import `RazzleDazzle` into your view controller. 115 | 116 | ```swift 117 | import RazzleDazzle 118 | ``` 119 | 120 | Then, create an Animator to manage all of the animations in this `UIViewController`. 121 | 122 | ```swift 123 | var animator = Animator() 124 | ``` 125 | 126 | Create an animation for a view that you want to animate. There are multiple types of animation that can be applied to a view. For this example, we'll use `AlphaAnimation`, which fades a view in and out. 127 | 128 | ```swift 129 | let alphaAnimation = AlphaAnimation(view: viewThatYouWantToAnimate) 130 | ``` 131 | 132 | Register the animation with the animator. 133 | 134 | ```swift 135 | animator.addAnimation(alphaAnimation) 136 | ``` 137 | 138 | Add some keyframes to the animation. Let's fade this view out between times 30 and 60. 139 | 140 | ```swift 141 | alphaAnimation[30] = 1 142 | alphaAnimation[60] = 0 143 | ``` 144 | 145 | Now, to animate the view, tell the animator what time it is. For example, to tie this animation to a UIScrollView, notify the animator of time in the scroller's delegate method. 146 | 147 | ```swift 148 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 149 | animator.animate(scrollView.contentOffset.x) 150 | } 151 | ``` 152 | 153 | This will produce an effect where the view will be fully faded in and visible for scroll positions 0 to 30. Between scroll positions 30 and 60, the view will fade out to be invisible, and it will stay faded out for scroll positions greater than 60. 154 | 155 | ## Animation Types 156 | 157 | `RazzleDazzle` supports several types of animations: 158 | 159 | * **AlphaAnimation** animates the `alpha` property _(creates fade effects)_. 160 | * **BackgroundColorAnimation** animates the `backgroundColor` property. 161 | * **RotationAnimation** animates a rotation transform _(for rotation effects)_. 162 | * **ScaleAnimation** applies a scaling transform _(to scale view sizes)_. 163 | * **TranslationAnimation** applies a translation transform _(to translate view position)_. 164 | * **CornerRadiusAnimation** animates the `layer.cornerRadius` property. 165 | * **HideAnimation** animates the `hidden` property _(hides and shows views)_. 166 | * **LayerStrokeStartAnimation** animates the `strokeStart` property of a `CAShapeLayer` _(does not work with LayerStrokeEndAnimation)_. 167 | * **LayerStrokeEndAnimation** animates the `strokeEnd` property of a `CAShapeLayer` _(does not work with LayerStrokeStartAnimation)_. 168 | * **LayerFillColorAnimation** animates the `fillColor` property of a `CAShapeLayer`. 169 | * **LayerStrokeColorAnimation** animates the `strokeColor` property of a `CAShapeLayer`. 170 | * **PathPositionAnimation** animates the `layer.position` property of a `UIView` along a path. 171 | * **LabelTextColorAnimation** animates the `textColor` property of a `UILabel`. 172 | * **ConstraintConstantAnimation** animates an `AutoLayout` constraint constant. 173 | * **ConstraintMultiplierAnimation** animates an `AutoLayout` constraint constant as a multiple of an attribute of another view _(to offset or resize views based on another view's size)_ 174 | * **ScrollViewPageConstraintAnimation** animates an `AutoLayout` constraint constant to place a view on a scroll view page _(to position views on a scrollView using AutoLayout)_. This is the animation doing the heavy lifting for `AnimatedPagingScrollViewController`'s `keepView(view: onPage:)` function. 175 | 176 | ## Creating Custom Animation Types 177 | 178 | `RazzleDazzle` is easy to extend by creating your own custom animation types! 179 | 180 | ### Custom Animation Types 181 | 182 | To create your own custom animation type, your type needs to conform to the `Animatable` protocol. All this requires is that you implement an `animate(time:)` function that takes a `CGFloat` time value and does something with it. 183 | 184 | For most custom animations, you'll want to subclass `Animation` with the specific type of the property you want to interpolate for each keyframe. 185 | 186 | ```swift 187 | public class BorderWidthAnimation : Animation, Animatable { 188 | ``` 189 | 190 | Create a property to store whatever view (or other object) you are applying the animations to, and create an initializer that takes a view as input. 191 | 192 | ```swift 193 | private let view : UIView 194 | 195 | public init(view: UIView) { 196 | self.view = view 197 | } 198 | ``` 199 | 200 | Optionally, you can add a function to validate any input values that will be checked each time a keyframe is added, such as for Alpha values that must range from 0 to 1. 201 | 202 | ```swift 203 | public override func validateValue(_ value: CGFloat) -> Bool { 204 | return (value >= 0) && (value <= 1) 205 | } 206 | ``` 207 | 208 | Then, all you need to do is to make the appropriate changes to your view when the `animate(time:)` function is called. 209 | 210 | ```swift 211 | public func animate(_ time: CGFloat) { 212 | if !hasKeyframes() {return} 213 | view.layer.borderWidth = self[time] 214 | } 215 | ``` 216 | 217 | You can then create an instance of your new Animation in your `UIViewController`, give it the view you'd like to animate, add it to your Animator and set some keyframes as above, and it will animate your custom property when the Animator is told to animate. 218 | 219 | ### Interpolatable Types 220 | 221 | `RazzleDazzle` can animate any type that conforms to the `Interpolatable` protocol. It comes pre-cooked to support animating `CGFloats`, `CGPoints`, `CGSizes`, `CGRects`, and `UIColors`. 222 | 223 | If the property you'd like to animate is of a different type, just extend that type to conform to `Interpolatable` by adding a static function `interpolateFrom(fromValue: toValue: withProgress:)` that returns an instance of that type between two other instances of the same type. 224 | 225 | ```swift 226 | extension CGPoint : Interpolatable { 227 | public static func interpolateFrom(fromValue: CGPoint, to toValue: CGPoint, withProgress progress: CGFloat) -> CGPoint { 228 | assert((0 <= progress) && (progress <= 1), "Progress must be between 0 and 1") 229 | let interpolatedX = CGFloat.interpolateFrom(fromValue.x, to: toValue.x, withProgress: progress) 230 | let interpolatedY = CGFloat.interpolateFrom(fromValue.y, to: toValue.y, withProgress: progress) 231 | return CGPointMake(interpolatedX, interpolatedY) 232 | } 233 | } 234 | ``` 235 | 236 | If your property is a `CGFloat` or one of the other built-in interpolatable types, you only need to create an animation type that tells `RazzleDazzle` how to apply the keyframe values to your view, as above. 237 | 238 | ## Notes 239 | 240 | An animator can only handle one animation per type per view. If you want multiple animations of the same type on a view, use keyframes of a single animation instead of two separate animations. 241 | 242 | `RazzleDazzle` is written in Swift 3.0, so it will only compile in Xcode 8 and up. If you want to use a library like this that will integrate with an older version of Swift, you can use [`JazzHands`](https://github.com/IFTTT/JazzHands), which is written in Objective-C, and use a bridging header to access the methods from your Swift 1.2 classes. 243 | 244 | Looking for libraries to build awesome keyframe animations like RazzleDazzle on Android? Check out [`SparkleMotion`](https://github.com/IFTTT/SparkleMotion). 245 | 246 | ## Contributors 247 | 248 | * [Laura Skelton](https://github.com/lauraskelton), creator. 249 | 250 | ## Contributing 251 | 252 | 1. Fork it ( https://github.com/[my-github-username]/RazzleDazzle/fork ) 253 | 2. Create your feature branch (`git checkout -b my-new-feature`) 254 | 3. Commit your changes (`git commit -am 'Add some feature'`) 255 | 4. Push to the branch (`git push origin my-new-feature`) 256 | 5. Create a new Pull Request 257 | 258 | ## License 259 | 260 | `RazzleDazzle` is available under the MIT license. See the LICENSE file for more info. 261 | 262 | Copyright 2015 IFTTT Inc. 263 | -------------------------------------------------------------------------------- /RazzleDazzle.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "RazzleDazzle" 3 | s.version = "0.1.5" 4 | s.summary = "Simple Swift keyframe animations for scrolling intros" 5 | s.homepage = "https://github.com/IFTTT/RazzleDazzle" 6 | s.license = 'MIT' 7 | s.author = { 8 | "Laura Skelton" => "laura@ifttt.com", 9 | "Max Meyers" => "max@ifttt.com", 10 | "Devin Foley" => "devin@ifttt.com" 11 | } 12 | s.source = { :git => "https://github.com/IFTTT/RazzleDazzle.git", :tag => s.version.to_s } 13 | s.social_media_url = 'https://twitter.com/IFTTT' 14 | s.platform = :ios, '8.0' 15 | s.requires_arc = true 16 | s.compiler_flags = '-fmodules' 17 | s.frameworks = 'UIKit' 18 | s.description = "Razzle Dazzle is a Swift keyframe animation framework by IFTTT, based on Jazz Hands. Move UIViews around the screen based on UIScrollView input, KVO, or anything really. Works well with AutoLayout." 19 | s.source_files = 'Source/*.{h,swift}' 20 | 21 | end 22 | -------------------------------------------------------------------------------- /Source/AlphaAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphaAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/13/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the `alpha` property of a `UIView`. 13 | */ 14 | public class AlphaAnimation : Animation, Animatable { 15 | private let view : UIView 16 | 17 | public init(view: UIView) { 18 | self.view = view 19 | } 20 | 21 | public func animate(_ time: CGFloat) { 22 | if !hasKeyframes() {return} 23 | view.alpha = self[time] 24 | } 25 | 26 | public override func validateValue(_ value: CGFloat) -> Bool { 27 | return (value >= 0) && (value <= 1) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/Animatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Animatable.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/14/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Protocol for types that can animate a view for a given time. 13 | */ 14 | public protocol Animatable { 15 | /** 16 | Animate the view for a certain point in time. 17 | 18 | - parameter time: The point in time for which to animate the view 19 | */ 20 | func animate(_ time: CGFloat) 21 | } 22 | -------------------------------------------------------------------------------- /Source/AnimatedPagingScrollViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimatedPagingScrollViewController.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/16/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | View controller for creating scrolling app intros. Set animation times based on the page number, and this view controller handles calling `animate:` on the `animator`. 13 | */ 14 | open class AnimatedPagingScrollViewController : UIViewController, UIScrollViewDelegate { 15 | open let scrollView = UIScrollView() 16 | open let contentView = UIView() 17 | open var animator = Animator() 18 | private var scrollViewPageConstraintAnimations = [ScrollViewPageConstraintAnimation]() 19 | open var pageWidth : CGFloat { 20 | get { 21 | return scrollView.frame.width 22 | } 23 | } 24 | open var pageOffset : CGFloat { 25 | get { 26 | var currentOffset = scrollView.contentOffset.x 27 | if pageWidth > 0 { 28 | currentOffset = currentOffset / pageWidth 29 | } 30 | return currentOffset 31 | } 32 | } 33 | 34 | open func numberOfPages() -> Int { 35 | return 2 36 | } 37 | 38 | open override func viewDidLoad() { 39 | super.viewDidLoad() 40 | scrollView.delegate = self 41 | scrollView.isPagingEnabled = true 42 | scrollView.showsHorizontalScrollIndicator = false 43 | scrollView.contentSize = CGSize(width: CGFloat(numberOfPages()) * view.frame.width, height: view.frame.height) 44 | view.addSubview(scrollView) 45 | scrollView.frame = view.bounds 46 | scrollView.addSubview(contentView) 47 | scrollView.translatesAutoresizingMaskIntoConstraints = false 48 | contentView.translatesAutoresizingMaskIntoConstraints = false 49 | 50 | NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|[scrollView]|", options: [], metrics: nil, views: ["scrollView" : scrollView])) 51 | NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|[scrollView]|", options: [], metrics: nil, views: ["scrollView" : scrollView])) 52 | 53 | let scrollViewLeft = NSLayoutConstraint(item: scrollView, attribute: .left, relatedBy: .equal, toItem: contentView, attribute: .left, multiplier: 1, constant: 0) 54 | let scrollViewRight = NSLayoutConstraint(item: scrollView, attribute: .right, relatedBy: .equal, toItem: contentView, attribute: .right, multiplier: 1, constant: 0) 55 | let scrollViewTop = NSLayoutConstraint(item: scrollView, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 0) 56 | let scrollViewBottom = NSLayoutConstraint(item: scrollView, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: 0) 57 | 58 | NSLayoutConstraint.activate([scrollViewLeft, scrollViewRight, scrollViewTop, scrollViewBottom]) 59 | 60 | let contentViewWidth = NSLayoutConstraint(item: contentView, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: CGFloat(numberOfPages()), constant: 0) 61 | let contentViewHeight = NSLayoutConstraint(item: contentView, attribute: .height, relatedBy: .equal, toItem: view, attribute: .height, multiplier: 1, constant: 0) 62 | 63 | NSLayoutConstraint.activate([contentViewWidth, contentViewHeight]) 64 | } 65 | 66 | open override func viewDidAppear(_ animated: Bool) { 67 | super.viewDidAppear(animated) 68 | animateCurrentFrame() 69 | } 70 | 71 | open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 72 | super.viewWillTransition(to: size, with: coordinator) 73 | let newPageWidth = size.width 74 | for animation in scrollViewPageConstraintAnimations { 75 | animation.pageWidth = newPageWidth 76 | } 77 | let futurePixelOffset = pageOffset * newPageWidth 78 | coordinator.animate(alongsideTransition: {context in 79 | self.animateCurrentFrame() 80 | self.scrollView.contentOffset.x = futurePixelOffset 81 | }, completion: nil) 82 | } 83 | 84 | open func scrollViewDidScroll(_ scrollView: UIScrollView) { 85 | animateCurrentFrame() 86 | } 87 | 88 | open func animateCurrentFrame () { 89 | animator.animate(pageOffset) 90 | } 91 | 92 | open func keepView(_ view: UIView, onPage page: CGFloat) { 93 | keepView(view, onPage: page, withAttribute: .centerX) 94 | } 95 | 96 | open func keepView(_ view: UIView, onPage page: CGFloat, withAttribute attribute: HorizontalPositionAttribute) { 97 | view.translatesAutoresizingMaskIntoConstraints = false 98 | NSLayoutConstraint(item: view, attribute: layoutAttributeFromRazAttribute(attribute), relatedBy: .equal, toItem: contentView, attribute: .centerX, multiplier: multiplierForPage(page, attribute: attribute), constant: 0).isActive = true 99 | } 100 | 101 | open func keepView(_ view: UIView, onPages pages: [CGFloat]) { 102 | keepView(view, onPages: pages, atTimes: pages) 103 | } 104 | 105 | open func keepView(_ view: UIView, onPages pages: [CGFloat], withAttribute attribute: HorizontalPositionAttribute) { 106 | keepView(view, onPages: pages, atTimes: pages, withAttribute: attribute) 107 | } 108 | 109 | open func keepView(_ view: UIView, onPages pages: [CGFloat], atTimes times: [CGFloat]) { 110 | keepView(view, onPages: pages, atTimes: times, withAttribute: .centerX) 111 | } 112 | 113 | open func keepView(_ view: UIView, onPages pages: [CGFloat], atTimes times: [CGFloat], withAttribute attribute: HorizontalPositionAttribute) { 114 | assert(pages.count == times.count, "Make sure you set a time for each position.") 115 | view.translatesAutoresizingMaskIntoConstraints = false 116 | 117 | let xPositionConstraint = NSLayoutConstraint(item: view, attribute: layoutAttributeFromRazAttribute(attribute), relatedBy: .equal, toItem: contentView, attribute: .left, multiplier: 1, constant: 0) 118 | xPositionConstraint.isActive = true 119 | let xPositionAnimation = ScrollViewPageConstraintAnimation(superview: contentView, constraint: xPositionConstraint, pageWidth: pageWidth, attribute: attribute) 120 | for (index, page) in pages.enumerated() { 121 | xPositionAnimation[times[index]] = page 122 | } 123 | animator.addAnimation(xPositionAnimation) 124 | scrollViewPageConstraintAnimations.append(xPositionAnimation) 125 | } 126 | 127 | open func centerXMultiplierForPage(_ page: CGFloat) -> CGFloat { 128 | return multiplierForPage(page, attribute: .centerX) 129 | } 130 | 131 | open func leftMultiplierForPage(_ page: CGFloat) -> CGFloat { 132 | return multiplierForPage(page, attribute: .left) 133 | } 134 | 135 | open func rightMultiplierForPage(_ page: CGFloat) -> CGFloat { 136 | return multiplierForPage(page, attribute: .right) 137 | } 138 | 139 | open func multiplierForPage(_ page: CGFloat, attribute: HorizontalPositionAttribute) -> CGFloat { 140 | var offset : CGFloat 141 | switch attribute { 142 | case .centerX: 143 | offset = 0.5 144 | case .left: 145 | offset = 0 146 | case .right: 147 | offset = 1 148 | } 149 | return 2.0 * (offset + page) / CGFloat(numberOfPages()) 150 | } 151 | 152 | open func layoutAttributeFromRazAttribute(_ razAttribute: HorizontalPositionAttribute) -> NSLayoutAttribute { 153 | var attribute : NSLayoutAttribute 154 | switch razAttribute { 155 | case .centerX: 156 | attribute = .centerX 157 | case .left: 158 | attribute = .left 159 | case .right: 160 | attribute = .right 161 | } 162 | return attribute 163 | } 164 | } 165 | 166 | -------------------------------------------------------------------------------- /Source/Animation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Animation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/24/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class Animation where T.ValueType == T { 12 | private let filmstrip = Filmstrip() 13 | 14 | public init() {} 15 | 16 | open subscript(time: CGFloat) -> T { 17 | get { 18 | return filmstrip[time] 19 | } 20 | set { 21 | addKeyframe(time, value: newValue) 22 | } 23 | } 24 | 25 | open func addKeyframe(_ time: CGFloat, value: T) { 26 | if !checkValidity(value) {return} 27 | filmstrip[time] = value 28 | } 29 | 30 | open func addKeyframe(_ time: CGFloat, value: T, easing: @escaping EasingFunction) { 31 | if !checkValidity(value) {return} 32 | filmstrip.setValue(value, atTime: time, easing: easing) 33 | } 34 | 35 | open func hasKeyframes() -> Bool { 36 | return !filmstrip.isEmpty 37 | } 38 | 39 | open func validateValue(_ value: T) -> Bool { 40 | return true 41 | } 42 | 43 | private func checkValidity(_ value: T) -> Bool { 44 | let valid = validateValue(value) 45 | assert(valid, "The keyframe value is invalid for this type of animation.") 46 | return valid 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Source/Animator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Animator.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/11/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Keeps an array of all of the animations being controlled, and calls the `animate:` function on each. 13 | */ 14 | public class Animator { 15 | public var animations = [Animatable]() 16 | 17 | public init() { } 18 | 19 | public func animate(_ time: CGFloat) { 20 | for animation in animations { 21 | animation.animate(time) 22 | } 23 | } 24 | 25 | public func addAnimation(_ animation: Animatable) { 26 | animations.append(animation) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Source/BackgroundColorAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/13/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the `backgroundColor` property of a `UIView`. 13 | */ 14 | public class BackgroundColorAnimation : Animation, Animatable { 15 | private let view : UIView 16 | 17 | public init(view: UIView) { 18 | self.view = view 19 | } 20 | 21 | public func animate(_ time: CGFloat) { 22 | if !hasKeyframes() {return} 23 | view.backgroundColor = self[time] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Source/ConstraintConstantAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConstraintAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/15/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the `constant` of an `NSLayoutConstraint` and lays out the given `superview`. 13 | */ 14 | public class ConstraintConstantAnimation : Animation, Animatable { 15 | private let superview : UIView 16 | private let constraint : NSLayoutConstraint 17 | 18 | public init(superview: UIView, constraint: NSLayoutConstraint) { 19 | self.superview = superview 20 | self.constraint = constraint 21 | } 22 | 23 | public func animate(_ time: CGFloat) { 24 | if !hasKeyframes() {return} 25 | constraint.constant = self[time] 26 | superview.layoutIfNeeded() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Source/ConstraintMultiplierAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConstraintMultiplierAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/15/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum LayoutAttribute { 12 | case originX 13 | case originY 14 | case centerX 15 | case centerY 16 | case width 17 | case height 18 | } 19 | 20 | /** 21 | Animates the `constant` of an `NSLayoutConstraint` to a multiple of an attribute of another view, and lays out the given `superview`. 22 | */ 23 | public class ConstraintMultiplierAnimation : Animation, Animatable { 24 | private let superview : UIView 25 | private let constraint : NSLayoutConstraint 26 | private let referenceView : UIView 27 | private let attribute : LayoutAttribute 28 | private let constant : CGFloat 29 | 30 | public convenience init(superview: UIView, constraint: NSLayoutConstraint, attribute: LayoutAttribute, referenceView: UIView) { 31 | self.init(superview: superview, constraint: constraint, attribute: attribute, referenceView: referenceView, constant: 0) 32 | } 33 | 34 | public init(superview: UIView, constraint: NSLayoutConstraint, attribute: LayoutAttribute, referenceView: UIView, constant: CGFloat) { 35 | self.superview = superview 36 | self.constraint = constraint 37 | self.referenceView = referenceView 38 | self.attribute = attribute 39 | self.constant = constant 40 | } 41 | 42 | public func animate(_ time: CGFloat) { 43 | if !hasKeyframes() {return} 44 | let multiplier = self[time] 45 | var referenceAttributeValue : CGFloat 46 | switch attribute { 47 | case .originX: 48 | referenceAttributeValue = referenceView.frame.minX 49 | case .originY: 50 | referenceAttributeValue = referenceView.frame.minY 51 | case .centerX: 52 | referenceAttributeValue = referenceView.frame.minX + (referenceView.frame.width / 2.0) 53 | case .centerY: 54 | referenceAttributeValue = referenceView.frame.minY + (referenceView.frame.height / 2.0) 55 | case .width: 56 | referenceAttributeValue = referenceView.frame.width 57 | case .height: 58 | referenceAttributeValue = referenceView.frame.height 59 | } 60 | constraint.constant = (multiplier * referenceAttributeValue) + constant 61 | superview.layoutIfNeeded() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Source/CornerRadiusAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CornerRadiusAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/15/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the `cornerRadius` property of a `UIView's` `layer`. 13 | */ 14 | public class CornerRadiusAnimation : Animation, Animatable { 15 | private let view : UIView 16 | 17 | public init(view: UIView) { 18 | self.view = view 19 | } 20 | 21 | public func animate(_ time: CGFloat) { 22 | if !hasKeyframes() {return} 23 | view.layer.cornerRadius = self[time] 24 | } 25 | 26 | public override func validateValue(_ value: CGFloat) -> Bool { 27 | return (value >= 0) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/CubicBezier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CubicBezier.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/15/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | // Ported to Swift from Robert Böhnke's RBBAnimation, original available here: 10 | // 11 | 12 | import Foundation 13 | 14 | public func CubicBezier(_ x1: CGFloat, _ y1: CGFloat, _ x2: CGFloat, _ y2: CGFloat) -> EasingFunction { 15 | if (x1 == y1) && (x2 == y2) { return EasingFunctionLinear } 16 | return { x in 17 | let t = CubicBezierBinarySubdivide(x, x1: x1, x2: x2) 18 | return CubicBezierCalculate(t, a1: y1, a2: y2) 19 | } 20 | } 21 | 22 | private func A(_ a1: CGFloat, a2: CGFloat) -> CGFloat { 23 | return 1.0 - (3.0 * a2) + (3.0 * a1) 24 | } 25 | 26 | private func B(_ a1: CGFloat, a2: CGFloat) -> CGFloat { 27 | return (3.0 * a2) - (6.0 * a1) 28 | } 29 | 30 | private func C(_ a1: CGFloat) -> CGFloat { 31 | return (3.0 * a1) 32 | } 33 | 34 | private func CubicBezierCalculate(_ t: CGFloat, a1: CGFloat, a2: CGFloat) -> CGFloat { 35 | return ((((A(a1, a2: a2) * t) + B(a1, a2: a2)) * t) + C(a1)) * t 36 | } 37 | 38 | private func CubicBezierSlope(_ t: CGFloat, a1: CGFloat, a2: CGFloat) -> CGFloat { 39 | return (3.0 * A(a1, a2: a2) * t * t) + (2.0 * B(a1, a2: a2) * t) + C(a1) 40 | } 41 | 42 | private func CubicBezierBinarySubdivide(_ x: CGFloat, x1: CGFloat, x2: CGFloat) -> CGFloat { 43 | let epsilon : CGFloat = 0.0000001 44 | let maxIterations = 10 45 | 46 | var start : CGFloat = 0 47 | var end : CGFloat = 1 48 | 49 | var currentX : CGFloat 50 | var currentT : CGFloat 51 | 52 | var i = 0 53 | repeat { 54 | currentT = start + (end - start) / 2.0 55 | currentX = CubicBezierCalculate(currentT, a1: x1, a2: x2) - x 56 | 57 | if (currentX > 0) { 58 | end = currentT 59 | } else { 60 | start = currentT 61 | } 62 | i += 1 63 | } while (fabs(currentX) > epsilon && i < maxIterations) 64 | 65 | return currentT 66 | } 67 | -------------------------------------------------------------------------------- /Source/EasingFunction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasingFunction.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/15/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | // Ported to Swift from Robert Böhnke's RBBAnimation, original available here: 10 | // 11 | 12 | import Foundation 13 | 14 | public typealias EasingFunction = (CGFloat) -> (CGFloat) 15 | 16 | public let EasingFunctionLinear: EasingFunction = { t in 17 | return t 18 | } 19 | 20 | public let EasingFunctionEaseInQuad: EasingFunction = { t in 21 | return t * t 22 | } 23 | 24 | public let EasingFunctionEaseOutQuad: EasingFunction = { t in 25 | return t * (2 - t) 26 | } 27 | 28 | public let EasingFunctionEaseInOutQuad: EasingFunction = { t in 29 | if (t < 0.5) { return 2 * t * t } 30 | return -1 + ((4 - (2 * t)) * t) 31 | } 32 | 33 | public let EasingFunctionEaseInCubic: EasingFunction = { t in 34 | return t * t * t 35 | } 36 | 37 | public let EasingFunctionEaseOutCubic: EasingFunction = { t in 38 | return pow(t - 1, 3) + 1 39 | } 40 | 41 | public let EasingFunctionEaseInOutCubic: EasingFunction = { t in 42 | if (t < 0.5) { return 4 * pow(t, 3) } 43 | return ((t - 1) * pow((2 * t) - 2, 2)) + 1 44 | } 45 | 46 | public let EasingFunctionEaseInBounce: EasingFunction = { t in 47 | return 1 - EasingFunctionEaseOutBounce(1 - t) 48 | } 49 | 50 | public let EasingFunctionEaseOutBounce: EasingFunction = { t in 51 | if (t < (4.0 / 11.0)) { 52 | return pow((11.0 / 4.0), 2) * pow(t, 2) 53 | } 54 | 55 | if (t < (8.0 / 11.0)) { 56 | return (3.0 / 4.0) + (pow((11.0 / 4.0), 2) * pow(t - (6.0 / 11.0), 2)) 57 | } 58 | 59 | if (t < (10.0 / 11.0)) { 60 | return (15.0 / 16.0) + (pow((11.0 / 4.0), 2) * pow(t - (9.0 / 11.0), 2)) 61 | } 62 | 63 | return (63.0 / 64.0) + (pow((11.0 / 4.0), 2) * pow(t - (21.0 / 22.0), 2)) 64 | } 65 | -------------------------------------------------------------------------------- /Source/Filmstrip.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Filmstrip.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/14/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Keyframe where T.ValueType == T { 12 | let time : CGFloat 13 | let value : T 14 | let easing : EasingFunction 15 | 16 | public convenience init(time: CGFloat, value: T) { 17 | self.init(time: time, value: value, easing: EasingFunctionLinear) 18 | } 19 | 20 | public init(time: CGFloat, value: T, easing: @escaping EasingFunction) { 21 | self.time = time 22 | self.value = value 23 | self.easing = easing 24 | } 25 | } 26 | 27 | /** 28 | Keeps track of the keyframes set, and lazily generates interpolated values between them for the requested time as needed. 29 | */ 30 | public class Filmstrip where T.ValueType == T { 31 | 32 | var keyframes = [Keyframe]() 33 | 34 | public init() { } 35 | 36 | public var isEmpty: Bool { 37 | get { 38 | return keyframes.isEmpty 39 | } 40 | } 41 | 42 | public subscript(time: CGFloat) -> T { 43 | get { 44 | return valueAtTime(time) 45 | } 46 | set { 47 | setValue(newValue, atTime: time) 48 | } 49 | } 50 | 51 | public func setValue(_ value: T, atTime time: CGFloat) { 52 | let index = indexOfKeyframeAfterTime(time) ?? keyframes.count 53 | keyframes.insert(Keyframe(time: time, value: value), at: index) 54 | } 55 | 56 | public func setValue(_ value: T, atTime time: CGFloat, easing: @escaping EasingFunction) { 57 | let index = indexOfKeyframeAfterTime(time) ?? keyframes.count 58 | keyframes.insert(Keyframe(time: time, value: value, easing: easing), at: index) 59 | } 60 | 61 | public func valueAtTime(_ time: CGFloat) -> T { 62 | assert(!self.isEmpty, "At least one KeyFrame must be set before animation begins.") 63 | var value : T 64 | let indexAfter = (indexOfKeyframeAfterTime(time) ?? keyframes.count) 65 | switch indexAfter { 66 | case 0: 67 | value = keyframes[0].value 68 | case 1.. Int? { 80 | var indexAfter : Int? 81 | for (index, keyframe) in keyframes.enumerated() { 82 | if time < keyframe.time { 83 | indexAfter = index 84 | break 85 | } 86 | } 87 | return indexAfter 88 | } 89 | 90 | private func progressFromTime(_ fromTime: CGFloat, toTime: CGFloat, atTime: CGFloat, easing: EasingFunction) -> CGFloat { 91 | let duration = toTime - fromTime 92 | if duration == 0 {return 0} 93 | let timeElapsed = atTime - fromTime 94 | return easing(timeElapsed / duration) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Source/HideAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HideAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/27/15. 6 | // Copyright © 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the `hidden` property of a `UIView`. 13 | */ 14 | public class HideAnimation : Animatable { 15 | private let filmstrip = Filmstrip() 16 | private let view : UIView 17 | 18 | public init(view: UIView, hideAt: CGFloat) { 19 | self.view = view 20 | filmstrip[hideAt] = false 21 | filmstrip[hideAt + 0.00001] = true 22 | } 23 | 24 | public init(view: UIView, showAt: CGFloat) { 25 | self.view = view 26 | filmstrip[showAt] = true 27 | filmstrip[showAt + 0.00001] = false 28 | } 29 | 30 | public func animate(_ time: CGFloat) { 31 | if filmstrip.isEmpty {return} 32 | view.isHidden = filmstrip[time] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Source/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 | 0.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/Interpolatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Interpolatable.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/14/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Protocol for types that can return smoothly incremented values between two given values. 13 | */ 14 | public protocol Interpolatable { 15 | associatedtype ValueType 16 | /** 17 | Find a value at a certain progress point (from 0 to 1) between two values of the same type. 18 | 19 | - parameter toValue: The ending value 20 | - parameter progress: The progress (from 0 to 1) from the starting value to the end value for this value 21 | 22 | - returns: The value at the given progress point between the given starting and ending values 23 | */ 24 | static func interpolateFrom(_ fromValue: ValueType, to toValue: ValueType, withProgress progress: CGFloat) -> ValueType 25 | } 26 | 27 | extension CGFloat : Interpolatable { 28 | /** 29 | Find a CGFloat at a certain progress point (from 0 to 1) between two other CGFloats. 30 | 31 | - parameter fromValue: The starting CGFloat 32 | - parameter toValue: The ending CGFloat 33 | - parameter progress: The progress (from 0 to 1) from the starting CGFloat to the end CGFloat for this CGFloat 34 | 35 | - returns: The CGFloat at the given progress point between the given starting and ending CGFloats 36 | */ 37 | public static func interpolateFrom(_ fromValue: CGFloat, to toValue: CGFloat, withProgress progress: CGFloat) -> CGFloat { 38 | assert((0 <= progress) && (progress <= 1), "Progress must be between 0 and 1") 39 | let totalChange = toValue - fromValue 40 | let currentChange = totalChange * progress 41 | return fromValue + currentChange 42 | } 43 | } 44 | 45 | extension CGPoint : Interpolatable { 46 | /** 47 | Find a CGPoint at a certain progress point (from 0 to 1) between two other CGPoints. 48 | 49 | - parameter fromValue: The starting CGPoint 50 | - parameter toValue: The ending CGPoint 51 | - parameter progress: The progress (from 0 to 1) from the starting CGPoint to the end CGPoint for this CGPoint 52 | 53 | - returns: The CGPoint at the given progress point between the given starting and ending CGPoints 54 | */ 55 | public static func interpolateFrom(_ fromValue: CGPoint, to toValue: CGPoint, withProgress progress: CGFloat) -> CGPoint { 56 | assert((0 <= progress) && (progress <= 1), "Progress must be between 0 and 1") 57 | let interpolatedX = CGFloat.interpolateFrom(fromValue.x, to: toValue.x, withProgress: progress) 58 | let interpolatedY = CGFloat.interpolateFrom(fromValue.y, to: toValue.y, withProgress: progress) 59 | return CGPoint(x: interpolatedX, y: interpolatedY) 60 | } 61 | } 62 | 63 | extension CGSize : Interpolatable { 64 | /** 65 | Find a CGSize at a certain progress point (from 0 to 1) between two other CGSizes. 66 | 67 | - parameter fromValue: The starting CGSize 68 | - parameter toValue: The ending CGSize 69 | - parameter progress: The progress (from 0 to 1) from the starting CGSize to the end CGSize for this CGSize 70 | 71 | - returns: The CGSize at the given progress point between the given starting and ending CGSizes 72 | */ 73 | public static func interpolateFrom(_ fromValue: CGSize, to toValue: CGSize, withProgress progress: CGFloat) -> CGSize { 74 | assert((0 <= progress) && (progress <= 1), "Progress must be between 0 and 1") 75 | let interpolatedWidth = CGFloat.interpolateFrom(fromValue.width, to: toValue.width, withProgress: progress) 76 | let interpolatedHeight = CGFloat.interpolateFrom(fromValue.height, to: toValue.height, withProgress: progress) 77 | return CGSize(width: interpolatedWidth, height: interpolatedHeight) 78 | } 79 | } 80 | 81 | extension CGRect : Interpolatable { 82 | /** 83 | Find a CGRect at a certain progress point (from 0 to 1) between two other CGRects. 84 | 85 | - parameter fromValue: The starting CGRect 86 | - parameter toValue: The ending CGRect 87 | - parameter progress: The progress (from 0 to 1) from the starting CGRect to the end CGRect for this CGRect 88 | 89 | - returns: The CGRect at the given progress point between the given starting and ending CGRects 90 | */ 91 | public static func interpolateFrom(_ fromValue: CGRect, to toValue: CGRect, withProgress progress: CGFloat) -> CGRect { 92 | assert((0 <= progress) && (progress <= 1), "Progress must be between 0 and 1") 93 | let interpolatedOrigin = CGPoint.interpolateFrom(fromValue.origin, to: toValue.origin, withProgress: progress) 94 | let interpolatedSize = CGSize.interpolateFrom(fromValue.size, to: toValue.size, withProgress: progress) 95 | return CGRect(x: interpolatedOrigin.x, y: interpolatedOrigin.y, width: interpolatedSize.width, height: interpolatedSize.height) 96 | } 97 | } 98 | 99 | extension UIColor : Interpolatable { 100 | /** 101 | Find a UIColor at a certain progress point (from 0 to 1) between two other UIColors. 102 | 103 | - parameter fromValue: The starting UIColor 104 | - parameter toValue: The ending UIColor 105 | - parameter progress: The progress (from 0 to 1) from the starting UIColor to the end UIColor for this UIColor 106 | 107 | - returns: The UIColor at the given progress point between the given starting and ending UIColors 108 | */ 109 | public static func interpolateFrom(_ fromValue: UIColor, to toValue: UIColor, withProgress progress: CGFloat) -> UIColor { 110 | assert((0 <= progress) && (progress <= 1), "Progress must be between 0 and 1") 111 | var fromRed : CGFloat = 0 112 | var fromGreen : CGFloat = 0 113 | var fromBlue : CGFloat = 0 114 | var fromAlpha : CGFloat = 0 115 | 116 | var toRed : CGFloat = 0 117 | var toGreen : CGFloat = 0 118 | var toBlue : CGFloat = 0 119 | var toAlpha : CGFloat = 0 120 | 121 | let hasStartColor = razGetRed(&fromRed, green: &fromGreen, blue: &fromBlue, alpha: &fromAlpha, fromColor: fromValue) 122 | let hasEndColor = razGetRed(&toRed, green: &toGreen, blue: &toBlue, alpha: &toAlpha, fromColor: toValue) 123 | 124 | if hasStartColor && hasEndColor { 125 | let red = CGFloat.interpolateFrom(fromRed, to: toRed, withProgress: progress) 126 | let green = CGFloat.interpolateFrom(fromGreen, to: toGreen, withProgress: progress) 127 | let blue = CGFloat.interpolateFrom(fromBlue, to: toBlue, withProgress: progress) 128 | let alpha = CGFloat.interpolateFrom(fromAlpha, to: toAlpha, withProgress: progress) 129 | return UIColor(red: red, green: green, blue: blue, alpha: alpha) 130 | } 131 | return fromValue 132 | } 133 | 134 | private static func razGetRed(_ red: UnsafeMutablePointer, green: UnsafeMutablePointer, blue: UnsafeMutablePointer, alpha: UnsafeMutablePointer, fromColor color: UIColor) -> Bool { 135 | if color.getRed(red, green: green, blue: blue, alpha: alpha) {return true} 136 | var white : CGFloat = 0.0 137 | if color.getWhite(&white, alpha: alpha) { 138 | red.pointee = white 139 | green.pointee = white 140 | blue.pointee = white 141 | return true 142 | } 143 | return false 144 | } 145 | } 146 | 147 | extension Bool : Interpolatable { 148 | public static func interpolateFrom(_ fromValue: Bool, to toValue: Bool, withProgress progress: CGFloat) -> Bool { 149 | assert((0 <= progress) && (progress <= 1), "Progress must be between 0 and 1") 150 | if progress == 0 { 151 | return fromValue 152 | } 153 | return toValue 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Source/LabelTextColorAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LabelTextColorAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/24/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the `textColor` property of a `UILabel`. 13 | */ 14 | public class LabelTextColorAnimation: Animation, Animatable { 15 | private let label : UILabel 16 | 17 | public init(label : UILabel) { 18 | self.label = label 19 | } 20 | 21 | public func animate(_ time: CGFloat) { 22 | if !hasKeyframes() {return} 23 | label.textColor = self[time] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Source/LayerFillColorAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayerFillColorAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/24/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the `fillColor` property of a `CAShapeLayer`. 13 | */ 14 | public class LayerFillColorAnimation : Animation, Animatable { 15 | private let layer : CAShapeLayer 16 | 17 | public init(layer: CAShapeLayer) { 18 | self.layer = layer 19 | } 20 | 21 | public func animate(_ time: CGFloat) { 22 | if !hasKeyframes() {return} 23 | layer.fillColor = self[time].cgColor 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Source/LayerStrokeColorAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayerStrokeColorAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/24/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the `strokeColor` property of a `CAShapeLayer`. 13 | */ 14 | public class LayerStrokeColorAnimation : Animation, Animatable { 15 | private let layer : CAShapeLayer 16 | 17 | public init(layer: CAShapeLayer) { 18 | self.layer = layer 19 | } 20 | 21 | public func animate(_ time: CGFloat) { 22 | if !hasKeyframes() {return} 23 | layer.strokeColor = self[time].cgColor 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Source/LayerStrokeEndAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayerStrokeEndAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/24/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the `strokeEnd` property of a `CAShapeLayer`. 13 | */ 14 | public class LayerStrokeEndAnimation : Animation, Animatable { 15 | private let layer : CAShapeLayer 16 | private let animationKey = "StrokeEnd" 17 | 18 | public init(layer: CAShapeLayer) { 19 | self.layer = layer 20 | self.layer.speed = 0 21 | super.init() 22 | createStrokeEndAnimation() 23 | 24 | // CAAnimations are lost when application enters the background, so re-add them 25 | NotificationCenter.default.addObserver( 26 | self, 27 | selector: #selector(LayerStrokeEndAnimation.createStrokeEndAnimation), 28 | name: NSNotification.Name.UIApplicationDidBecomeActive, 29 | object: nil) 30 | } 31 | 32 | deinit { 33 | NotificationCenter.default.removeObserver(self) 34 | } 35 | 36 | public func animate(_ time: CGFloat) { 37 | if !hasKeyframes() {return} 38 | layer.timeOffset = CFTimeInterval(self[time]) 39 | } 40 | 41 | public override func validateValue(_ value: CGFloat) -> Bool { 42 | return (value >= 0) && (value <= 1) 43 | } 44 | 45 | @objc private func createStrokeEndAnimation() { 46 | // Set up a CABasicAnimation to change the stroke end 47 | layer.add(strokeEndAnimation(), forKey: animationKey) 48 | layer.speed = 0 49 | layer.timeOffset = 0 50 | } 51 | 52 | private func strokeEndAnimation() -> CABasicAnimation { 53 | let animation = CABasicAnimation(keyPath: "strokeEnd") 54 | animation.duration = 1 55 | animation.fromValue = 0 56 | animation.toValue = 1 57 | animation.fillMode = kCAFillModeBoth 58 | animation.isRemovedOnCompletion = false 59 | return animation 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Source/LayerStrokeStartAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayerStrokeStartAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/24/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the `strokeStart` property of a `CAShapeLayer`. 13 | */ 14 | public class LayerStrokeStartAnimation : Animation, Animatable { 15 | private let layer : CAShapeLayer 16 | private let animationKey = "StrokeStart" 17 | 18 | public init(layer: CAShapeLayer) { 19 | self.layer = layer 20 | super.init() 21 | createStrokeStartAnimation() 22 | 23 | // CAAnimations are lost when application enters the background, so re-add them 24 | NotificationCenter.default.addObserver( 25 | self, 26 | selector: #selector(LayerStrokeStartAnimation.createStrokeStartAnimation), 27 | name: NSNotification.Name.UIApplicationDidBecomeActive, 28 | object: nil) 29 | } 30 | 31 | deinit { 32 | NotificationCenter.default.removeObserver(self) 33 | } 34 | 35 | public func animate(_ time: CGFloat) { 36 | if !hasKeyframes() {return} 37 | layer.timeOffset = CFTimeInterval(self[time]) 38 | } 39 | 40 | public override func validateValue(_ value: CGFloat) -> Bool { 41 | return (value >= 0) && (value <= 1) 42 | } 43 | 44 | @objc private func createStrokeStartAnimation() { 45 | // Set up a CABasicAnimation to change the stroke start 46 | layer.add(strokeStartAnimation(), forKey: animationKey) 47 | layer.speed = 0 48 | layer.timeOffset = 0 49 | } 50 | 51 | private func strokeStartAnimation() -> CABasicAnimation { 52 | let animation = CABasicAnimation(keyPath: "strokeStart") 53 | animation.duration = 1 54 | animation.fromValue = 0 55 | animation.toValue = 1 56 | animation.fillMode = kCAFillModeBoth 57 | animation.isRemovedOnCompletion = false 58 | return animation 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Source/PathPositionAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PathPositionAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/30/15. 6 | // Copyright © 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the view's position along the given path. 13 | */ 14 | public class PathPositionAnimation : Animation, Animatable { 15 | private let view : UIView 16 | public var path : CGPath? { 17 | didSet { 18 | createKeyframeAnimation() 19 | } 20 | } 21 | private let animationKey = "PathPosition" 22 | public var rotationMode : String? = kCAAnimationRotateAuto { 23 | didSet { 24 | createKeyframeAnimation() 25 | } 26 | } 27 | 28 | public init(view: UIView, path: CGPath?) { 29 | self.view = view 30 | self.path = path 31 | super.init() 32 | createKeyframeAnimation() 33 | 34 | // CAAnimations are lost when application enters the background, so re-add them 35 | NotificationCenter.default.addObserver( 36 | self, 37 | selector: #selector(PathPositionAnimation.createKeyframeAnimation), 38 | name: NSNotification.Name.UIApplicationDidBecomeActive, 39 | object: nil) 40 | } 41 | 42 | deinit { 43 | NotificationCenter.default.removeObserver(self) 44 | } 45 | 46 | public func animate(_ time: CGFloat) { 47 | if !hasKeyframes() {return} 48 | view.layer.timeOffset = CFTimeInterval(self[time]) 49 | } 50 | 51 | public override func validateValue(_ value: CGFloat) -> Bool { 52 | return (value >= 0) && (value <= 1) 53 | } 54 | 55 | @objc private func createKeyframeAnimation() { 56 | // Set up a CAKeyframeAnimation to move the view along the path 57 | view.layer.add(pathAnimation(), forKey: animationKey) 58 | view.layer.speed = 0 59 | view.layer.timeOffset = 0 60 | } 61 | 62 | private func pathAnimation() -> CAKeyframeAnimation { 63 | let animation = CAKeyframeAnimation() 64 | animation.keyPath = "position" 65 | animation.path = path 66 | animation.duration = 1 67 | animation.isAdditive = true 68 | animation.repeatCount = Float.infinity 69 | animation.calculationMode = kCAAnimationPaced 70 | animation.rotationMode = rotationMode 71 | animation.fillMode = kCAFillModeBoth 72 | animation.isRemovedOnCompletion = false 73 | return animation 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Source/RazzleDazzle.h: -------------------------------------------------------------------------------- 1 | // 2 | // RazzleDazzle.h 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/11/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for RazzleDazzle. 12 | FOUNDATION_EXPORT double RazzleDazzleVersionNumber; 13 | 14 | //! Project version string for RazzleDazzle. 15 | FOUNDATION_EXPORT const unsigned char RazzleDazzleVersionString[]; 16 | -------------------------------------------------------------------------------- /Source/RotationAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RotationAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/13/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the rotation of the `transform` of a `UIView`. 13 | */ 14 | public class RotationAnimation : Animation, Animatable { 15 | private let view : UIView 16 | 17 | public init(view: UIView) { 18 | self.view = view 19 | } 20 | 21 | public func animate(_ time: CGFloat) { 22 | if !hasKeyframes() {return} 23 | let degrees = self[time] 24 | let radians = degrees * CGFloat(M_PI / -180.0) 25 | let rotationTransform = CGAffineTransform(rotationAngle: radians) 26 | view.rotationTransform = rotationTransform 27 | var newTransform = rotationTransform 28 | if let scaleTransform = view.scaleTransform { 29 | newTransform = newTransform.concatenating(scaleTransform) 30 | } 31 | if let translationTransform = view.translationTransform { 32 | newTransform = newTransform.concatenating(translationTransform) 33 | } 34 | view.transform = newTransform 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Source/ScaleAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScaleAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/13/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the scale of the `transform` of a `UIView`. 13 | */ 14 | public class ScaleAnimation : Animation, Animatable { 15 | private let view : UIView 16 | 17 | public init(view: UIView) { 18 | self.view = view 19 | } 20 | 21 | public func animate(_ time: CGFloat) { 22 | if !hasKeyframes() {return} 23 | let scale = self[time] 24 | let scaleTransform = CGAffineTransform(scaleX: scale, y: scale) 25 | view.scaleTransform = scaleTransform 26 | var newTransform = scaleTransform 27 | if let rotationTransform = view.rotationTransform { 28 | newTransform = newTransform.concatenating(rotationTransform) 29 | } 30 | if let translationTransform = view.translationTransform { 31 | newTransform = newTransform.concatenating(translationTransform) 32 | } 33 | view.transform = newTransform 34 | } 35 | 36 | public override func validateValue(_ value: CGFloat) -> Bool { 37 | return (value >= 0) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Source/ScrollViewPageConstraintAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewPageConstraintAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/16/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum HorizontalPositionAttribute { 12 | case left 13 | case right 14 | case centerX 15 | } 16 | 17 | /** 18 | Animates the `constant` of an `NSLayoutConstraint` to keep the view on a certain page given the `pageWidth` of the scroll view. 19 | */ 20 | public class ScrollViewPageConstraintAnimation : Animation, Animatable { 21 | private let superview : UIView 22 | private let constraint : NSLayoutConstraint 23 | private let attribute : HorizontalPositionAttribute 24 | public var pageWidth : CGFloat 25 | 26 | public init(superview: UIView, constraint: NSLayoutConstraint, pageWidth: CGFloat, attribute: HorizontalPositionAttribute) { 27 | self.superview = superview 28 | self.constraint = constraint 29 | self.attribute = attribute 30 | self.pageWidth = pageWidth 31 | } 32 | 33 | public func animate(_ time: CGFloat) { 34 | if !hasKeyframes() {return} 35 | let page = self[time] 36 | 37 | var offset : CGFloat 38 | switch attribute { 39 | case .centerX: 40 | offset = 0.5 41 | case .left: 42 | offset = 0 43 | case .right: 44 | offset = 1 45 | } 46 | 47 | constraint.constant = (offset + page) * pageWidth 48 | superview.layoutIfNeeded() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Source/TranslationAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TranslationAnimation.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/14/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Animates the translation of the `transform` of a `UIView`. 13 | */ 14 | public class TranslationAnimation : Animation, Animatable { 15 | private let view : UIView 16 | 17 | public init(view: UIView) { 18 | self.view = view 19 | } 20 | 21 | public func animate(_ time: CGFloat) { 22 | if !hasKeyframes() {return} 23 | let translation = self[time] 24 | let translationTransform = CGAffineTransform(translationX: translation.x, y: translation.y) 25 | view.translationTransform = translationTransform 26 | var newTransform = translationTransform 27 | if let scaleTransform = view.scaleTransform { 28 | newTransform = newTransform.concatenating(scaleTransform) 29 | } 30 | if let rotationTransform = view.rotationTransform { 31 | newTransform = newTransform.concatenating(rotationTransform) 32 | } 33 | view.transform = newTransform 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/UIView+Transform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Transform.swift 3 | // RazzleDazzle 4 | // 5 | // Created by Laura Skelton on 6/13/15. 6 | // Copyright (c) 2015 IFTTT. All rights reserved. 7 | // 8 | 9 | import ObjectiveC 10 | 11 | internal extension UIView { 12 | 13 | private struct RotationTransformAssociatedKey { 14 | static var viewExtension = "ViewRotationExtension" 15 | } 16 | 17 | private struct ScaleTransformAssociatedKey { 18 | static var viewExtension = "ViewScaleExtension" 19 | } 20 | 21 | private struct TranslationTransformAssociatedKey { 22 | static var viewExtension = "ViewTranslationExtension" 23 | } 24 | 25 | internal var rotationTransform: CGAffineTransform? { 26 | get { 27 | return getAssociatedObject(self, associativeKey: &RotationTransformAssociatedKey.viewExtension) 28 | } 29 | 30 | set { 31 | if let value = newValue { 32 | setAssociatedObject(self, value: value, associativeKey: &RotationTransformAssociatedKey.viewExtension, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 33 | } 34 | } 35 | } 36 | 37 | internal var scaleTransform: CGAffineTransform? { 38 | get { 39 | return getAssociatedObject(self, associativeKey: &ScaleTransformAssociatedKey.viewExtension) 40 | } 41 | 42 | set { 43 | if let value = newValue { 44 | setAssociatedObject(self, value: value, associativeKey: &ScaleTransformAssociatedKey.viewExtension, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 45 | } 46 | } 47 | } 48 | 49 | internal var translationTransform: CGAffineTransform? { 50 | get { 51 | return getAssociatedObject(self, associativeKey: &TranslationTransformAssociatedKey.viewExtension) 52 | } 53 | 54 | set { 55 | if let value = newValue { 56 | setAssociatedObject(self, value: value, associativeKey: &TranslationTransformAssociatedKey.viewExtension, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 57 | } 58 | } 59 | } 60 | } 61 | 62 | private final class Lifted { 63 | let value: T 64 | init(_ x: T) { 65 | value = x 66 | } 67 | } 68 | 69 | private func lift(_ x: T) -> Lifted { 70 | return Lifted(x) 71 | } 72 | 73 | private func setAssociatedObject(_ object: AnyObject, value: T, associativeKey: UnsafeRawPointer, policy: objc_AssociationPolicy) { 74 | objc_setAssociatedObject(object, associativeKey, value, policy) 75 | } 76 | 77 | private func getAssociatedObject(_ object: AnyObject, associativeKey: UnsafeRawPointer) -> T? { 78 | if let v = objc_getAssociatedObject(object, associativeKey) as? T { 79 | return v 80 | } 81 | else if let v = objc_getAssociatedObject(object, associativeKey) as? Lifted { 82 | return v.value 83 | } 84 | else { 85 | return nil 86 | } 87 | } 88 | --------------------------------------------------------------------------------