├── .gitignore ├── .swift-version ├── .travis.yml ├── LICENSE ├── README.md ├── TweenController.podspec ├── TweenController.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── TweenController.xcscheme └── xcuserdata │ └── daltonclaybrook.xcuserdatad │ └── xcschemes │ ├── TweenControllerDemo.xcscheme │ └── xcschememanagement.plist ├── TweenController ├── Boundary.swift ├── Easing.swift ├── Extensions.swift ├── Info.plist ├── KVC.swift ├── StandardTweenables.swift ├── TweenController.h ├── TweenController.swift ├── TweenDescriptor.swift ├── TweenPromise.swift └── Tweenable.swift ├── TweenControllerDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Tutorial │ │ ├── Screen2 │ │ ├── bottom_copy_s2.imageset │ │ │ ├── Contents.json │ │ │ └── addPhotoVideoRecipients_6Plus.pdf │ │ ├── card_copy_s2.imageset │ │ │ ├── Contents.json │ │ │ └── YADAbars_6Plus.pdf │ │ ├── chick_face.imageset │ │ │ ├── Contents.json │ │ │ └── girlHead_6Plus.pdf │ │ ├── sunrise.imageset │ │ │ ├── Contents.json │ │ │ └── Sun_6Plus.pdf │ │ └── top_copy_s2.imageset │ │ │ ├── Contents.json │ │ │ └── PinYadaIsLikeOther_6Plus.pdf │ │ ├── Screen3 │ │ ├── birthday_cake.imageset │ │ │ ├── Cake_6Plus.pdf │ │ │ └── Contents.json │ │ ├── bottom_copy_s3.imageset │ │ │ ├── Contents.json │ │ │ └── pinMessageToTime_6Plus.pdf │ │ ├── card_copy_s3.imageset │ │ │ ├── Contents.json │ │ │ └── messageSusan_6Plus.pdf │ │ ├── hill.imageset │ │ │ ├── Contents.json │ │ │ └── tinyHill_6Plus.pdf │ │ ├── hill_mark.imageset │ │ │ ├── Contents.json │ │ │ └── smallMark_6Plus.pdf │ │ ├── stars.imageset │ │ │ ├── Contents.json │ │ │ └── stars_6Plus.pdf │ │ └── top_copy_s3.imageset │ │ │ ├── Contents.json │ │ │ └── exceptyoucan_6plus.pdf │ │ ├── Screen4 │ │ ├── bottom_copy_s4.imageset │ │ │ ├── Contents.json │ │ │ └── pinMessageToPlace_6Plus.pdf │ │ ├── card_copy_s4.imageset │ │ │ ├── Contents.json │ │ │ └── messageJohn_6Plus.pdf │ │ ├── dude_face.imageset │ │ │ ├── Contents.json │ │ │ └── boyHead_6Plus.pdf │ │ ├── eiffel.imageset │ │ │ ├── Contents.json │ │ │ └── EiffelTower_6Plus-1.pdf │ │ ├── eiffel_tower.imageset │ │ │ ├── Contents.json │ │ │ └── eiffelTowerBckrnd_6Plus.pdf │ │ └── top_copy_s4.imageset │ │ │ ├── Contents.json │ │ │ └── orYouCan_6Plus.pdf │ │ ├── Screen5 │ │ ├── bottom_copy_s5.imageset │ │ │ ├── Contents.json │ │ │ └── yourMessageIsReceived_6Plus.pdf │ │ ├── card_detail.imageset │ │ │ ├── Contents.json │ │ │ └── fullCardCenterPoint_6Plus.pdf │ │ ├── chick_arm_down.imageset │ │ │ ├── Contents.json │ │ │ └── girlArmDown_6Plus.pdf │ │ ├── chick_arm_up.imageset │ │ │ ├── Contents.json │ │ │ └── girlArmUp_6Plus.pdf │ │ └── top_copy_s5.imageset │ │ │ ├── Contents.json │ │ │ └── soThat_6Plus.pdf │ │ ├── left_arrow.imageset │ │ ├── Contents.json │ │ └── swipeArrowNText_6Plus.pdf │ │ ├── logo_tagline.imageset │ │ ├── Contents.json │ │ └── LogoTag_6Plus.pdf │ │ ├── mark.imageset │ │ ├── BigMark_6Plus.pdf │ │ └── Contents.json │ │ ├── mark_shadow.imageset │ │ ├── Contents.json │ │ └── shadow_6Plus.pdf │ │ └── message_bubble.imageset │ │ ├── Contents.json │ │ └── messageBubble_6Plus.pdf ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── StartViewController.swift ├── TutorialBuilder.swift └── ViewController.swift ├── TweenControllerTests ├── EasingTests.swift ├── ExtensionsTests.swift ├── Info.plist ├── KeyPathTests.swift ├── KeypathListener.swift ├── StandardTweenablesTests.swift └── TweenControllerTests.swift └── example.gif /.gitignore: -------------------------------------------------------------------------------- 1 | TweenController.xcodeproj/project.xcworkspace/xcuserdata 2 | TweenController.xcodeproj/xcuserdata 3 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9.2 3 | script: xcodebuild -project TweenController.xcodeproj -scheme TweenController -sdk iphonesimulator -destination 'platform=iOS Simulator,id=48099C45-0EB1-4FDF-8C6A-360266EB796B,OS=11.2' test 4 | after_success: 5 | - bash <(curl -s https://codecov.io/bash) -J '^TweenController$' 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dalton Claybrook 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TweenController [![travisci](https://travis-ci.org/daltonclaybrook/tween-controller.svg?branch=master)](https://travis-ci.org/daltonclaybrook/tween-controller) [![codecov](https://codecov.io/gh/daltonclaybrook/tween-controller/branch/master/graph/badge.svg)](https://codecov.io/gh/daltonclaybrook/tween-controller) [![Swift v4.1](https://img.shields.io/badge/Swift-v4.1-orange.svg)](https://swift.org) ![License MIT](https://img.shields.io/badge/license-MIT-lightgrey.svg) [![CocoaPods](https://img.shields.io/badge/pod-v1.0.1-blue.svg)](https://cocoapods.org) [![Carthage](https://img.shields.io/badge/Carthage-compatible-green.svg)](https://github.com/Carthage/Carthage) 2 | 3 | On the surface, TweenController makes it easy to build interactive menus and tutorials. Under the hood, it's a simple but powerful toolkit to interpolate between values that are *Tweenable*. 4 | 5 | ## Example 6 | ![Pinyada](https://raw.githubusercontent.com/daltonclaybrook/tween-controller/master/example.gif) 7 | 8 | ## Usage 9 | 10 | Tween anything that is *Tweenable*, such as **CGAffineTransform**: 11 | 12 | ``` swift 13 | func tweenTransform() { 14 | let transformA = CGAffineTransform.identity 15 | let transformB = CGAffineTransform(scaleX: 2.0, y: 2.0) 16 | let transformC = CGAffineTransform(rotationAngle: CGFloat(M_PI_2)) 17 | 18 | controller.tween(from: transformA, at: 0.0) 19 | .to(transformB, at: 0.5) 20 | .then(to: transformC, at: 1.0) 21 | .with(action: tweenView.layer.twc_applyAffineTransform) 22 | } 23 | ``` 24 | or **UIColor**: 25 | 26 | ``` swift 27 | func tweenColor() { 28 | let colorA = UIColor.green 29 | let colorB = UIColor.blue 30 | let colorC = UIColor.red 31 | tweenView.backgroundColor = colorA 32 | 33 | controller.tween(from: colorA, at: 0.0) 34 | .to(colorB, at: 0.5) 35 | .then(to: colorC, at: 1.0) 36 | .with(action: tweenView.twc_applyBackgroundColor) 37 | } 38 | ``` 39 | or your own custom type: 40 | 41 | ``` swift 42 | enum Step: Int { 43 | case One = 1, Two = 2, Three = 3, Four = 4 44 | } 45 | 46 | extension Step: Tweenable { 47 | static func valueBetween(_ val1: Step, _ val2: Step, percent: Double) -> Step { 48 | let val = Int(round(Double(val2.rawValue - val1.rawValue) * percent + Double(val1.rawValue))) 49 | return Step(rawValue: max(min(val, 4), 1)) ?? .One 50 | } 51 | } 52 | 53 | func tweenStep() { 54 | tweenController.tween(from: Step.One, at: 0.0) 55 | .to(.Four, at: 0.25) 56 | .thenHold(until: 0.75) 57 | .then(to: .Two, at: 1.0) 58 | .with { step in 59 | // use step 60 | } 61 | } 62 | ``` 63 | 64 | then simply call: 65 | 66 | ``` swift 67 | // can be called in response to user interaction 68 | // such as a gesture recognizer or scroll view 69 | // or something else, like a CADisplayLink 70 | 71 | // progress range is arbitrary 72 | // you might choose to use a percentage, like 0.0 - 1.0 73 | // or the content size of a scroll view 74 | 75 | controller.update(progress: newProgress) 76 | ``` 77 | 78 | You can use *easing functions*: 79 | 80 | ``` swift 81 | controller.tween(from: transformA, at: 0.0) 82 | .to(transformB, at: 1.0, withEasing: Easing.easeInOutQuart) 83 | .with(action: tweenView.layer.twc_applyAffineTransform) 84 | ``` 85 | 86 | You can use *[Key-Value Coding](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/Key-ValueCodingExtensions/Key-ValueCodingExtensions.html)*: 87 | 88 | ``` swift 89 | controller.tween(from: 0.0, at: 0.0) 90 | .to(M_PI * 8.0, at: 1.0) 91 | .with(object: tweenView.layer, keyPath: "transform.rotation.z") 92 | ``` 93 | 94 | You can also *observe boundaries*: 95 | 96 | ``` swift 97 | func observeBoundaries() { 98 | controller.observeForward(progress: 0.5) { progress in 99 | // halfway finished! 100 | } 101 | controller.observeBoth(progress: 0.75) { progress in 102 | // this is called when moving backwards or forwards 103 | } 104 | } 105 | ``` 106 | 107 | ## Standard Tweenables 108 | 109 | 110 | * Double 111 | * Float 112 | * Int 113 | * CGFloat 114 | * CGPoint 115 | * CGSize 116 | * CGRect 117 | * UIColor 118 | * CGAffineTransform 119 | * CATransform3D 120 | 121 | ## Installation 122 | 123 | ### CocoaPods 124 | 125 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command: 126 | 127 | ``` bash 128 | $ gem install cocoapods 129 | ``` 130 | 131 | 132 | To integrate TweenController into your Xcode project using CocoaPods, specify it in your `Podfile`: 133 | 134 | ``` ruby 135 | platform :ios, '10.0' 136 | use_frameworks! 137 | 138 | pod 'TweenController', '~> 1.0' 139 | ``` 140 | 141 | Then, run the following command: 142 | 143 | ``` bash 144 | $ pod install 145 | ``` 146 | 147 | You should open the `{Project}.xcworkspace` instead of the `{Project}.xcodeproj` after you installed anything from CocoaPods. 148 | 149 | ### Carthage 150 | 151 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager for Cocoa application. To install the carthage tool, you can use [Homebrew](http://brew.sh). 152 | 153 | ``` bash 154 | $ brew update 155 | $ brew install carthage 156 | ``` 157 | 158 | To integrate TweenController into your Xcode project using Carthage, specify it in your `Cartfile`: 159 | 160 | ``` ogdl 161 | github "daltonclaybrook/tween-controller" ~> 1.0.1 162 | ``` 163 | 164 | Then, run the following command to build the TweenController framework: 165 | 166 | ``` bash 167 | $ carthage update 168 | 169 | ``` 170 | 171 | At last, you need to set up your Xcode project manually to add the TweenController framework. 172 | 173 | On your application targets’ “General” settings tab, in the “Linked Frameworks and Libraries” section, drag and drop each framework you want to use from the Carthage/Build folder on disk. 174 | 175 | On your application targets’ “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase”. Create a Run Script with the following content: 176 | 177 | ``` 178 | /usr/local/bin/carthage copy-frameworks 179 | ``` 180 | 181 | and add the paths to the frameworks you want to use under “Input Files”: 182 | 183 | ``` 184 | $(SRCROOT)/Carthage/Build/iOS/TweenController.framework 185 | ``` 186 | 187 | For more information about how to use Carthage, please see its [project page](https://github.com/Carthage/Carthage). 188 | 189 | 190 | ## Contact 191 | 192 | Follow and contact me on [Twitter](http://twitter.com/daltonclaybrook). If you find an issue, just [open a ticket](https://github.com/daltonclaybrook/tween-controller/issues/new) on it. Pull requests are warmly welcome as well. 193 | 194 | ## License 195 | 196 | TweenController is released under the MIT license. See LICENSE for details. 197 | -------------------------------------------------------------------------------- /TweenController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "TweenController" 4 | s.version = "1.0.1" 5 | s.summary = "A pure Swift toolkit for creating interactive menus and tutorials" 6 | s.platform = :ios, '8.0' 7 | 8 | s.description = <<-DESC 9 | On the surface, TweenController makes it easy to build interactive menus and tutorials. Under the hood, it's a simple but powerful toolkit to interpolate between values that are Tweenable. 10 | DESC 11 | 12 | s.homepage = "https://github.com/daltonclaybrook/tween-controller" 13 | s.screenshots = "https://raw.githubusercontent.com/daltonclaybrook/tween-controller/master/example.gif" 14 | 15 | s.license = { :type => "MIT", :file => "LICENSE" } 16 | 17 | s.authors = { "Dalton Claybrook" => "daltonclaybrook@gmail.com" } 18 | s.social_media_url = "http://twitter.com/daltonclaybrook" 19 | 20 | s.source = { :git => "https://github.com/daltonclaybrook/tween-controller.git", :tag => s.version } 21 | 22 | s.source_files = ["TweenController/*.swift", "TweenController/TweenController.h"] 23 | s.public_header_files = ["TweenController/TweenController.h"] 24 | 25 | s.requires_arc = true 26 | 27 | end -------------------------------------------------------------------------------- /TweenController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 753655FD1CE127100031BEA5 /* TweenController.h in Headers */ = {isa = PBXBuildFile; fileRef = 753655FC1CE127100031BEA5 /* TweenController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 7536560B1CE127350031BEA5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7536560A1CE127350031BEA5 /* AppDelegate.swift */; }; 12 | 7536560D1CE127350031BEA5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7536560C1CE127350031BEA5 /* ViewController.swift */; }; 13 | 753656101CE127350031BEA5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7536560E1CE127350031BEA5 /* Main.storyboard */; }; 14 | 753656121CE127350031BEA5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 753656111CE127350031BEA5 /* Assets.xcassets */; }; 15 | 753656151CE127350031BEA5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 753656131CE127350031BEA5 /* LaunchScreen.storyboard */; }; 16 | 7536561B1CE127460031BEA5 /* TweenController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7536561A1CE127460031BEA5 /* TweenController.swift */; }; 17 | 7536561D1CE12A510031BEA5 /* Tweenable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7536561C1CE12A510031BEA5 /* Tweenable.swift */; }; 18 | 7536561F1CE12CB70031BEA5 /* StandardTweenables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7536561E1CE12CB70031BEA5 /* StandardTweenables.swift */; }; 19 | 753656211CE13D170031BEA5 /* TweenDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753656201CE13D170031BEA5 /* TweenDescriptor.swift */; }; 20 | 755644921E249DB4007C6143 /* TweenControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755644911E249DB4007C6143 /* TweenControllerTests.swift */; }; 21 | 755644941E249DB4007C6143 /* TweenController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 753655F91CE127100031BEA5 /* TweenController.framework */; }; 22 | 7556449D1E249E24007C6143 /* StandardTweenablesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7556449C1E249E24007C6143 /* StandardTweenablesTests.swift */; }; 23 | 755644A01E24A7FE007C6143 /* KeypathListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7556449F1E24A7FE007C6143 /* KeypathListener.swift */; }; 24 | 755644A31E25F4EA007C6143 /* KeyPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755644A21E25F4EA007C6143 /* KeyPathTests.swift */; }; 25 | 755644A51E25F618007C6143 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755644A41E25F618007C6143 /* ExtensionsTests.swift */; }; 26 | 755644A71E2731DD007C6143 /* EasingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755644A61E2731DD007C6143 /* EasingTests.swift */; }; 27 | 7586C3A91CF7EB70007DC4CB /* TutorialBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7586C3A81CF7EB70007DC4CB /* TutorialBuilder.swift */; }; 28 | 7586C3AB1CF7EF9A007DC4CB /* StartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7586C3AA1CF7EF9A007DC4CB /* StartViewController.swift */; }; 29 | 7586C3AD1CF806FC007DC4CB /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7586C3AC1CF806FC007DC4CB /* Extensions.swift */; }; 30 | 75A3255F1CFFEE9200D2A16E /* KVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75A3255E1CFFEE9200D2A16E /* KVC.swift */; }; 31 | 75CCBD711CFA5DA40024787B /* TweenController.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 753655F91CE127100031BEA5 /* TweenController.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 32 | 75FD5A721CFE947400B5C4A6 /* Easing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75FD5A711CFE947400B5C4A6 /* Easing.swift */; }; 33 | BE4030611CE3C11D0067DF4F /* Boundary.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE4030601CE3C11D0067DF4F /* Boundary.swift */; }; 34 | BE40306E1CE3C9420067DF4F /* TweenController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 753655F91CE127100031BEA5 /* TweenController.framework */; }; 35 | BE4030701CE3D9020067DF4F /* TweenPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE40306F1CE3D9020067DF4F /* TweenPromise.swift */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXContainerItemProxy section */ 39 | 755644951E249DB4007C6143 /* PBXContainerItemProxy */ = { 40 | isa = PBXContainerItemProxy; 41 | containerPortal = 753655F01CE127100031BEA5 /* Project object */; 42 | proxyType = 1; 43 | remoteGlobalIDString = 753655F81CE127100031BEA5; 44 | remoteInfo = TweenController; 45 | }; 46 | BE40306C1CE3C93C0067DF4F /* PBXContainerItemProxy */ = { 47 | isa = PBXContainerItemProxy; 48 | containerPortal = 753655F01CE127100031BEA5 /* Project object */; 49 | proxyType = 1; 50 | remoteGlobalIDString = 753655F81CE127100031BEA5; 51 | remoteInfo = TweenController; 52 | }; 53 | /* End PBXContainerItemProxy section */ 54 | 55 | /* Begin PBXCopyFilesBuildPhase section */ 56 | 75CCBD701CFA5D920024787B /* Copy Frameworks */ = { 57 | isa = PBXCopyFilesBuildPhase; 58 | buildActionMask = 2147483647; 59 | dstPath = ""; 60 | dstSubfolderSpec = 10; 61 | files = ( 62 | 75CCBD711CFA5DA40024787B /* TweenController.framework in Copy Frameworks */, 63 | ); 64 | name = "Copy Frameworks"; 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | /* End PBXCopyFilesBuildPhase section */ 68 | 69 | /* Begin PBXFileReference section */ 70 | 753655F91CE127100031BEA5 /* TweenController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TweenController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | 753655FC1CE127100031BEA5 /* TweenController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TweenController.h; sourceTree = ""; }; 72 | 753655FE1CE127100031BEA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73 | 753656081CE127350031BEA5 /* TweenControllerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TweenControllerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 74 | 7536560A1CE127350031BEA5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 75 | 7536560C1CE127350031BEA5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 76 | 7536560F1CE127350031BEA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 77 | 753656111CE127350031BEA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 78 | 753656141CE127350031BEA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 79 | 753656161CE127350031BEA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 80 | 7536561A1CE127460031BEA5 /* TweenController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweenController.swift; sourceTree = ""; }; 81 | 7536561C1CE12A510031BEA5 /* Tweenable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tweenable.swift; sourceTree = ""; }; 82 | 7536561E1CE12CB70031BEA5 /* StandardTweenables.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandardTweenables.swift; sourceTree = ""; }; 83 | 753656201CE13D170031BEA5 /* TweenDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweenDescriptor.swift; sourceTree = ""; }; 84 | 7556448F1E249DB4007C6143 /* TweenControllerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TweenControllerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 85 | 755644911E249DB4007C6143 /* TweenControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweenControllerTests.swift; sourceTree = ""; }; 86 | 755644931E249DB4007C6143 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 87 | 7556449C1E249E24007C6143 /* StandardTweenablesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandardTweenablesTests.swift; sourceTree = ""; }; 88 | 7556449F1E24A7FE007C6143 /* KeypathListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeypathListener.swift; sourceTree = ""; }; 89 | 755644A11E24B923007C6143 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = SOURCE_ROOT; }; 90 | 755644A21E25F4EA007C6143 /* KeyPathTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPathTests.swift; sourceTree = ""; }; 91 | 755644A41E25F618007C6143 /* ExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionsTests.swift; sourceTree = ""; }; 92 | 755644A61E2731DD007C6143 /* EasingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EasingTests.swift; sourceTree = ""; }; 93 | 7586C3A81CF7EB70007DC4CB /* TutorialBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TutorialBuilder.swift; sourceTree = ""; }; 94 | 7586C3AA1CF7EF9A007DC4CB /* StartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StartViewController.swift; sourceTree = ""; }; 95 | 7586C3AC1CF806FC007DC4CB /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 96 | 75A3255E1CFFEE9200D2A16E /* KVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KVC.swift; sourceTree = ""; }; 97 | 75FD5A711CFE947400B5C4A6 /* Easing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Easing.swift; sourceTree = ""; }; 98 | BE4030601CE3C11D0067DF4F /* Boundary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Boundary.swift; sourceTree = ""; }; 99 | BE40306F1CE3D9020067DF4F /* TweenPromise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweenPromise.swift; sourceTree = ""; }; 100 | /* End PBXFileReference section */ 101 | 102 | /* Begin PBXFrameworksBuildPhase section */ 103 | 753655F51CE127100031BEA5 /* Frameworks */ = { 104 | isa = PBXFrameworksBuildPhase; 105 | buildActionMask = 2147483647; 106 | files = ( 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | 753656051CE127350031BEA5 /* Frameworks */ = { 111 | isa = PBXFrameworksBuildPhase; 112 | buildActionMask = 2147483647; 113 | files = ( 114 | BE40306E1CE3C9420067DF4F /* TweenController.framework in Frameworks */, 115 | ); 116 | runOnlyForDeploymentPostprocessing = 0; 117 | }; 118 | 7556448C1E249DB4007C6143 /* Frameworks */ = { 119 | isa = PBXFrameworksBuildPhase; 120 | buildActionMask = 2147483647; 121 | files = ( 122 | 755644941E249DB4007C6143 /* TweenController.framework in Frameworks */, 123 | ); 124 | runOnlyForDeploymentPostprocessing = 0; 125 | }; 126 | /* End PBXFrameworksBuildPhase section */ 127 | 128 | /* Begin PBXGroup section */ 129 | 753655EF1CE127100031BEA5 = { 130 | isa = PBXGroup; 131 | children = ( 132 | 753655FB1CE127100031BEA5 /* TweenController */, 133 | 753656091CE127350031BEA5 /* TweenControllerDemo */, 134 | 755644901E249DB4007C6143 /* TweenControllerTests */, 135 | 753655FA1CE127100031BEA5 /* Products */, 136 | ); 137 | sourceTree = ""; 138 | }; 139 | 753655FA1CE127100031BEA5 /* Products */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 753655F91CE127100031BEA5 /* TweenController.framework */, 143 | 753656081CE127350031BEA5 /* TweenControllerDemo.app */, 144 | 7556448F1E249DB4007C6143 /* TweenControllerTests.xctest */, 145 | ); 146 | name = Products; 147 | sourceTree = ""; 148 | }; 149 | 753655FB1CE127100031BEA5 /* TweenController */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 753656231CE13F030031BEA5 /* SupportingFiles */, 153 | 753656221CE13EFB0031BEA5 /* Source */, 154 | ); 155 | path = TweenController; 156 | sourceTree = ""; 157 | }; 158 | 753656091CE127350031BEA5 /* TweenControllerDemo */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 7586C3A71CF7EB56007DC4CB /* Tutorial */, 162 | 7536560A1CE127350031BEA5 /* AppDelegate.swift */, 163 | 7536560C1CE127350031BEA5 /* ViewController.swift */, 164 | 7586C3AA1CF7EF9A007DC4CB /* StartViewController.swift */, 165 | 7536560E1CE127350031BEA5 /* Main.storyboard */, 166 | 753656111CE127350031BEA5 /* Assets.xcassets */, 167 | 753656131CE127350031BEA5 /* LaunchScreen.storyboard */, 168 | 753656161CE127350031BEA5 /* Info.plist */, 169 | ); 170 | path = TweenControllerDemo; 171 | sourceTree = ""; 172 | }; 173 | 753656221CE13EFB0031BEA5 /* Source */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 75FD5A711CFE947400B5C4A6 /* Easing.swift */, 177 | 7536561A1CE127460031BEA5 /* TweenController.swift */, 178 | 7536561C1CE12A510031BEA5 /* Tweenable.swift */, 179 | 7536561E1CE12CB70031BEA5 /* StandardTweenables.swift */, 180 | BE40306F1CE3D9020067DF4F /* TweenPromise.swift */, 181 | 753656201CE13D170031BEA5 /* TweenDescriptor.swift */, 182 | BE4030601CE3C11D0067DF4F /* Boundary.swift */, 183 | 7586C3AC1CF806FC007DC4CB /* Extensions.swift */, 184 | 75A3255E1CFFEE9200D2A16E /* KVC.swift */, 185 | ); 186 | name = Source; 187 | sourceTree = ""; 188 | }; 189 | 753656231CE13F030031BEA5 /* SupportingFiles */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | 753655FE1CE127100031BEA5 /* Info.plist */, 193 | 753655FC1CE127100031BEA5 /* TweenController.h */, 194 | ); 195 | name = SupportingFiles; 196 | sourceTree = ""; 197 | }; 198 | 755644901E249DB4007C6143 /* TweenControllerTests */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 7556449E1E24A7E7007C6143 /* Helpers */, 202 | 7556449A1E249DEA007C6143 /* Tests */, 203 | 7556449B1E249DF5007C6143 /* SupportingFiles */, 204 | ); 205 | path = TweenControllerTests; 206 | sourceTree = ""; 207 | }; 208 | 7556449A1E249DEA007C6143 /* Tests */ = { 209 | isa = PBXGroup; 210 | children = ( 211 | 755644911E249DB4007C6143 /* TweenControllerTests.swift */, 212 | 7556449C1E249E24007C6143 /* StandardTweenablesTests.swift */, 213 | 755644A21E25F4EA007C6143 /* KeyPathTests.swift */, 214 | 755644A41E25F618007C6143 /* ExtensionsTests.swift */, 215 | 755644A61E2731DD007C6143 /* EasingTests.swift */, 216 | ); 217 | name = Tests; 218 | sourceTree = ""; 219 | }; 220 | 7556449B1E249DF5007C6143 /* SupportingFiles */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 755644A11E24B923007C6143 /* .travis.yml */, 224 | 755644931E249DB4007C6143 /* Info.plist */, 225 | ); 226 | name = SupportingFiles; 227 | sourceTree = ""; 228 | }; 229 | 7556449E1E24A7E7007C6143 /* Helpers */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | 7556449F1E24A7FE007C6143 /* KeypathListener.swift */, 233 | ); 234 | name = Helpers; 235 | sourceTree = ""; 236 | }; 237 | 7586C3A71CF7EB56007DC4CB /* Tutorial */ = { 238 | isa = PBXGroup; 239 | children = ( 240 | 7586C3A81CF7EB70007DC4CB /* TutorialBuilder.swift */, 241 | ); 242 | name = Tutorial; 243 | sourceTree = ""; 244 | }; 245 | /* End PBXGroup section */ 246 | 247 | /* Begin PBXHeadersBuildPhase section */ 248 | 753655F61CE127100031BEA5 /* Headers */ = { 249 | isa = PBXHeadersBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | 753655FD1CE127100031BEA5 /* TweenController.h in Headers */, 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | /* End PBXHeadersBuildPhase section */ 257 | 258 | /* Begin PBXNativeTarget section */ 259 | 753655F81CE127100031BEA5 /* TweenController */ = { 260 | isa = PBXNativeTarget; 261 | buildConfigurationList = 753656011CE127100031BEA5 /* Build configuration list for PBXNativeTarget "TweenController" */; 262 | buildPhases = ( 263 | 753655F41CE127100031BEA5 /* Sources */, 264 | 753655F51CE127100031BEA5 /* Frameworks */, 265 | 753655F61CE127100031BEA5 /* Headers */, 266 | 753655F71CE127100031BEA5 /* Resources */, 267 | ); 268 | buildRules = ( 269 | ); 270 | dependencies = ( 271 | ); 272 | name = TweenController; 273 | productName = TweenController; 274 | productReference = 753655F91CE127100031BEA5 /* TweenController.framework */; 275 | productType = "com.apple.product-type.framework"; 276 | }; 277 | 753656071CE127350031BEA5 /* TweenControllerDemo */ = { 278 | isa = PBXNativeTarget; 279 | buildConfigurationList = 753656171CE127350031BEA5 /* Build configuration list for PBXNativeTarget "TweenControllerDemo" */; 280 | buildPhases = ( 281 | 753656041CE127350031BEA5 /* Sources */, 282 | 753656051CE127350031BEA5 /* Frameworks */, 283 | 753656061CE127350031BEA5 /* Resources */, 284 | 75CCBD701CFA5D920024787B /* Copy Frameworks */, 285 | ); 286 | buildRules = ( 287 | ); 288 | dependencies = ( 289 | BE40306D1CE3C93C0067DF4F /* PBXTargetDependency */, 290 | ); 291 | name = TweenControllerDemo; 292 | productName = TweenControllerDemo; 293 | productReference = 753656081CE127350031BEA5 /* TweenControllerDemo.app */; 294 | productType = "com.apple.product-type.application"; 295 | }; 296 | 7556448E1E249DB4007C6143 /* TweenControllerTests */ = { 297 | isa = PBXNativeTarget; 298 | buildConfigurationList = 755644971E249DB4007C6143 /* Build configuration list for PBXNativeTarget "TweenControllerTests" */; 299 | buildPhases = ( 300 | 7556448B1E249DB4007C6143 /* Sources */, 301 | 7556448C1E249DB4007C6143 /* Frameworks */, 302 | 7556448D1E249DB4007C6143 /* Resources */, 303 | ); 304 | buildRules = ( 305 | ); 306 | dependencies = ( 307 | 755644961E249DB4007C6143 /* PBXTargetDependency */, 308 | ); 309 | name = TweenControllerTests; 310 | productName = TweenControllerTests; 311 | productReference = 7556448F1E249DB4007C6143 /* TweenControllerTests.xctest */; 312 | productType = "com.apple.product-type.bundle.unit-test"; 313 | }; 314 | /* End PBXNativeTarget section */ 315 | 316 | /* Begin PBXProject section */ 317 | 753655F01CE127100031BEA5 /* Project object */ = { 318 | isa = PBXProject; 319 | attributes = { 320 | LastSwiftUpdateCheck = 0820; 321 | LastUpgradeCheck = 0930; 322 | ORGANIZATIONNAME = "Claybrook Software"; 323 | TargetAttributes = { 324 | 753655F81CE127100031BEA5 = { 325 | CreatedOnToolsVersion = 7.3; 326 | LastSwiftMigration = 0930; 327 | }; 328 | 753656071CE127350031BEA5 = { 329 | CreatedOnToolsVersion = 7.3; 330 | LastSwiftMigration = 0930; 331 | ProvisioningStyle = Automatic; 332 | }; 333 | 7556448E1E249DB4007C6143 = { 334 | CreatedOnToolsVersion = 8.2.1; 335 | DevelopmentTeam = 4SGAZDJL4U; 336 | LastSwiftMigration = 0930; 337 | ProvisioningStyle = Automatic; 338 | }; 339 | }; 340 | }; 341 | buildConfigurationList = 753655F31CE127100031BEA5 /* Build configuration list for PBXProject "TweenController" */; 342 | compatibilityVersion = "Xcode 3.2"; 343 | developmentRegion = English; 344 | hasScannedForEncodings = 0; 345 | knownRegions = ( 346 | en, 347 | Base, 348 | ); 349 | mainGroup = 753655EF1CE127100031BEA5; 350 | productRefGroup = 753655FA1CE127100031BEA5 /* Products */; 351 | projectDirPath = ""; 352 | projectRoot = ""; 353 | targets = ( 354 | 753655F81CE127100031BEA5 /* TweenController */, 355 | 753656071CE127350031BEA5 /* TweenControllerDemo */, 356 | 7556448E1E249DB4007C6143 /* TweenControllerTests */, 357 | ); 358 | }; 359 | /* End PBXProject section */ 360 | 361 | /* Begin PBXResourcesBuildPhase section */ 362 | 753655F71CE127100031BEA5 /* Resources */ = { 363 | isa = PBXResourcesBuildPhase; 364 | buildActionMask = 2147483647; 365 | files = ( 366 | ); 367 | runOnlyForDeploymentPostprocessing = 0; 368 | }; 369 | 753656061CE127350031BEA5 /* Resources */ = { 370 | isa = PBXResourcesBuildPhase; 371 | buildActionMask = 2147483647; 372 | files = ( 373 | 753656151CE127350031BEA5 /* LaunchScreen.storyboard in Resources */, 374 | 753656121CE127350031BEA5 /* Assets.xcassets in Resources */, 375 | 753656101CE127350031BEA5 /* Main.storyboard in Resources */, 376 | ); 377 | runOnlyForDeploymentPostprocessing = 0; 378 | }; 379 | 7556448D1E249DB4007C6143 /* Resources */ = { 380 | isa = PBXResourcesBuildPhase; 381 | buildActionMask = 2147483647; 382 | files = ( 383 | ); 384 | runOnlyForDeploymentPostprocessing = 0; 385 | }; 386 | /* End PBXResourcesBuildPhase section */ 387 | 388 | /* Begin PBXSourcesBuildPhase section */ 389 | 753655F41CE127100031BEA5 /* Sources */ = { 390 | isa = PBXSourcesBuildPhase; 391 | buildActionMask = 2147483647; 392 | files = ( 393 | 7536561B1CE127460031BEA5 /* TweenController.swift in Sources */, 394 | BE4030701CE3D9020067DF4F /* TweenPromise.swift in Sources */, 395 | 7536561F1CE12CB70031BEA5 /* StandardTweenables.swift in Sources */, 396 | 753656211CE13D170031BEA5 /* TweenDescriptor.swift in Sources */, 397 | 7536561D1CE12A510031BEA5 /* Tweenable.swift in Sources */, 398 | 75A3255F1CFFEE9200D2A16E /* KVC.swift in Sources */, 399 | BE4030611CE3C11D0067DF4F /* Boundary.swift in Sources */, 400 | 75FD5A721CFE947400B5C4A6 /* Easing.swift in Sources */, 401 | 7586C3AD1CF806FC007DC4CB /* Extensions.swift in Sources */, 402 | ); 403 | runOnlyForDeploymentPostprocessing = 0; 404 | }; 405 | 753656041CE127350031BEA5 /* Sources */ = { 406 | isa = PBXSourcesBuildPhase; 407 | buildActionMask = 2147483647; 408 | files = ( 409 | 7536560D1CE127350031BEA5 /* ViewController.swift in Sources */, 410 | 7586C3AB1CF7EF9A007DC4CB /* StartViewController.swift in Sources */, 411 | 7586C3A91CF7EB70007DC4CB /* TutorialBuilder.swift in Sources */, 412 | 7536560B1CE127350031BEA5 /* AppDelegate.swift in Sources */, 413 | ); 414 | runOnlyForDeploymentPostprocessing = 0; 415 | }; 416 | 7556448B1E249DB4007C6143 /* Sources */ = { 417 | isa = PBXSourcesBuildPhase; 418 | buildActionMask = 2147483647; 419 | files = ( 420 | 755644A51E25F618007C6143 /* ExtensionsTests.swift in Sources */, 421 | 7556449D1E249E24007C6143 /* StandardTweenablesTests.swift in Sources */, 422 | 755644A01E24A7FE007C6143 /* KeypathListener.swift in Sources */, 423 | 755644A31E25F4EA007C6143 /* KeyPathTests.swift in Sources */, 424 | 755644A71E2731DD007C6143 /* EasingTests.swift in Sources */, 425 | 755644921E249DB4007C6143 /* TweenControllerTests.swift in Sources */, 426 | ); 427 | runOnlyForDeploymentPostprocessing = 0; 428 | }; 429 | /* End PBXSourcesBuildPhase section */ 430 | 431 | /* Begin PBXTargetDependency section */ 432 | 755644961E249DB4007C6143 /* PBXTargetDependency */ = { 433 | isa = PBXTargetDependency; 434 | target = 753655F81CE127100031BEA5 /* TweenController */; 435 | targetProxy = 755644951E249DB4007C6143 /* PBXContainerItemProxy */; 436 | }; 437 | BE40306D1CE3C93C0067DF4F /* PBXTargetDependency */ = { 438 | isa = PBXTargetDependency; 439 | target = 753655F81CE127100031BEA5 /* TweenController */; 440 | targetProxy = BE40306C1CE3C93C0067DF4F /* PBXContainerItemProxy */; 441 | }; 442 | /* End PBXTargetDependency section */ 443 | 444 | /* Begin PBXVariantGroup section */ 445 | 7536560E1CE127350031BEA5 /* Main.storyboard */ = { 446 | isa = PBXVariantGroup; 447 | children = ( 448 | 7536560F1CE127350031BEA5 /* Base */, 449 | ); 450 | name = Main.storyboard; 451 | sourceTree = ""; 452 | }; 453 | 753656131CE127350031BEA5 /* LaunchScreen.storyboard */ = { 454 | isa = PBXVariantGroup; 455 | children = ( 456 | 753656141CE127350031BEA5 /* Base */, 457 | ); 458 | name = LaunchScreen.storyboard; 459 | sourceTree = ""; 460 | }; 461 | /* End PBXVariantGroup section */ 462 | 463 | /* Begin XCBuildConfiguration section */ 464 | 753655FF1CE127100031BEA5 /* Debug */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ALWAYS_SEARCH_USER_PATHS = NO; 468 | CLANG_ANALYZER_NONNULL = YES; 469 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 470 | CLANG_CXX_LIBRARY = "libc++"; 471 | CLANG_ENABLE_MODULES = YES; 472 | CLANG_ENABLE_OBJC_ARC = YES; 473 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 474 | CLANG_WARN_BOOL_CONVERSION = YES; 475 | CLANG_WARN_COMMA = YES; 476 | CLANG_WARN_CONSTANT_CONVERSION = YES; 477 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 478 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 479 | CLANG_WARN_EMPTY_BODY = YES; 480 | CLANG_WARN_ENUM_CONVERSION = YES; 481 | CLANG_WARN_INFINITE_RECURSION = YES; 482 | CLANG_WARN_INT_CONVERSION = YES; 483 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 484 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 485 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 486 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 487 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 488 | CLANG_WARN_STRICT_PROTOTYPES = YES; 489 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 490 | CLANG_WARN_UNREACHABLE_CODE = YES; 491 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 492 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 493 | COPY_PHASE_STRIP = NO; 494 | CURRENT_PROJECT_VERSION = 1; 495 | DEBUG_INFORMATION_FORMAT = dwarf; 496 | ENABLE_STRICT_OBJC_MSGSEND = YES; 497 | ENABLE_TESTABILITY = YES; 498 | GCC_C_LANGUAGE_STANDARD = gnu99; 499 | GCC_DYNAMIC_NO_PIC = NO; 500 | GCC_NO_COMMON_BLOCKS = YES; 501 | GCC_OPTIMIZATION_LEVEL = 0; 502 | GCC_PREPROCESSOR_DEFINITIONS = ( 503 | "DEBUG=1", 504 | "$(inherited)", 505 | ); 506 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 507 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 508 | GCC_WARN_UNDECLARED_SELECTOR = YES; 509 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 510 | GCC_WARN_UNUSED_FUNCTION = YES; 511 | GCC_WARN_UNUSED_VARIABLE = YES; 512 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 513 | MTL_ENABLE_DEBUG_INFO = YES; 514 | ONLY_ACTIVE_ARCH = YES; 515 | SDKROOT = iphoneos; 516 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 517 | TARGETED_DEVICE_FAMILY = "1,2"; 518 | VERSIONING_SYSTEM = "apple-generic"; 519 | VERSION_INFO_PREFIX = ""; 520 | }; 521 | name = Debug; 522 | }; 523 | 753656001CE127100031BEA5 /* Release */ = { 524 | isa = XCBuildConfiguration; 525 | buildSettings = { 526 | ALWAYS_SEARCH_USER_PATHS = NO; 527 | CLANG_ANALYZER_NONNULL = YES; 528 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 529 | CLANG_CXX_LIBRARY = "libc++"; 530 | CLANG_ENABLE_MODULES = YES; 531 | CLANG_ENABLE_OBJC_ARC = YES; 532 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 533 | CLANG_WARN_BOOL_CONVERSION = YES; 534 | CLANG_WARN_COMMA = YES; 535 | CLANG_WARN_CONSTANT_CONVERSION = YES; 536 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 537 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 538 | CLANG_WARN_EMPTY_BODY = YES; 539 | CLANG_WARN_ENUM_CONVERSION = YES; 540 | CLANG_WARN_INFINITE_RECURSION = YES; 541 | CLANG_WARN_INT_CONVERSION = YES; 542 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 543 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 544 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 545 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 546 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 547 | CLANG_WARN_STRICT_PROTOTYPES = YES; 548 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 549 | CLANG_WARN_UNREACHABLE_CODE = YES; 550 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 551 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 552 | COPY_PHASE_STRIP = NO; 553 | CURRENT_PROJECT_VERSION = 1; 554 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 555 | ENABLE_NS_ASSERTIONS = NO; 556 | ENABLE_STRICT_OBJC_MSGSEND = YES; 557 | GCC_C_LANGUAGE_STANDARD = gnu99; 558 | GCC_NO_COMMON_BLOCKS = YES; 559 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 560 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 561 | GCC_WARN_UNDECLARED_SELECTOR = YES; 562 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 563 | GCC_WARN_UNUSED_FUNCTION = YES; 564 | GCC_WARN_UNUSED_VARIABLE = YES; 565 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 566 | MTL_ENABLE_DEBUG_INFO = NO; 567 | SDKROOT = iphoneos; 568 | TARGETED_DEVICE_FAMILY = "1,2"; 569 | VALIDATE_PRODUCT = YES; 570 | VERSIONING_SYSTEM = "apple-generic"; 571 | VERSION_INFO_PREFIX = ""; 572 | }; 573 | name = Release; 574 | }; 575 | 753656021CE127100031BEA5 /* Debug */ = { 576 | isa = XCBuildConfiguration; 577 | buildSettings = { 578 | CLANG_ENABLE_MODULES = YES; 579 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 580 | DEFINES_MODULE = YES; 581 | DEVELOPMENT_TEAM = ""; 582 | DYLIB_COMPATIBILITY_VERSION = 1; 583 | DYLIB_CURRENT_VERSION = 1; 584 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 585 | INFOPLIST_FILE = TweenController/Info.plist; 586 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 587 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 588 | PRODUCT_BUNDLE_IDENTIFIER = com.claybrooksoftware.TweenController; 589 | PRODUCT_NAME = "$(TARGET_NAME)"; 590 | SKIP_INSTALL = YES; 591 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 592 | SWIFT_VERSION = 4.0; 593 | }; 594 | name = Debug; 595 | }; 596 | 753656031CE127100031BEA5 /* Release */ = { 597 | isa = XCBuildConfiguration; 598 | buildSettings = { 599 | CLANG_ENABLE_MODULES = YES; 600 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 601 | DEFINES_MODULE = YES; 602 | DEVELOPMENT_TEAM = ""; 603 | DYLIB_COMPATIBILITY_VERSION = 1; 604 | DYLIB_CURRENT_VERSION = 1; 605 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 606 | INFOPLIST_FILE = TweenController/Info.plist; 607 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 608 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 609 | PRODUCT_BUNDLE_IDENTIFIER = com.claybrooksoftware.TweenController; 610 | PRODUCT_NAME = "$(TARGET_NAME)"; 611 | SKIP_INSTALL = YES; 612 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 613 | SWIFT_VERSION = 4.0; 614 | }; 615 | name = Release; 616 | }; 617 | 753656181CE127350031BEA5 /* Debug */ = { 618 | isa = XCBuildConfiguration; 619 | buildSettings = { 620 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 621 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 622 | DEVELOPMENT_TEAM = ""; 623 | INFOPLIST_FILE = TweenControllerDemo/Info.plist; 624 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 625 | PRODUCT_BUNDLE_IDENTIFIER = com.claybrooksoftware.TweenControllerDemo; 626 | PRODUCT_NAME = "$(TARGET_NAME)"; 627 | SWIFT_VERSION = 4.0; 628 | }; 629 | name = Debug; 630 | }; 631 | 753656191CE127350031BEA5 /* Release */ = { 632 | isa = XCBuildConfiguration; 633 | buildSettings = { 634 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 635 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 636 | DEVELOPMENT_TEAM = ""; 637 | INFOPLIST_FILE = TweenControllerDemo/Info.plist; 638 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 639 | PRODUCT_BUNDLE_IDENTIFIER = com.claybrooksoftware.TweenControllerDemo; 640 | PRODUCT_NAME = "$(TARGET_NAME)"; 641 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 642 | SWIFT_VERSION = 4.0; 643 | }; 644 | name = Release; 645 | }; 646 | 755644981E249DB4007C6143 /* Debug */ = { 647 | isa = XCBuildConfiguration; 648 | buildSettings = { 649 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 650 | DEVELOPMENT_TEAM = 4SGAZDJL4U; 651 | INFOPLIST_FILE = TweenControllerTests/Info.plist; 652 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 653 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 654 | PRODUCT_BUNDLE_IDENTIFIER = com.claybrooksoftware.TweenControllerTests; 655 | PRODUCT_NAME = "$(TARGET_NAME)"; 656 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 657 | SWIFT_VERSION = 4.0; 658 | }; 659 | name = Debug; 660 | }; 661 | 755644991E249DB4007C6143 /* Release */ = { 662 | isa = XCBuildConfiguration; 663 | buildSettings = { 664 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 665 | DEVELOPMENT_TEAM = 4SGAZDJL4U; 666 | INFOPLIST_FILE = TweenControllerTests/Info.plist; 667 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 668 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 669 | PRODUCT_BUNDLE_IDENTIFIER = com.claybrooksoftware.TweenControllerTests; 670 | PRODUCT_NAME = "$(TARGET_NAME)"; 671 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 672 | SWIFT_VERSION = 4.0; 673 | }; 674 | name = Release; 675 | }; 676 | /* End XCBuildConfiguration section */ 677 | 678 | /* Begin XCConfigurationList section */ 679 | 753655F31CE127100031BEA5 /* Build configuration list for PBXProject "TweenController" */ = { 680 | isa = XCConfigurationList; 681 | buildConfigurations = ( 682 | 753655FF1CE127100031BEA5 /* Debug */, 683 | 753656001CE127100031BEA5 /* Release */, 684 | ); 685 | defaultConfigurationIsVisible = 0; 686 | defaultConfigurationName = Release; 687 | }; 688 | 753656011CE127100031BEA5 /* Build configuration list for PBXNativeTarget "TweenController" */ = { 689 | isa = XCConfigurationList; 690 | buildConfigurations = ( 691 | 753656021CE127100031BEA5 /* Debug */, 692 | 753656031CE127100031BEA5 /* Release */, 693 | ); 694 | defaultConfigurationIsVisible = 0; 695 | defaultConfigurationName = Release; 696 | }; 697 | 753656171CE127350031BEA5 /* Build configuration list for PBXNativeTarget "TweenControllerDemo" */ = { 698 | isa = XCConfigurationList; 699 | buildConfigurations = ( 700 | 753656181CE127350031BEA5 /* Debug */, 701 | 753656191CE127350031BEA5 /* Release */, 702 | ); 703 | defaultConfigurationIsVisible = 0; 704 | defaultConfigurationName = Release; 705 | }; 706 | 755644971E249DB4007C6143 /* Build configuration list for PBXNativeTarget "TweenControllerTests" */ = { 707 | isa = XCConfigurationList; 708 | buildConfigurations = ( 709 | 755644981E249DB4007C6143 /* Debug */, 710 | 755644991E249DB4007C6143 /* Release */, 711 | ); 712 | defaultConfigurationIsVisible = 0; 713 | defaultConfigurationName = Release; 714 | }; 715 | /* End XCConfigurationList section */ 716 | }; 717 | rootObject = 753655F01CE127100031BEA5 /* Project object */; 718 | } 719 | -------------------------------------------------------------------------------- /TweenController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TweenController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TweenController.xcodeproj/xcshareddata/xcschemes/TweenController.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /TweenController.xcodeproj/xcuserdata/daltonclaybrook.xcuserdatad/xcschemes/TweenControllerDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /TweenController.xcodeproj/xcuserdata/daltonclaybrook.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TweenController.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | TweenControllerDemo.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 753655F81CE127100031BEA5 21 | 22 | primary 23 | 24 | 25 | 753656071CE127350031BEA5 26 | 27 | primary 28 | 29 | 30 | 7556448E1E249DB4007C6143 31 | 32 | primary 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /TweenController/Boundary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Boundary.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 5/11/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | struct Boundary { 29 | struct Direction: OptionSet { 30 | let rawValue: Int 31 | init(rawValue: Int) { self.rawValue = rawValue } 32 | 33 | static let forward = Direction(rawValue: 1 << 0) 34 | static let backward = Direction(rawValue: 1 << 1) 35 | static let both: Direction = [forward, backward] 36 | } 37 | 38 | let progress: Double 39 | let block: TweenObserverBlock 40 | let direction: Direction 41 | } 42 | -------------------------------------------------------------------------------- /TweenController/Easing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Easing.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 5/31/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | /// A collection of easing functions beautifully demonstrated at http://gizma.com/easing/ 29 | public struct Easing { 30 | 31 | // t is a time value between 0.0 - 1.0 32 | // Return value is a value between 0.0 - 1.0 with the easing function applied 33 | public typealias Function = (_ t: Double) -> Double 34 | 35 | //MARK: Linear 36 | 37 | public static func linear(_ t: Double) -> Double { 38 | return t 39 | } 40 | 41 | //MARK: Quadratic 42 | 43 | public static func easeInQuad(_ t: Double) -> Double { 44 | return t * t 45 | } 46 | 47 | public static func easeOutQuad(_ t: Double) -> Double { 48 | return -t * (t - 2) 49 | } 50 | 51 | public static func easeInOutQuad(_ t: Double) -> Double { 52 | var _t = t / 0.5 53 | if _t < 1.0 { 54 | return 0.5 * _t * _t 55 | } 56 | _t -= 1.0 57 | return -0.5 * (_t * (_t - 2.0) - 1.0) 58 | } 59 | 60 | //MARK: Cubic 61 | 62 | public static func easeInCubic(_ t: Double) -> Double { 63 | return t * t * t 64 | } 65 | 66 | public static func easeOutCubic(_ t: Double) -> Double { 67 | let _t = t - 1.0 68 | return _t * _t * _t + 1 69 | } 70 | 71 | public static func easeInOutCubic(_ t: Double) -> Double { 72 | var _t = t / 0.5 73 | if _t < 1.0 { 74 | return 0.5 * _t * _t * _t 75 | } 76 | _t -= 2.0 77 | return 0.5 * (_t * _t * _t + 2.0) 78 | } 79 | 80 | //MARK: Quartic 81 | 82 | public static func easeInQuart(_ t: Double) -> Double { 83 | return t * t * t * t 84 | } 85 | 86 | public static func easeOutQuart(_ t: Double) -> Double { 87 | let _t = t - 1.0 88 | return -(_t * _t * _t * _t + 1) 89 | } 90 | 91 | public static func easeInOutQuart(_ t: Double) -> Double { 92 | var _t = t / 0.5 93 | if _t < 1.0 { 94 | return 0.5 * _t * _t * _t * _t 95 | } 96 | _t -= 2.0 97 | return -0.5 * (_t * _t * _t * _t - 2.0) 98 | } 99 | 100 | //MARK: Quintic 101 | 102 | public static func easeInQuint(_ t: Double) -> Double { 103 | return t * t * t * t * t 104 | } 105 | 106 | public static func easeOutQuint(_ t: Double) -> Double { 107 | let _t = t - 1.0 108 | return _t * _t * _t * _t * _t + 1 109 | } 110 | 111 | public static func easeInOutQuint(_ t: Double) -> Double { 112 | var _t = t / 0.5 113 | if _t < 1.0 { 114 | return 0.5 * _t * _t * _t * _t * _t 115 | } 116 | _t -= 2.0 117 | return 0.5 * (_t * _t * _t * _t * _t + 2.0) 118 | } 119 | 120 | //MARK: Sinusoidal 121 | 122 | public static func easeInSine(_ t: Double) -> Double { 123 | return -cos(t * (Double.pi/2.0)) + 1.0 124 | } 125 | 126 | public static func easeOutSine(_ t: Double) -> Double { 127 | return sin(t * (Double.pi/2.0)) 128 | } 129 | 130 | public static func easeInOutSine(_ t: Double) -> Double { 131 | return -0.5 * (cos(Double.pi * t) - 1.0) 132 | } 133 | 134 | //MARK: Exponential 135 | 136 | public static func easeInExpo(_ t: Double) -> Double { 137 | return pow(2.0, 10.0 * (t - 1.0)) 138 | } 139 | 140 | public static func easeOutExpo(_ t: Double) -> Double { 141 | return (-pow(2.0, -10.0 * t) + 1.0) 142 | } 143 | 144 | public static func easeInOutExpo(_ t: Double) -> Double { 145 | var _t = t / 0.5 146 | if _t < 1.0 { 147 | return 0.5 * pow(2.0, 10.0 * (_t - 1.0)) 148 | } 149 | _t -= 1.0 150 | return 0.5 * (-pow(2.0, -10.0 * _t) + 2.0) 151 | } 152 | 153 | //MARK: Circular 154 | 155 | public static func easeInCirc(_ t: Double) -> Double { 156 | return -(sqrt(1.0 - t * t) - 1.0) 157 | } 158 | 159 | public static func easeOutCirc(_ t: Double) -> Double { 160 | let _t = t - 1.0 161 | return sqrt(1.0 - _t * _t) 162 | } 163 | 164 | public static func easeInOutCirc(_ t: Double) -> Double { 165 | var _t = t / 0.5 166 | if _t < 1.0 { 167 | return -0.5 * (sqrt(1.0 - _t * _t) - 1.0) 168 | } 169 | _t -= 2.0 170 | return 0.5 * (sqrt(1.0 - _t * _t) + 1.0) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /TweenController/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 5/26/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | extension UIView { 31 | public func twc_applyBackgroundColor(_ color: UIColor) { 32 | self.backgroundColor = color 33 | } 34 | 35 | public func twc_applyBounds(_ bounds: CGRect) { 36 | self.bounds = bounds 37 | } 38 | 39 | public func twc_applyFrame(_ frame: CGRect) { 40 | self.frame = frame 41 | } 42 | 43 | /// This function can be used as the action block of a tween operation where the `Tweenable` type is `CGRect`. 44 | /// The rect passed to the block is offset by the scroll view's `contentOffset`, and thus, appears to be 45 | /// unaffected by the scrolling of the view, and acting as a 'sliding window.' 46 | /// 47 | /// - parameter scrollView: The scroll view used to offset the rect 48 | /// - returns: A block which can be used as the action block for a tween operation. 49 | public func twc_slidingFrameAction(scrollView: UIScrollView) -> (_ frame: CGRect) -> () { 50 | return { [weak self, weak scrollView] frame in 51 | guard let scrollView = scrollView else { return } 52 | self?.frame = frame.offsetBy(dx: scrollView.contentOffset.x, dy: scrollView.contentOffset.y) 53 | } 54 | } 55 | 56 | public func twc_applyCenter(_ center: CGPoint) { 57 | self.center = center 58 | } 59 | 60 | public func twc_applyTransform(_ transform: CGAffineTransform) { 61 | self.transform = transform 62 | } 63 | 64 | public func twc_applyAlpha(_ alpha: CGFloat) { 65 | self.alpha = alpha 66 | } 67 | } 68 | 69 | extension UIScrollView { 70 | 71 | /// Used to obtain the current horizontal 'page' value for the scroll view. 72 | /// 73 | /// For example, if the scroll view has been swiped three pages to the right and is being dragged halfway to the next page, 74 | /// this value will be 3.5. 75 | public var twc_horizontalPageProgress: CGFloat { 76 | return contentOffset.x / bounds.width 77 | } 78 | 79 | /// Used to obtain the current vertical 'page' value for the scroll view. 80 | /// 81 | /// For example, if the scroll view has been swiped three pages down and is being dragged halfway to the next page, 82 | /// this value will be 3.5. 83 | public var twc_verticalPageProgress: CGFloat { 84 | return contentOffset.y / bounds.height 85 | } 86 | } 87 | 88 | extension CALayer { 89 | public func twc_applyTransform(_ transform: CATransform3D) { 90 | self.transform = transform 91 | } 92 | 93 | public func twc_applyAffineTransform(_ transform: CGAffineTransform) { 94 | self.setAffineTransform(transform) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /TweenController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /TweenController/KVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KVC.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 6/1/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | 30 | public protocol ObjectConvertible { 31 | func toObject() -> AnyObject 32 | } 33 | 34 | extension Int: ObjectConvertible { 35 | public func toObject() -> AnyObject { 36 | return self as AnyObject 37 | } 38 | } 39 | 40 | extension Float: ObjectConvertible { 41 | public func toObject() -> AnyObject { 42 | return self as AnyObject 43 | } 44 | } 45 | 46 | extension Double: ObjectConvertible { 47 | public func toObject() -> AnyObject { 48 | return self as AnyObject 49 | } 50 | } 51 | 52 | extension CGFloat: ObjectConvertible { 53 | public func toObject() -> AnyObject { 54 | return self as AnyObject 55 | } 56 | } 57 | 58 | extension TweenPromise where T: ObjectConvertible { 59 | 60 | /// Instead of using an action block with a tween operation, you can 61 | /// call this method to apply the value directly to an object using 62 | /// Key-Value-Coding. For example: 63 | /// 64 | /// controller.tween(from: 0.0, at: 0.0) 65 | /// .to(M_PI * 8.0, at: 1.0) 66 | /// .with(object: tweenView.layer, keyPath: "transform.rotation.z") 67 | /// 68 | /// - parameter object: The object to apply tweened values to. 69 | /// - parameter keyPath: The key-path on `object` used when applying tweened values. 70 | public func with(object: NSObject, keyPath: String) { 71 | with { [weak object] tweenable in 72 | object?.setValue(tweenable.toObject(), forKeyPath: keyPath) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /TweenController/StandardTweenables.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StandardTweenables.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 5/9/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | extension Double: Tweenable { 31 | public static func valueBetween(_ val1: Double, _ val2: Double, percent: Double) -> Double { 32 | return (val2 - val1) * percent + val1 33 | } 34 | } 35 | 36 | extension Float: Tweenable { 37 | public static func valueBetween(_ val1: Float, _ val2: Float, percent: Double) -> Float { 38 | return (val2 - val1) * Float(percent) + val1 39 | } 40 | } 41 | 42 | extension Int: Tweenable { 43 | public static func valueBetween(_ val1: Int, _ val2: Int, percent: Double) -> Int { 44 | return Int(round((Double(val2) - Double(val1)) * Double(percent) + Double(val1))) 45 | } 46 | } 47 | 48 | extension CGFloat: Tweenable { 49 | public static func valueBetween(_ val1: CGFloat, _ val2: CGFloat, percent: Double) -> CGFloat { 50 | return (val2 - val1) * CGFloat(percent) + val1 51 | } 52 | } 53 | 54 | extension CGPoint: Tweenable { 55 | public static func valueBetween(_ val1: CGPoint, _ val2: CGPoint, percent: Double) -> CGPoint { 56 | let x = CGFloat.valueBetween(val1.x, val2.x, percent: percent) 57 | let y = CGFloat.valueBetween(val1.y, val2.y, percent: percent) 58 | return CGPoint(x: x, y: y) 59 | } 60 | } 61 | 62 | extension CGSize: Tweenable { 63 | public static func valueBetween(_ val1: CGSize, _ val2: CGSize, percent: Double) -> CGSize { 64 | let w = CGFloat.valueBetween(val1.width, val2.width, percent: percent) 65 | let h = CGFloat.valueBetween(val1.height, val2.height, percent: percent) 66 | return CGSize(width: w, height: h) 67 | } 68 | } 69 | 70 | extension CGRect: Tweenable { 71 | public static func valueBetween(_ val1: CGRect, _ val2: CGRect, percent: Double) -> CGRect { 72 | let origin = CGPoint.valueBetween(val1.origin, val2.origin, percent: percent) 73 | let size = CGSize.valueBetween(val1.size, val2.size, percent: percent) 74 | return CGRect(origin: origin, size: size) 75 | } 76 | } 77 | 78 | extension UIColor: Tweenable { 79 | public static func valueBetween(_ val1: UIColor, _ val2: UIColor, percent: Double) -> Self { 80 | var r1: CGFloat = 0.0 81 | var g1: CGFloat = 0.0 82 | var b1: CGFloat = 0.0 83 | var a1: CGFloat = 0.0 84 | 85 | var r2: CGFloat = 0.0 86 | var g2: CGFloat = 0.0 87 | var b2: CGFloat = 0.0 88 | var a2: CGFloat = 0.0 89 | 90 | val1.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) 91 | val2.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) 92 | 93 | let r3 = CGFloat.valueBetween(r1, r2, percent: percent) 94 | let g3 = CGFloat.valueBetween(g1, g2, percent: percent) 95 | let b3 = CGFloat.valueBetween(b1, b2, percent: percent) 96 | let a3 = CGFloat.valueBetween(a1, a2, percent: percent) 97 | 98 | return self.init(red: r3, green: g3, blue: b3, alpha: a3) 99 | } 100 | } 101 | 102 | extension CGAffineTransform: Tweenable { 103 | public static func valueBetween(_ val1: CGAffineTransform, _ val2: CGAffineTransform, percent: Double) -> CGAffineTransform { 104 | let a = CGFloat.valueBetween(val1.a, val2.a, percent: percent) 105 | let b = CGFloat.valueBetween(val1.b, val2.b, percent: percent) 106 | let c = CGFloat.valueBetween(val1.c, val2.c, percent: percent) 107 | let d = CGFloat.valueBetween(val1.d, val2.d, percent: percent) 108 | let tx = CGFloat.valueBetween(val1.tx, val2.tx, percent: percent) 109 | let ty = CGFloat.valueBetween(val1.ty, val2.ty, percent: percent) 110 | return CGAffineTransform(a: a, b: b, c: c, d: d, tx: tx, ty: ty) 111 | } 112 | } 113 | 114 | extension CATransform3D: Tweenable { 115 | public static func valueBetween(_ val1: CATransform3D, _ val2: CATransform3D, percent: Double) -> CATransform3D { 116 | let m11 = CGFloat.valueBetween(val1.m11, val2.m11, percent: percent) 117 | let m12 = CGFloat.valueBetween(val1.m12, val2.m12, percent: percent) 118 | let m13 = CGFloat.valueBetween(val1.m13, val2.m13, percent: percent) 119 | let m14 = CGFloat.valueBetween(val1.m14, val2.m14, percent: percent) 120 | let m21 = CGFloat.valueBetween(val1.m21, val2.m21, percent: percent) 121 | let m22 = CGFloat.valueBetween(val1.m22, val2.m22, percent: percent) 122 | let m23 = CGFloat.valueBetween(val1.m23, val2.m23, percent: percent) 123 | let m24 = CGFloat.valueBetween(val1.m24, val2.m24, percent: percent) 124 | let m31 = CGFloat.valueBetween(val1.m31, val2.m31, percent: percent) 125 | let m32 = CGFloat.valueBetween(val1.m32, val2.m32, percent: percent) 126 | let m33 = CGFloat.valueBetween(val1.m33, val2.m33, percent: percent) 127 | let m34 = CGFloat.valueBetween(val1.m34, val2.m34, percent: percent) 128 | let m41 = CGFloat.valueBetween(val1.m41, val2.m41, percent: percent) 129 | let m42 = CGFloat.valueBetween(val1.m42, val2.m42, percent: percent) 130 | let m43 = CGFloat.valueBetween(val1.m43, val2.m43, percent: percent) 131 | let m44 = CGFloat.valueBetween(val1.m44, val2.m44, percent: percent) 132 | return CATransform3D(m11: m11, m12: m12, m13: m13, m14: m14, m21: m21, m22: m22, m23: m23, m24: m24, m31: m31, m32: m32, m33: m33, m34: m34, m41: m41, m42: m42, m43: m43, m44: m44) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /TweenController/TweenController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TweenController.h 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 5/9/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | #import 29 | 30 | //! Project version number for TweenController. 31 | FOUNDATION_EXPORT double TweenControllerVersionNumber; 32 | 33 | //! Project version string for TweenController. 34 | FOUNDATION_EXPORT const unsigned char TweenControllerVersionString[]; 35 | 36 | // In this header, you should import all the public headers of your framework using statements like #import 37 | 38 | 39 | -------------------------------------------------------------------------------- /TweenController/TweenController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweenController.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 5/9/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | public typealias TweenObserverBlock = (_ progress: Double) -> () 29 | 30 | /// `TweenController` is the entry point into the framework. 31 | /// 32 | /// let controller = TweenController() 33 | /// controller.tween(from: transformA, at: 0.0) 34 | /// .to(transformB, at: 0.5) 35 | /// .then(to: transformC, at: 1.0) 36 | /// .with(action: myView.layer.twc_applyAffineTransform) 37 | open class TweenController { 38 | 39 | /// The current progress of the controller. 40 | /// The range of progress is completely arbitrary. 41 | /// For example, you could use a percentage, i.e. 0.0 - 1.0, or the width of a scroll view in points. 42 | open private(set) var progress: Double = 0.0 43 | fileprivate var descriptors: [TweenIntervalType] = [] 44 | fileprivate var boundaries: [Boundary] = [] 45 | 46 | //MARK: Public 47 | 48 | public init() {} 49 | 50 | /// Used to begin describing a tween operation. 51 | /// 52 | /// - parameter from: The `Tweenable` value to begin tweening from, such as a UIColor. 53 | /// - parameter progress: The progress point at which the tween will begin. 54 | /// 55 | /// - returns: A TweenPromise, which is used to continue (and ultimately finish) describing a tween operation. 56 | open func tween(from: T, at progress: Double) -> TweenPromise where T: Tweenable { 57 | return TweenPromise(from: from, progress: progress, resolvedDescriptors: [], registration: self) 58 | } 59 | 60 | /// Used to observe when `progress` crosses a boundary moving forward. 61 | /// 62 | /// - parameter progress: The boundary marker progress. 63 | /// - parameter block: The block to execute with the boundary is crossed. 64 | open func observeForward(progress: Double, block: @escaping TweenObserverBlock) { 65 | boundaries.append(Boundary(progress: progress, block: block, direction: .forward)) 66 | } 67 | 68 | /// Used to observe when `progress` crosses a boundary moving backward. 69 | /// 70 | /// - parameter progress: The boundary marker progress. 71 | /// - parameter block: The block to execute with the boundary is crossed. 72 | open func observeBackward(progress: Double, block: @escaping TweenObserverBlock) { 73 | boundaries.append(Boundary(progress: progress, block: block, direction: .backward)) 74 | } 75 | 76 | /// Used to observe when `progress` crosses a boundary moving in either direction. 77 | /// 78 | /// - parameter progress: The boundary marker progress. 79 | /// - parameter block: The block to execute with the boundary is crossed. 80 | open func observeBoth(progress: Double, block: @escaping TweenObserverBlock) { 81 | boundaries.append(Boundary(progress: progress, block: block, direction: .both)) 82 | } 83 | 84 | /// Used to update the `progress` of the `TweenController` and perform any actions necessary. 85 | /// 86 | /// **Note:** You should not use this method to reset progress back to 0.0 as it will call boundary observer blocks. 87 | /// Instead, call `resetProgress()`. 88 | /// 89 | /// - parameter progress: The new progress value. 90 | open func update(progress: Double) { 91 | let lastProgress = self.progress 92 | self.progress = progress 93 | updateDescriptors(progress: progress) 94 | handleBoundaryCrossingIfNecessary(progress: progress, lastProgress: lastProgress) 95 | } 96 | 97 | /// Resets the `progress` of the `TweenController` back to 0.0 98 | open func resetProgress() { 99 | progress = 0.0 100 | updateDescriptors(progress: progress) 101 | } 102 | 103 | //MARK: Private 104 | 105 | private func updateDescriptors(progress: Double) { 106 | let filtered = descriptors.filter() { $0.contains(progress: progress) } 107 | filtered.forEach { $0.handleProgressUpdate(progress) } 108 | } 109 | 110 | private func handleBoundaryCrossingIfNecessary(progress: Double, lastProgress: Double) { 111 | guard progress != lastProgress else { return } 112 | let direction: Boundary.Direction = progress > lastProgress ? .forward : .backward 113 | 114 | var boundaries = self.boundaries.filter() { $0.direction.contains(direction) } 115 | boundaries = boundaries.filter() { boundary in 116 | if direction == .forward { 117 | return progress >= boundary.progress && lastProgress < boundary.progress 118 | } else { 119 | return lastProgress > boundary.progress && progress <= boundary.progress 120 | } 121 | } 122 | 123 | boundaries.forEach { $0.block(progress) } 124 | } 125 | } 126 | 127 | extension TweenController: DescriptorRegistration { 128 | 129 | func register(descriptor: TweenDescriptor) where T: Tweenable { 130 | descriptors.append(descriptor) 131 | } 132 | 133 | func observe(boundary: Boundary) { 134 | boundaries.append(boundary) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /TweenController/TweenDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweenDescriptor.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 5/9/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | public protocol TweenIntervalType { 29 | var interval: Range { get } 30 | var easingFunction: Easing.Function { get } 31 | var isIntervalClosed: Bool { get set } 32 | func contains(progress: Double) -> Bool 33 | func handleProgressUpdate(_ progress: Double) 34 | } 35 | 36 | extension TweenIntervalType { 37 | public func contains(progress: Double) -> Bool { 38 | return interval.contains(progress) || (isIntervalClosed && interval.upperBound == progress) 39 | } 40 | } 41 | 42 | open class TweenDescriptor: TweenIntervalType { 43 | 44 | open let fromValue: T 45 | open let toValue: T 46 | open let interval: Range 47 | open let easingFunction: Easing.Function 48 | open var isIntervalClosed: Bool = false 49 | open var updateBlock: ((T) -> ())? 50 | 51 | init(fromValue: T, toValue: T, interval: Range, easingFunction: @escaping Easing.Function) { 52 | self.fromValue = fromValue 53 | self.toValue = toValue 54 | self.interval = interval 55 | self.easingFunction = easingFunction 56 | } 57 | 58 | open func handleProgressUpdate(_ progress: Double) { 59 | guard let block = updateBlock, contains(progress: progress) else { return } 60 | let percent = percentFromProgress(progress) 61 | let easedPercent = easingFunction(percent) 62 | block(T.valueBetween(fromValue, toValue, percent: easedPercent)) 63 | } 64 | 65 | //MARK: Private 66 | 67 | private func percentFromProgress(_ progress: Double) -> Double { 68 | return (progress - interval.lowerBound) / (interval.upperBound - interval.lowerBound) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /TweenController/TweenPromise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweenPromise.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 5/11/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | protocol DescriptorRegistration: class { 29 | func register(descriptor: TweenDescriptor) where T: Tweenable 30 | func observe(boundary: Boundary) 31 | } 32 | 33 | /// `TweenPromise` is used to finish describing a tween operation after `tween(from: at:)` has been called on `TweenController`. 34 | public struct TweenPromise { 35 | let from: T 36 | let progress: Double 37 | let resolvedDescriptors: [TweenDescriptor] 38 | weak var registration: DescriptorRegistration? 39 | 40 | /// Used to mark a 'key frame' of a tween operation. 41 | /// This will register a descriptor with the `TweenController` with the `to` value as the upper bound. 42 | /// 43 | /// - parameter to: The `Tweenable` value to end a tweening operation on, such as a UIColor. 44 | /// - parameter progress: The progress point at which the tween will end. 45 | /// - parameter easing: A function that can be used to apply an easing effect to the tween operation. Many easing functions are defined in `Easing.swift`. 46 | /// 47 | /// - returns: Another instance of `TweenPromise` used to register an additional tween operation. 48 | public func to(_ to: T, at progress: Double, withEasing easing: @escaping Easing.Function = Easing.linear) -> TweenPromise { 49 | precondition(progress != self.progress, "'to' progress must be different than 'from' progress") 50 | 51 | let descriptor = TweenDescriptor(fromValue: from, toValue: to, interval: self.progress.. TweenPromise { 66 | return self.to(to, at: progress, withEasing: easing) 67 | } 68 | 69 | /// Used to keep a value constant across two progress points. 70 | /// Functionally equivalent to calling `to(...)` and passing the previous 'key frame' value as the `to` value. 71 | /// 72 | /// - parameter until: The progress point at which the hold will end. 73 | /// 74 | /// - returns: Another instance of `TweenPromise` used to register an additional tween operation. 75 | public func thenHold(until: Double) -> TweenPromise { 76 | return self.to(from, at: until) 77 | } 78 | 79 | /// Used to assign an action block to be executed in response to changes in any of the tween operations created by prior calls. 80 | /// 81 | /// - parameter action: The block which is executed when changes occur. 82 | public func with(action: @escaping (T) -> ()) { 83 | addEdgeObservers() 84 | resolvedDescriptors.last?.isIntervalClosed = true 85 | resolvedDescriptors.forEach() { $0.updateBlock = action } 86 | } 87 | 88 | //MARK: Private 89 | 90 | /// When progress crosses from inside to outside of a tween action interval, 91 | /// a final terminating action callback occurs using the edge progress value that was crossed over. 92 | private func addEdgeObservers() { 93 | guard let first = resolvedDescriptors.first, let last = resolvedDescriptors.last else { return } 94 | let firstProgress = first.interval.lowerBound 95 | let lastProgress = last.interval.upperBound 96 | 97 | // if we're exiting the interval, update the progress to the edges 98 | registration?.observe(boundary: Boundary(progress: firstProgress, block: { [weak first] progress in 99 | guard let first = first else { return } 100 | if !first.contains(progress: progress) || firstProgress == progress { 101 | first.handleProgressUpdate(firstProgress) 102 | } 103 | }, direction: .both)) 104 | registration?.observe(boundary: Boundary(progress: lastProgress, block: { [weak last] progress in 105 | guard let last = last else { return } 106 | if !last.contains(progress: progress) || progress == lastProgress { 107 | last.handleProgressUpdate(lastProgress) 108 | } 109 | }, direction: .both)) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /TweenController/Tweenable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tweenable.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 5/9/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | /// Types that conform to `Tweenable` can be used by a `TweenController` in order to perform tween operations. 29 | /// Most commonly, you'll use a type defined in Swift or iOS: 30 | /// * Double 31 | /// * Float 32 | /// * Int 33 | /// * CGFloat 34 | /// * CGPoint 35 | /// * CGSize 36 | /// * CGRect 37 | /// * UIColor 38 | /// * CGAffineTransform 39 | /// * CATransform3D 40 | /// 41 | /// You can also make your own types conform to `Tweenable`. 42 | public protocol Tweenable { 43 | 44 | /// Returns a value that is between `val1` and `val2`, interpolated by a `percent`. 45 | /// 46 | /// - parameter val1: The lower bound of the interpolation. 47 | /// - parameter val2: The upper bound of the interpolation. 48 | /// - parameter percent: The percentage between 0.0 - 1.0 used to interpolate the value. 49 | /// 50 | /// - returns: A new value of the same type, interpolated by `percent`. 51 | static func valueBetween(_ val1: Self, _ val2: Self, percent: Double) -> Self 52 | } 53 | -------------------------------------------------------------------------------- /TweenControllerDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TweenControllerDemo 4 | // 5 | // Created by Dalton Claybrook on 5/9/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | @UIApplicationMain 31 | class AppDelegate: UIResponder, UIApplicationDelegate { 32 | 33 | var window: UIWindow? 34 | 35 | 36 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 37 | // Override point for customization after application launch. 38 | return true 39 | } 40 | 41 | func applicationWillResignActive(_ application: UIApplication) { 42 | // 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. 43 | // 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. 44 | } 45 | 46 | func applicationDidEnterBackground(_ application: UIApplication) { 47 | // 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. 48 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 49 | } 50 | 51 | func applicationWillEnterForeground(_ application: UIApplication) { 52 | // 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. 53 | } 54 | 55 | func applicationDidBecomeActive(_ application: UIApplication) { 56 | // 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. 57 | } 58 | 59 | func applicationWillTerminate(_ application: UIApplication) { 60 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 61 | } 62 | 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.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" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/bottom_copy_s2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "addPhotoVideoRecipients_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/bottom_copy_s2.imageset/addPhotoVideoRecipients_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/bottom_copy_s2.imageset/addPhotoVideoRecipients_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/card_copy_s2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YADAbars_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/card_copy_s2.imageset/YADAbars_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/card_copy_s2.imageset/YADAbars_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/chick_face.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "girlHead_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/chick_face.imageset/girlHead_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/chick_face.imageset/girlHead_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/sunrise.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Sun_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/sunrise.imageset/Sun_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/sunrise.imageset/Sun_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/top_copy_s2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "PinYadaIsLikeOther_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/top_copy_s2.imageset/PinYadaIsLikeOther_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen2/top_copy_s2.imageset/PinYadaIsLikeOther_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/birthday_cake.imageset/Cake_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/birthday_cake.imageset/Cake_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/birthday_cake.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Cake_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/bottom_copy_s3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "pinMessageToTime_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/bottom_copy_s3.imageset/pinMessageToTime_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/bottom_copy_s3.imageset/pinMessageToTime_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/card_copy_s3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "messageSusan_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/card_copy_s3.imageset/messageSusan_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/card_copy_s3.imageset/messageSusan_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/hill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tinyHill_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/hill.imageset/tinyHill_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/hill.imageset/tinyHill_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/hill_mark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "smallMark_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/hill_mark.imageset/smallMark_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/hill_mark.imageset/smallMark_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/stars.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "stars_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/stars.imageset/stars_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/stars.imageset/stars_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/top_copy_s3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "exceptyoucan_6plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/top_copy_s3.imageset/exceptyoucan_6plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen3/top_copy_s3.imageset/exceptyoucan_6plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/bottom_copy_s4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "pinMessageToPlace_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/bottom_copy_s4.imageset/pinMessageToPlace_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/bottom_copy_s4.imageset/pinMessageToPlace_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/card_copy_s4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "messageJohn_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/card_copy_s4.imageset/messageJohn_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/card_copy_s4.imageset/messageJohn_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/dude_face.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "boyHead_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/dude_face.imageset/boyHead_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/dude_face.imageset/boyHead_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/eiffel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "EiffelTower_6Plus-1.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/eiffel.imageset/EiffelTower_6Plus-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/eiffel.imageset/EiffelTower_6Plus-1.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/eiffel_tower.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "eiffelTowerBckrnd_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/eiffel_tower.imageset/eiffelTowerBckrnd_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/eiffel_tower.imageset/eiffelTowerBckrnd_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/top_copy_s4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "orYouCan_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/top_copy_s4.imageset/orYouCan_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen4/top_copy_s4.imageset/orYouCan_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/bottom_copy_s5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "yourMessageIsReceived_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/bottom_copy_s5.imageset/yourMessageIsReceived_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/bottom_copy_s5.imageset/yourMessageIsReceived_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/card_detail.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "fullCardCenterPoint_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/card_detail.imageset/fullCardCenterPoint_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/card_detail.imageset/fullCardCenterPoint_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/chick_arm_down.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "girlArmDown_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/chick_arm_down.imageset/girlArmDown_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/chick_arm_down.imageset/girlArmDown_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/chick_arm_up.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "girlArmUp_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/chick_arm_up.imageset/girlArmUp_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/chick_arm_up.imageset/girlArmUp_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/top_copy_s5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "soThat_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/top_copy_s5.imageset/soThat_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/Screen5/top_copy_s5.imageset/soThat_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/left_arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "swipeArrowNText_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/left_arrow.imageset/swipeArrowNText_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/left_arrow.imageset/swipeArrowNText_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/logo_tagline.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LogoTag_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/logo_tagline.imageset/LogoTag_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/logo_tagline.imageset/LogoTag_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/mark.imageset/BigMark_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/mark.imageset/BigMark_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/mark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "BigMark_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/mark_shadow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "shadow_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/mark_shadow.imageset/shadow_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/mark_shadow.imageset/shadow_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/message_bubble.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "messageBubble_6Plus.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TweenControllerDemo/Assets.xcassets/Tutorial/message_bubble.imageset/messageBubble_6Plus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/TweenControllerDemo/Assets.xcassets/Tutorial/message_bubble.imageset/messageBubble_6Plus.pdf -------------------------------------------------------------------------------- /TweenControllerDemo/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 | -------------------------------------------------------------------------------- /TweenControllerDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Texta-Bold 13 | Texta-Bold 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 106 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /TweenControllerDemo/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 | UIStatusBarStyle 34 | UIStatusBarStyleLightContent 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /TweenControllerDemo/StartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StartViewController.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 5/26/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | import TweenController 30 | 31 | class StartViewController: UIViewController, TutorialViewController { 32 | 33 | @IBOutlet var containerView: UIView! 34 | @IBOutlet var buttonsContainerView: UIView! 35 | @IBOutlet var pageControl: UIPageControl! 36 | 37 | private var hasAppeared = false 38 | private var tweenController: TweenController! 39 | private var scrollView: UIScrollView! 40 | 41 | //MARK: Superclass 42 | 43 | override func viewDidAppear(_ animated: Bool) { 44 | super.viewDidAppear(animated) 45 | if !hasAppeared { 46 | hasAppeared = true 47 | 48 | let (tc, scrollView) = TutorialBuilder.buildWithContainerViewController(self) 49 | self.tweenController = tc 50 | self.scrollView = scrollView 51 | self.scrollView.delegate = self 52 | } 53 | } 54 | 55 | //MARK: Private 56 | 57 | private func updatePageControl() { 58 | pageControl.currentPage = Int(round(scrollView.contentOffset.x / containerView.frame.width)) 59 | } 60 | } 61 | 62 | extension StartViewController: UIScrollViewDelegate { 63 | 64 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 65 | tweenController.update(progress: Double(scrollView.twc_horizontalPageProgress)) 66 | } 67 | 68 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 69 | updatePageControl() 70 | } 71 | 72 | func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 73 | if !decelerate { 74 | updatePageControl() 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /TweenControllerDemo/TutorialBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TutorialBuilder.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 5/26/16. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | import TweenController 30 | 31 | protocol TutorialViewController: class { 32 | var containerView: UIView! { get } 33 | var buttonsContainerView: UIView! { get } 34 | var pageControl: UIPageControl! { get } 35 | } 36 | 37 | struct TutorialBuilder { 38 | 39 | private static let starsSize = CGSize(width: 326, height: 462) 40 | private static let baselineScreenWidth: CGFloat = 414 41 | private static let baselineCardViewHeight: CGFloat = 496 42 | private static let cardYOffset: CGFloat = 186.0 43 | private static let cardYTranslation: CGFloat = -28.0 44 | 45 | //MARK: Public 46 | 47 | static func buildWithContainerViewController(_ viewController: TutorialViewController) -> (TweenController, UIScrollView) { 48 | let tweenController = TweenController() 49 | let scrollView = layoutViewsWithVC(viewController) 50 | describeBottomControlsWithVC(viewController, tweenController: tweenController, scrollView: scrollView) 51 | observeEndOfScrollView(viewController, tweenController: tweenController, scrollView: scrollView) 52 | describeBackgroundWithVC(viewController, tweenController: tweenController, scrollView: scrollView) 53 | describeTextWithVC(viewController, tweenController: tweenController, scrollView: scrollView) 54 | describeCardTextWithVC(viewController, tweenController: tweenController, scrollView: scrollView) 55 | describeCardImageWithVC(viewController, tweenController: tweenController, scrollView: scrollView) 56 | describeCardFacesWithVC(viewController, tweenController: tweenController, scrollView: scrollView) 57 | describePinHillWithVC(viewController, tweenController: tweenController, scrollView: scrollView) 58 | describeAnimationWithVC(viewController, tweenController: tweenController, scrollView: scrollView) 59 | 60 | scrollView.bringSubview(toFront: viewController.buttonsContainerView) 61 | scrollView.bringSubview(toFront: viewController.pageControl) 62 | 63 | return (tweenController, scrollView) 64 | } 65 | 66 | //MARK: Private 67 | //MARK: Initial Layout 68 | 69 | private static func layoutViewsWithVC(_ vc: TutorialViewController) -> UIScrollView { 70 | let scrollView = UIScrollView(frame: vc.containerView.bounds) 71 | guard let superview = vc.containerView.superview else { return scrollView } 72 | 73 | layoutButtonsAndPageControlWithVC(vc, scrollView: scrollView) 74 | 75 | superview.addSubview(scrollView) 76 | return scrollView 77 | } 78 | 79 | private static func layoutButtonsAndPageControlWithVC(_ vc: TutorialViewController, scrollView: UIScrollView) { 80 | let snapshot = vc.containerView.snapshotView(afterScreenUpdates: true) 81 | snapshot?.backgroundColor = UIColor.clear 82 | 83 | scrollView.isPagingEnabled = true 84 | scrollView.showsHorizontalScrollIndicator = false 85 | scrollView.showsVerticalScrollIndicator = false 86 | 87 | let viewSize = vc.containerView.bounds.size 88 | scrollView.contentSize = CGSize(width: viewSize.width * 6.0, height: viewSize.height) 89 | vc.pageControl.numberOfPages = 5 90 | 91 | vc.containerView.translatesAutoresizingMaskIntoConstraints = true 92 | scrollView.addSubview(vc.containerView) 93 | 94 | let xOffset = scrollView.contentSize.width - viewSize.width 95 | snapshot?.frame = vc.containerView.frame.offsetBy(dx: xOffset, dy: 0.0) 96 | scrollView.addSubview(snapshot!) 97 | 98 | let buttonsFrame = vc.buttonsContainerView.convert(vc.buttonsContainerView.bounds, to: scrollView) 99 | let pageControlFrame = vc.pageControl.convert(vc.pageControl.bounds, to: scrollView) 100 | 101 | vc.buttonsContainerView.translatesAutoresizingMaskIntoConstraints = true 102 | vc.pageControl.translatesAutoresizingMaskIntoConstraints = true 103 | scrollView.addSubview(vc.buttonsContainerView) 104 | scrollView.addSubview(vc.pageControl) 105 | vc.buttonsContainerView.frame = buttonsFrame 106 | vc.pageControl.frame = pageControlFrame 107 | } 108 | 109 | //MARK: Tutorial Actions 110 | 111 | private static func describeBottomControlsWithVC(_ vc: TutorialViewController, tweenController: TweenController, scrollView: UIScrollView) { 112 | let viewportSize = vc.containerView.frame.size 113 | let startingButtonFrame = vc.buttonsContainerView.frame 114 | let startingPageControlFrame = vc.pageControl.frame 115 | tweenController.tween(from: startingButtonFrame, at: -1.0) 116 | .to(startingButtonFrame, at: 0.0) 117 | .then(to: CGRect(x: startingButtonFrame.minX, y: viewportSize.height, width: startingButtonFrame.width, height: startingButtonFrame.height), at: 1.0) 118 | .thenHold(until: 4.0) 119 | .then(to: startingButtonFrame, at: 5.0) 120 | .with(action: vc.buttonsContainerView.twc_slidingFrameAction(scrollView: scrollView)) 121 | 122 | let nextPageControlFrame = CGRect(x: startingPageControlFrame.minX, y: startingPageControlFrame.minY + startingButtonFrame.height, width: startingPageControlFrame.width, height: startingPageControlFrame.height) 123 | tweenController.tween(from: startingPageControlFrame, at: 0.0) 124 | .to(nextPageControlFrame, at: 1.0) 125 | .thenHold(until: 4.0) 126 | .then(to: startingPageControlFrame, at: 5.0) 127 | .with(action: vc.pageControl.twc_slidingFrameAction(scrollView: scrollView)) 128 | } 129 | 130 | private static func describeBackgroundWithVC(_ vc: TutorialViewController, tweenController: TweenController, scrollView: UIScrollView) { 131 | describeStarGradientWithVC(vc, tweenController: tweenController, scrollView: scrollView) 132 | describeStarsWithVC(vc, tweenController: tweenController, scrollView: scrollView) 133 | describeEiffelTowerWithVC(vc, tweenController: tweenController, scrollView: scrollView) 134 | } 135 | 136 | private static func describeStarGradientWithVC(_ vc: TutorialViewController, tweenController: TweenController, scrollView: UIScrollView) { 137 | let viewportFrame = CGRect(origin: CGPoint.zero, size: vc.containerView.frame.size) 138 | let topColor = UIColor(red: 155.0/255.0, green: 39.0/255.0, blue: 153.0/255.0, alpha: 1.0) 139 | let bottomColor = UIColor(red: 38.0/255.0, green: 198.0/255.0, blue: 218.0/255.0, alpha: 1.0) 140 | let gradientLayer = CAGradientLayer() 141 | let gradientView = UIView(frame: viewportFrame.offsetBy(dx: viewportFrame.width, dy: 0.0)) 142 | 143 | gradientLayer.colors = [bottomColor.cgColor, topColor.cgColor] 144 | gradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0) 145 | gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0) 146 | gradientLayer.frame = viewportFrame 147 | gradientView.backgroundColor = UIColor.clear 148 | gradientView.layer.addSublayer(gradientLayer) 149 | gradientView.alpha = 0.0 150 | scrollView.insertSubview(gradientView, belowSubview: vc.pageControl) 151 | 152 | tweenController.tween(from: viewportFrame, at: 1.0) 153 | .thenHold(until: 3.0) 154 | .with(action: gradientView.twc_slidingFrameAction(scrollView: scrollView)) 155 | 156 | tweenController.tween(from: gradientView.alpha, at: 1.0) 157 | .to(1.0, at: 2.0) 158 | .then(to: 0.0, at: 3.0) 159 | .with(action: gradientView.twc_applyAlpha) 160 | } 161 | 162 | private static func describeStarsWithVC(_ vc: TutorialViewController, tweenController: TweenController, scrollView: UIScrollView) { 163 | let viewportSize = vc.containerView.frame.size 164 | let xOffset = (viewportSize.width-starsSize.width)/2.0 165 | let starsFrame = CGRect(x: xOffset, y: 0.0, width: starsSize.width, height: starsSize.height) 166 | let starsImageView = UIImageView(image: UIImage(named: "stars")) 167 | 168 | starsImageView.frame = starsFrame.offsetBy(dx: viewportSize.width, dy: 0.0) 169 | starsImageView.alpha = 0.0 170 | starsImageView.contentMode = .scaleToFill 171 | scrollView.insertSubview(starsImageView, belowSubview: vc.pageControl) 172 | 173 | tweenController.tween(from: starsFrame, at: 1.0) 174 | .thenHold(until: 3.0) 175 | .with(action: starsImageView.twc_slidingFrameAction(scrollView: scrollView)) 176 | 177 | tweenController.tween(from: starsImageView.alpha, at: 1.0) 178 | .to(1.0, at: 2.0) 179 | .then(to: 0.0, at: 3.0) 180 | .with(action: starsImageView.twc_applyAlpha) 181 | } 182 | 183 | private static func describeEiffelTowerWithVC(_ vc: TutorialViewController, tweenController: TweenController, scrollView: UIScrollView) { 184 | let viewportFrame = CGRect(origin: CGPoint.zero, size: vc.containerView.frame.size) 185 | let imageView = UIImageView(image: UIImage(named: "eiffel_tower")) 186 | imageView.frame = viewportFrame.offsetBy(dx: viewportFrame.width * 2.0, dy: 0.0) 187 | imageView.alpha = 0.0 188 | scrollView.addSubview(imageView) 189 | 190 | tweenController.tween(from: viewportFrame, at: 2.0) 191 | .thenHold(until: 4.0) 192 | .with(action: imageView.twc_slidingFrameAction(scrollView: scrollView)) 193 | 194 | tweenController.tween(from: imageView.alpha, at: 2.0) 195 | .to(1.0, at: 3.0) 196 | .thenHold(until: 4.0) 197 | .then(to: 0.0, at: 5.0) 198 | .with(action: imageView.twc_applyAlpha) 199 | } 200 | 201 | private static func describeTextWithVC(_ vc: TutorialViewController, tweenController: TweenController, scrollView: UIScrollView) { 202 | let viewportFrame = CGRect(origin: CGPoint.zero, size: vc.containerView.frame.size) 203 | let multiplier = viewportFrame.width / baselineScreenWidth 204 | let topYOffset = 50 * multiplier 205 | let bottomYOffset = 80 * multiplier 206 | 207 | let topView1 = UIImageView(image: UIImage(named: "top_copy_s2")) 208 | let topView2 = UIImageView(image: UIImage(named: "top_copy_s3")) 209 | let topView3 = UIImageView(image: UIImage(named: "top_copy_s4")) 210 | let topView4 = UIImageView(image: UIImage(named: "top_copy_s5")) 211 | 212 | let bottomView1 = UIImageView(image: UIImage(named: "bottom_copy_s2")) 213 | let bottomView2 = UIImageView(image: UIImage(named: "bottom_copy_s3")) 214 | let bottomView3 = UIImageView(image: UIImage(named: "bottom_copy_s4")) 215 | let bottomView4 = UIImageView(image: UIImage(named: "bottom_copy_s5")) 216 | 217 | let bottomViews = [bottomView1, bottomView2, bottomView3, bottomView4] 218 | for i in 0..= 1.0 { 54 | self.displayLink.invalidate() 55 | self.displayLink = nil 56 | } 57 | } 58 | 59 | private func tweenTransform() { 60 | controller.tween(from: 0.0, at: 0.0) 61 | .to(Double.pi * 8.0, at: 1.0, withEasing: Easing.easeInOutQuint) 62 | .with(object: tweenView.layer, keyPath: "transform.rotation.z") 63 | 64 | controller.tween(from: 1.0, at: 0.0) 65 | .to(2.0, at: 0.5) 66 | .then(to: 1.0, at: 1.0) 67 | .with { [weak self] scale in 68 | self?.tweenView.layer.setValue(scale, forKeyPath: "transform.scale.x") 69 | self?.tweenView.layer.setValue(scale, forKeyPath: "transform.scale.y") 70 | } 71 | } 72 | 73 | private func tweenColor() { 74 | let colorA = UIColor.green 75 | let colorB = UIColor.blue 76 | let colorC = UIColor.red 77 | tweenView.backgroundColor = colorA 78 | 79 | controller.tween(from: colorA, at: 0.0) 80 | .to(colorB, at: 0.5) 81 | .then(to: colorC, at: 1.0) 82 | .with(action: tweenView.twc_applyBackgroundColor) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /TweenControllerTests/EasingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasingTests.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 1/11/17. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import XCTest 29 | import TweenController 30 | 31 | class EasingTests: XCTestCase { 32 | 33 | let testValues = [0.25, 0.33, 0.5, 0.66, 0.75] 34 | 35 | //MARK: Tests 36 | 37 | //MARK: Linear 38 | 39 | func testLinear() { 40 | let expected = [0.25, 0.33000000000000002, 0.5, 0.66000000000000003, 0.75] 41 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.linear)) 42 | } 43 | 44 | //MARK: Quad 45 | 46 | func testEaseInQuad() { 47 | let expected = [0.0625, 0.10890000000000001, 0.25, 0.43560000000000004, 0.5625] 48 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInQuad)) 49 | } 50 | 51 | func testEaseOutQuad() { 52 | let expected = [0.4375, 0.55110000000000003, 0.75, 0.88439999999999996, 0.9375] 53 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeOutQuad)) 54 | } 55 | 56 | func testEaseInOutQuad() { 57 | let expected = [0.125, 0.21780000000000002, 0.5, 0.76880000000000004, 0.875] 58 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInOutQuad)) 59 | } 60 | 61 | //MARK: Cubic 62 | 63 | func testEaseInCubic() { 64 | let expected = [0.015625, 0.035937000000000004, 0.125, 0.28749600000000003, 0.421875] 65 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInCubic)) 66 | } 67 | 68 | func testEaseOutCubic() { 69 | let expected = [0.578125, 0.69923700000000011, 0.875, 0.96069599999999999, 0.984375] 70 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeOutCubic)) 71 | } 72 | 73 | func testEaseInOutCubic() { 74 | let expected = [0.0625, 0.14374800000000001, 0.5, 0.84278399999999998, 0.9375] 75 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInOutCubic)) 76 | } 77 | 78 | //MARK: Quart 79 | 80 | func testEaseInQuart() { 81 | let expected = [0.00390625, 0.011859210000000002, 0.0625, 0.18974736000000003, 0.31640625] 82 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInQuart)) 83 | } 84 | 85 | func testEaseOutQuart() { 86 | let expected = [-1.31640625, -1.2015112099999998, -1.0625, -1.01336336, -1.00390625] 87 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeOutQuart)) 88 | } 89 | 90 | func testEaseInOutQuart() { 91 | let expected = [0.03125, 0.094873680000000016, 0.5, 0.89309312000000007, 0.96875] 92 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInOutQuart)) 93 | } 94 | 95 | //MARK: Quint 96 | 97 | func testEaseInQuint() { 98 | let expected = [0.0009765625, 0.0039135393000000011, 0.03125, 0.12523325760000004, 0.2373046875] 99 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInQuint)) 100 | } 101 | 102 | func testEaseOutQuint() { 103 | let expected = [0.7626953125, 0.86498748930000002, 0.96875, 0.99545645760000001, 0.9990234375] 104 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeOutQuint)) 105 | } 106 | 107 | func testEaseInOutQuint() { 108 | let expected = [0.015625, 0.062616628800000018, 0.5, 0.92730332160000006, 0.984375] 109 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInOutQuint)) 110 | } 111 | 112 | //MARK: Sine 113 | 114 | func testEaseInSine() { 115 | let expected = [0.076120467488713262, 0.1313684855618088, 0.29289321881345243, 0.49095858424962868, 0.61731656763491016] 116 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInSine)) 117 | } 118 | 119 | func testEaseOutSine() { 120 | let expected = [0.38268343236508978, 0.4954586684324076, 0.70710678118654746, 0.86074202700394364, 0.92387953251128674] 121 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeOutSine)) 122 | } 123 | 124 | func testEaseInOutSine() { 125 | let expected = [0.14644660940672621, 0.24547929212481434, 0.49999999999999994, 0.74087683705085772, 0.85355339059327373] 126 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInOutSine)) 127 | } 128 | 129 | //MARK: Expo 130 | 131 | func testEaseInExpo() { 132 | let expected = [0.0055242717280199029, 0.0096183157292571639, 0.03125, 0.094732285406899916, 0.17677669529663689] 133 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInExpo)) 134 | } 135 | 136 | func testEaseOutExpo() { 137 | let expected = [0.82322330470336313, 0.89846845045547052, 0.96875, 0.98969134444708673, 0.99447572827198005] 138 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeOutExpo)) 139 | } 140 | 141 | func testEaseInOutExpo() { 142 | let expected = [0.015625, 0.047366142703449958, 0.5, 0.94559058979399224, 0.984375] 143 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInOutExpo)) 144 | } 145 | 146 | //MARK: Circ 147 | 148 | func testEaseInCirc() { 149 | let expected = [0.031754163448145745, 0.056019067989188653, 0.1339745962155614, 0.248734401160282, 0.33856217223385232] 150 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInCirc)) 151 | } 152 | 153 | func testEaseOutCirc() { 154 | let expected = [0.66143782776614768, 0.74236109811869866, 0.8660254037844386, 0.9404254356406998, 0.96824583655185426] 155 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeOutCirc)) 156 | } 157 | 158 | func testEaseInOutCirc() { 159 | let expected = [0.066987298107780702, 0.124367200580141, 0.5, 0.8666060555964672, 0.9330127018922193] 160 | XCTAssertTrue(verify(expectedResults: expected, of: Easing.easeInOutCirc)) 161 | } 162 | 163 | //MARK: Private 164 | 165 | func verify(expectedResults:[Double], of easingFunc: @escaping (Double)->(Double)) -> Bool { 166 | guard expectedResults.count == testValues.count else { return false } 167 | return zip(testValues, expectedResults).reduce(true) { $0 && (easingFunc($1.0) == $1.1) } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /TweenControllerTests/ExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionsTests.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 1/10/17. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import XCTest 29 | import TweenController 30 | 31 | class ExtensionsTests: XCTestCase { 32 | 33 | let tweenController = TweenController() 34 | 35 | //MARK: Tests 36 | 37 | func testApplyBackgroundColor() { 38 | let view = UIView() 39 | tweenController.tween(from: UIColor.green, at: 0.0) 40 | .to(.blue, at: 1.0) 41 | .with(action: view.twc_applyBackgroundColor) 42 | tweenController.update(progress: 0.5) 43 | 44 | let final = UIColor(red: 0.0, green: 0.5, blue: 0.5, alpha: 1.0) 45 | XCTAssertEqual(view.backgroundColor!, final) 46 | } 47 | 48 | func testApplyBounds() { 49 | let view = UIView() 50 | tweenController.tween(from: CGRect.zero, at: 0.0) 51 | .to(CGRect(x: 0.0, y: 0.0, width: 10, height: 10), at: 1.0) 52 | .with(action: view.twc_applyBounds) 53 | tweenController.update(progress: 0.5) 54 | 55 | XCTAssertEqual(view.bounds, CGRect(x: 0, y: 0, width: 5, height: 5)) 56 | } 57 | 58 | func testApplyFrame() { 59 | let view = UIView() 60 | tweenController.tween(from: CGRect.zero, at: 0.0) 61 | .to(CGRect(x: 0.0, y: 0.0, width: 10, height: 10), at: 1.0) 62 | .with(action: view.twc_applyFrame) 63 | tweenController.update(progress: 0.5) 64 | 65 | XCTAssertEqual(view.frame, CGRect(x: 0, y: 0, width: 5, height: 5)) 66 | } 67 | 68 | func testScrollViewSlidingFrame() { 69 | let scrollView = UIScrollView() 70 | let view = UIView() 71 | scrollView.frame = CGRect(x: 0, y: 0, width: 100, height: 100) 72 | scrollView.contentSize = CGSize(width: 500, height: 100) //5 pages 73 | view.frame = CGRect(x: 10, y: 10, width: 10, height: 10) 74 | scrollView.addSubview(view) 75 | 76 | tweenController.tween(from: view.frame, at: 0.0) 77 | .to(CGRect(x: 20, y: 20, width: 10, height: 10), at: 1.0) 78 | .with(action: view.twc_slidingFrameAction(scrollView: scrollView)) 79 | 80 | scrollView.contentOffset = CGPoint(x: 200, y: 0) 81 | tweenController.update(progress: 0.5) 82 | 83 | //200 (offset) + 15 (tween) 84 | XCTAssertEqual(view.frame, CGRect(x: 215, y: 15, width: 10, height: 10)) 85 | } 86 | 87 | func testApplyCenter() { 88 | let view = UIView() 89 | view.frame = CGRect(x: 0, y: 0, width: 10, height: 10) 90 | 91 | tweenController.tween(from: view.center, at: 0.0) 92 | .to(CGPoint(x: 15, y: 15), at: 1.0) 93 | .with(action: view.twc_applyCenter) 94 | 95 | tweenController.update(progress: 0.5) 96 | XCTAssertEqual(view.center, CGPoint(x: 10, y: 10)) 97 | } 98 | 99 | func testApplyTransform() { 100 | let view = UIView() 101 | view.frame = CGRect(x: 10, y: 10, width: 10, height: 10) 102 | view.transform = CGAffineTransform.identity 103 | 104 | tweenController.tween(from: view.transform, at: 0.0) 105 | .to(CGAffineTransform(scaleX: 2.0, y: 2.0), at: 1.0) 106 | .with(action: view.twc_applyTransform) 107 | 108 | tweenController.update(progress: 0.5) 109 | XCTAssertEqual(view.transform, CGAffineTransform(scaleX: 1.5, y: 1.5)) 110 | } 111 | 112 | func testApplyAlpha() { 113 | let view = UIView() 114 | view.alpha = 1.0 115 | tweenController.tween(from: 1.0, at: 0.0) 116 | .to(0.0, at: 1.0) 117 | .with(action: view.twc_applyAlpha) 118 | 119 | tweenController.update(progress: 0.25) 120 | XCTAssertEqual(view.alpha, 0.75) 121 | } 122 | 123 | func testHorizontalPageProgress() { 124 | let scrollView = UIScrollView() 125 | scrollView.frame = CGRect(x: 0, y: 0, width: 100, height: 100) 126 | scrollView.contentSize = CGSize(width: 500, height: 100) //5 pages 127 | scrollView.contentOffset = CGPoint(x: 200, y: 0) 128 | 129 | XCTAssertEqual(scrollView.twc_horizontalPageProgress, 2.0) 130 | } 131 | 132 | func testVerticalPageProgress() { 133 | let scrollView = UIScrollView() 134 | scrollView.frame = CGRect(x: 0, y: 0, width: 100, height: 100) 135 | scrollView.contentSize = CGSize(width: 100, height: 500) //5 pages 136 | scrollView.contentOffset = CGPoint(x: 0, y: 200) 137 | 138 | XCTAssertEqual(scrollView.twc_verticalPageProgress, 2.0) 139 | } 140 | 141 | func testApplyLayerTransform() { 142 | let layer = CALayer() 143 | layer.frame = CGRect(x: 10, y: 10, width: 10, height: 10) 144 | layer.transform = CATransform3DIdentity 145 | 146 | tweenController.tween(from: CATransform3DIdentity, at: 0.0) 147 | .to(CATransform3DMakeScale(2.0, 2.0, 2.0), at: 1.0) 148 | .with(action: layer.twc_applyTransform) 149 | 150 | tweenController.update(progress: 0.5) 151 | XCTAssertTrue(CATransform3DEqualToTransform(layer.transform, CATransform3DMakeScale(1.5, 1.5, 1.5))) 152 | } 153 | 154 | func testApplyLayerAffineTransform() { 155 | let layer = CALayer() 156 | layer.frame = CGRect(x: 10, y: 10, width: 10, height: 10) 157 | layer.setAffineTransform(.identity) 158 | 159 | tweenController.tween(from: layer.affineTransform(), at: 0.0) 160 | .to(CGAffineTransform(scaleX: 2.0, y: 2.0), at: 1.0) 161 | .with(action: layer.twc_applyAffineTransform) 162 | 163 | tweenController.update(progress: 0.5) 164 | XCTAssertEqual(layer.affineTransform(), CGAffineTransform(scaleX: 1.5, y: 1.5)) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /TweenControllerTests/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TweenControllerTests/KeyPathTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyPathTests.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 1/10/17. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import XCTest 29 | import TweenController 30 | 31 | class KeyPathTests: XCTestCase { 32 | 33 | let tweenController = TweenController() 34 | 35 | //MARK: Tests 36 | 37 | func testInt() { 38 | let listener = KeypathListener() 39 | tweenController.tween(from: 0, at: 0.0) 40 | .to(4, at: 1.0) 41 | .with(object: listener, keyPath: listener.keyPath) 42 | tweenController.update(progress: 0.75) 43 | 44 | XCTAssertEqual(listener.values.count, 1) 45 | XCTAssertEqual(listener.values[0], 3) 46 | } 47 | 48 | func testFloat() { 49 | let listener = KeypathListener() 50 | tweenController.tween(from: Float(0.0), at: 0.0) 51 | .to(1.0, at: 1.0) 52 | .with(object: listener, keyPath: listener.keyPath) 53 | tweenController.update(progress: 0.375) 54 | 55 | XCTAssertEqual(listener.values.count, 1) 56 | XCTAssertEqual(listener.values[0], 0.375) 57 | } 58 | 59 | func testDouble() { 60 | let listener = KeypathListener() 61 | tweenController.tween(from: 0.0, at: 0.0) 62 | .to(1.0, at: 1.0) 63 | .with(object: listener, keyPath: listener.keyPath) 64 | tweenController.update(progress: 0.375) 65 | 66 | XCTAssertEqual(listener.values.count, 1) 67 | XCTAssertEqual(listener.values[0], 0.375) 68 | } 69 | 70 | func testCGFloat() { 71 | let listener = KeypathListener() 72 | tweenController.tween(from: CGFloat(0.0), at: 0.0) 73 | .to(1.0, at: 1.0) 74 | .with(object: listener, keyPath: listener.keyPath) 75 | tweenController.update(progress: 0.375) 76 | 77 | XCTAssertEqual(listener.values.count, 1) 78 | XCTAssertEqual(listener.values[0], 0.375) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /TweenControllerTests/KeypathListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeypathListener.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 1/9/17. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import TweenController 30 | 31 | class KeypathListener: NSObject { 32 | 33 | let keyPath = "com.claybrooksoftware.tweencontroller.keyPath" 34 | private(set) var values = [T]() 35 | 36 | override func setValue(_ value: Any?, forKeyPath keyPath: String) { 37 | guard keyPath == self.keyPath, let tweenable = value as? T else { 38 | super.setValue(value, forKeyPath: keyPath) 39 | return 40 | } 41 | values.append(tweenable) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /TweenControllerTests/StandardTweenablesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StandardTweenablesTests.swift 3 | // TweenController 4 | // 5 | // Created by Dalton Claybrook on 1/9/17. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import XCTest 29 | import UIKit 30 | import TweenController 31 | 32 | class StandardTweenablesTests: XCTestCase { 33 | 34 | func testDouble() { 35 | let val = Double.valueBetween(0.0, 0.5, percent: 0.5) 36 | XCTAssertEqual(val, 0.25) 37 | } 38 | 39 | func testFloat() { 40 | let val = Float.valueBetween(0.0, 10.0, percent: 0.25) 41 | XCTAssertEqual(val, 2.5) 42 | } 43 | 44 | func testInt() { 45 | let val = Int.valueBetween(0, 8, percent: 0.5) 46 | XCTAssertEqual(val, 4) 47 | } 48 | 49 | func testCGFloat() { 50 | let val = CGFloat.valueBetween(1.0, 3.0, percent: 0.25) 51 | XCTAssertEqual(val, 1.5) 52 | } 53 | 54 | func testCGPoint() { 55 | let val = CGPoint.valueBetween(CGPoint(x: 0.0, y: 0.0) , CGPoint(x: 1.0, y: 2.0), percent: 0.25) 56 | XCTAssertEqual(val, CGPoint(x: 0.25, y: 0.5)) 57 | } 58 | 59 | func testCGSize() { 60 | let val = CGSize.valueBetween(CGSize(width: 0.0, height: 0.0) , CGSize(width: 1.0, height: 2.0), percent: 0.25) 61 | XCTAssertEqual(val, CGSize(width: 0.25, height: 0.5)) 62 | } 63 | 64 | func testCGRect() { 65 | let rect1 = CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0) 66 | let rect2 = CGRect(x: 10.0, y: 20.0, width: 10, height: 10) 67 | let finalRect = CGRect(x: 5.0, y: 10.0, width: 5.0, height: 5.0) 68 | let val = CGRect.valueBetween(rect1, rect2, percent: 0.5) 69 | XCTAssertEqual(val, finalRect) 70 | } 71 | 72 | func testUIColor() { 73 | let val = UIColor.valueBetween(.green, .blue, percent: 0.5) 74 | let final = UIColor(red: 0.0, green: 0.5, blue: 0.5, alpha: 1.0) 75 | XCTAssertEqual(val, final) 76 | } 77 | 78 | func testCGAffineTransform() { 79 | let tran1 = CGAffineTransform.identity 80 | let tran2 = CGAffineTransform(scaleX: 2.0, y: 2.0) 81 | let finalTran = CGAffineTransform(scaleX: 1.5, y: 1.5) 82 | let val = CGAffineTransform.valueBetween(tran1, tran2, percent: 0.5) 83 | XCTAssertEqual(val, finalTran) 84 | } 85 | 86 | func testCATransform3D() { 87 | let tran1 = CATransform3DIdentity 88 | let tran2 = CATransform3DMakeScale(2.0, 3.0, 4.0) 89 | let finalTran = CATransform3DMakeScale(1.5, 2.0, 2.5) 90 | let val = CATransform3D.valueBetween(tran1, tran2, percent: 0.5) 91 | XCTAssertTrue(CATransform3DEqualToTransform(val, finalTran)) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /TweenControllerTests/TweenControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweenControllerTests.swift 3 | // TweenControllerTests 4 | // 5 | // Created by Dalton Claybrook on 1/9/17. 6 | // 7 | // Copyright (c) 2017 Dalton Claybrook 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import XCTest 29 | import TweenController 30 | 31 | class TweenControllerTests: XCTestCase { 32 | 33 | var tweenController = TweenController() 34 | 35 | //MARK: Tests 36 | 37 | func testTweenProgress_To() { 38 | var finalVal = Double.nan 39 | tweenController.tween(from: 0.0, at: 0.0) 40 | .to(1.0, at: 1.0) 41 | .with { val in 42 | finalVal = val 43 | } 44 | tweenController.update(progress: 0.5) 45 | XCTAssertEqual(0.5, finalVal) 46 | } 47 | 48 | func testTweenProgress_Then() { 49 | var vals = [Double]() 50 | tweenController.tween(from: 0.0, at: 0.0) 51 | .to(0.25, at: 0.5) 52 | .then(to: 1.0, at: 1.0) 53 | .with { val in 54 | vals.append(val) 55 | } 56 | tweenController.update(progress: 0.25) 57 | tweenController.update(progress: 0.75) 58 | 59 | XCTAssertEqual(vals.count, 2) 60 | XCTAssertEqual(vals[0], 0.125) 61 | XCTAssertEqual(vals[1], 0.625) 62 | } 63 | 64 | func testTweenProgress_ThenHold() { 65 | var vals = [Double]() 66 | tweenController.tween(from: 0.0, at: 0.0) 67 | .to(0.5, at: 0.25) 68 | .thenHold(until: 0.75) 69 | .then(to: 1.0, at: 1.0) 70 | .with { val in 71 | vals.append(val) 72 | } 73 | tweenController.update(progress: 0.375) 74 | tweenController.update(progress: 0.875) 75 | 76 | XCTAssertEqual(vals[0], 0.5) 77 | XCTAssertEqual(vals[1], 0.75) 78 | } 79 | 80 | func testForwardBoundary() { 81 | var finalProgress = Double.nan 82 | var count = 0 83 | tweenController.observeForward(progress: 0.5) { (progress) in 84 | finalProgress = progress 85 | count += 1 86 | } 87 | tweenController.update(progress: 0.25) 88 | tweenController.update(progress: 0.75) 89 | tweenController.update(progress: 0.25) 90 | 91 | XCTAssertEqual(finalProgress, 0.75) 92 | XCTAssertEqual(count, 1) 93 | } 94 | 95 | func testBackwardBoundary() { 96 | var finalProgress = Double.nan 97 | var count = 0 98 | tweenController.observeBackward(progress: 0.5) { (progress) in 99 | finalProgress = progress 100 | count += 1 101 | } 102 | tweenController.update(progress: 0.25) 103 | tweenController.update(progress: 0.75) 104 | tweenController.update(progress: 0.25) 105 | 106 | XCTAssertEqual(finalProgress, 0.25) 107 | XCTAssertEqual(count, 1) 108 | } 109 | 110 | func testBothBoundaries() { 111 | var finalProgress = Double.nan 112 | var count = 0 113 | tweenController.observeBoth(progress: 0.5) { (progress) in 114 | finalProgress = progress 115 | count += 1 116 | } 117 | tweenController.update(progress: 0.25) 118 | tweenController.update(progress: 0.75) 119 | tweenController.update(progress: 0.25) 120 | 121 | XCTAssertEqual(finalProgress, 0.25) 122 | XCTAssertEqual(count, 2) 123 | } 124 | 125 | func testResetDoesNotFireBoundary() { 126 | var fired = false 127 | tweenController.observeBackward(progress: 0.5) { (progress) in 128 | fired = true 129 | } 130 | tweenController.update(progress: 0.25) 131 | tweenController.update(progress: 0.75) 132 | tweenController.resetProgress() 133 | 134 | XCTAssertEqual(tweenController.progress, 0.0) 135 | XCTAssertFalse(fired) 136 | } 137 | 138 | func testForwardEdgeObserver() { 139 | var vals = [Double]() 140 | tweenController.tween(from: 0.25, at: 0.25) 141 | .to(0.75, at: 0.75) 142 | .with { val in 143 | vals.append(val) 144 | } 145 | tweenController.update(progress: 0.5) 146 | tweenController.update(progress: 0.85) 147 | 148 | XCTAssertEqual(vals.count, 2) 149 | XCTAssertEqual(vals.last!, 0.75) 150 | } 151 | 152 | func testBackwardEdgeObserver() { 153 | var vals = [Double]() 154 | tweenController.tween(from: 0.25, at: 0.25) 155 | .to(0.75, at: 0.75) 156 | .with { val in 157 | vals.append(val) 158 | } 159 | tweenController.update(progress: 0.5) 160 | tweenController.update(progress: 0.15) 161 | 162 | XCTAssertEqual(vals.count, 2) 163 | XCTAssertEqual(vals.last!, 0.25) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daltonclaybrook/tween-controller/a025156c441fb139cdab32858783cabda6ec1d70/example.gif --------------------------------------------------------------------------------