├── .gitignore ├── .swift-version ├── ChainableAnimations.podspec ├── Examples ├── JHChainableAnimations iOS Example │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── JHChainableAnimations iOS Swift Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── JHChainableAnimations tvOS Example │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ ├── App Icon & Top Shelf Image.brandassets │ │ │ ├── App Icon - Large.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── App Icon - Small.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Top Shelf Image Wide.imageset │ │ │ │ └── Contents.json │ │ │ └── Top Shelf Image.imageset │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.h │ ├── ViewController.m │ └── main.m └── JHChainableAnimations tvOS Swift Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── App Icon & Top Shelf Image.brandassets │ │ ├── App Icon - Large.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── App Icon - Small.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Top Shelf Image Wide.imageset │ │ │ └── Contents.json │ │ └── Top Shelf Image.imageset │ │ │ └── Contents.json │ ├── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json │ ├── Base.lproj │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── Frameworks-Swift ├── ChainableAnimations.h └── Info.plist ├── Frameworks-iOS ├── Info.plist └── JHChainableAnimations.h ├── Gifs ├── JHChainableAnimationsExample1.gif ├── JHChainableAnimationsExample2.gif └── JHChainableAnimationsExample3.gif ├── JHChainableAnimations.podspec ├── JHChainableAnimations.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── JHChainableAnimations-iOS-Swift.xcscheme │ ├── JHChainableAnimations-iOS.xcscheme │ ├── JHChainableAnimations-tvOS-Swift.xcscheme │ └── JHChainableAnimations-tvOS.xcscheme ├── JHChainableAnimations ├── ChainableAnimator.swift ├── JHAnimationChainLink.h ├── JHAnimationChainLink.m ├── JHChainableAnimator.h ├── JHChainableAnimator.m ├── JHChainableBlocks.h ├── JHKeyframeAnimation.h ├── JHKeyframeAnimation.m ├── JHKeyframeAnimationFunctions.c ├── JHKeyframeAnimationFunctions.h └── UIView+ChainableAnimator.swift ├── JHChainableAnimationsTests ├── Info.plist └── JHChainableAnimationsTests.m ├── LICENSE ├── README.md └── img ├── JHChainableAnimationsAnchors.png ├── JHChainableAnimationsEasing.png ├── JHChainableAnimationsEffects.png ├── JHChainableAnimationsExample1.png ├── JHChainableAnimationsExample2.png ├── JHChainableAnimationsExample3.png └── logo.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | #Pods/ 27 | 28 | Carthage/ 29 | 30 | #Random Shit 31 | *.DS_Store 32 | 33 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /ChainableAnimations.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'ChainableAnimations' 3 | s.version = '3.0.1' 4 | s.author = { 'Jeff Hurray' => 'jhurray33@gmail.com' } 5 | s.homepage = 'https://github.com/jhurray/JHChainableAnimations' 6 | s.summary = 'Easy to read and write chainable Animations in Swift' 7 | s.license = 'MIT' 8 | s.source = { :git => 'https://github.com/jhurray/JHChainableAnimations.git', :tag => s.version.to_s } 9 | s.source_files = 'JHChainableAnimations/*{.h,.c,.m,.swift}', 'Frameworks-Swift/ChainableAnimations.h' 10 | s.platform = :ios 11 | s.ios.deployment_target = '9.0' 12 | s.requires_arc = true 13 | end 14 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // JHChainableAnimations iOS Example 4 | // 5 | // Created by Jeff Hurray on 12/22/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Example/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // JHChainableAnimations iOS Example 4 | // 5 | // Created by Jeff Hurray on 12/22/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Example/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 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Example/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Example/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // JHChainableAnimations iOS Example 4 | // 5 | // Created by Jeff Hurray on 12/22/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Example/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // JHChainableAnimations iOS Example 4 | // 5 | // Created by Jeff Hurray on 12/22/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | @import JHChainableAnimations; 11 | 12 | 13 | @interface ViewController () 14 | 15 | @property (nonatomic, weak) UIView *myView; 16 | @property (nonatomic, weak) UIButton *pauseButton; 17 | @property (nonatomic, strong) JHChainableAnimator *animator; 18 | 19 | @end 20 | 21 | @implementation ViewController 22 | 23 | - (void)viewDidLoad 24 | { 25 | [super viewDidLoad]; 26 | UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(100, 150, 50, 50)]; 27 | myView.backgroundColor = [UIColor blueColor]; 28 | [self.view addSubview:myView]; 29 | self.myView = myView; 30 | 31 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 32 | button.frame = CGRectMake(0, self.view.bounds.size.height-50.0, self.view.bounds.size.width, 50.0); 33 | button.backgroundColor = [UIColor blueColor]; 34 | [button setTitle:@"Action!" forState:UIControlStateNormal]; 35 | [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 36 | [button addTarget:self action:@selector(animateView:) forControlEvents:UIControlEventTouchUpInside]; 37 | [self.view addSubview:button]; 38 | 39 | UIButton *pauseButton = [UIButton buttonWithType:UIButtonTypeCustom]; 40 | pauseButton.frame = CGRectMake(16, 32, 100, 50); 41 | pauseButton.backgroundColor = [UIColor blueColor]; 42 | [pauseButton setTitle:@"Pause" forState:UIControlStateNormal]; 43 | [pauseButton addTarget:self action:@selector(pause:) forControlEvents:UIControlEventTouchUpInside]; 44 | [self.view addSubview:pauseButton]; 45 | self.pauseButton = pauseButton; 46 | 47 | JHChainableAnimator *animator = [[JHChainableAnimator alloc] initWithView:self.myView]; 48 | animator.moveWidth(50).bounce.makeBackground(UIColor.purpleColor).easeIn.anchorTopLeft. 49 | thenAfter(0.8).rotateZ(95).easeBack.wait(0.2). 50 | thenAfter(0.5).moveY(300).easeIn.makeOpacity(0.0).animate(0.4); 51 | 52 | 53 | animator.moveX(30).thenAfter(1).makeScale(2.0).spring.animate(1); 54 | 55 | self.animator = animator; 56 | 57 | } 58 | 59 | 60 | - (void)pause:(UIButton *)sender 61 | { 62 | if (!self.animator.isPaused) { 63 | [sender setTitle:@"Resume" forState:UIControlStateNormal]; 64 | [self.animator pause]; 65 | } 66 | else { 67 | [sender setTitle:@"Pause" forState:UIControlStateNormal]; 68 | [self.animator resume]; 69 | } 70 | } 71 | 72 | - (void)animateView:(UIButton *)sender 73 | { 74 | 75 | sender.userInteractionEnabled = NO; 76 | 77 | JHChainableAnimator *buttonAnimator = [[JHChainableAnimator alloc] initWithView:sender]; 78 | 79 | __weak JHChainableAnimator *weakAnimator = self.animator; 80 | __weak UIView *weakView = self.myView; 81 | self.animator.completionBlock = ^{ 82 | weakView.layer.transform = CATransform3DIdentity; 83 | weakView.frame = CGRectMake(100, 150, 50, 50); 84 | weakAnimator.transformIdentity.makeOpacity(1.0).makeBackground([UIColor blueColor]).animate(1.0); 85 | 86 | buttonAnimator.moveY(-50).easeInOutExpo.animateWithCompletion(1.1, ^{ 87 | sender.userInteractionEnabled = YES; 88 | }); 89 | }; 90 | 91 | buttonAnimator.moveY(50).easeInOutExpo.animate(0.5); 92 | 93 | UIColor *purple = [UIColor purpleColor]; 94 | self.animator.moveWidth(30).bounce.makeBackground(purple).easeIn.anchorTopLeft. 95 | repeat(0.5, 3).rotateZ(95).easeBack.wait(0.2). 96 | thenAfter(0.5).moveY(300).easeIn.makeOpacity(0.0).animate(0.4); 97 | 98 | 99 | /* OTHER COOL EFFECTS TO TRY: Animate on a bezier path */ 100 | // UIBezierPath *path = [UIBezierPath bezierPath]; 101 | // [path moveToPoint:self.myView.center]; 102 | // [path addCurveToPoint:CGPointMake(250, 200) controlPoint1:CGPointMake(150, 100) controlPoint2:CGPointMake(200, 250)]; 103 | // [path addLineToPoint:CGPointMake(25, 400)]; 104 | // [path addLineToPoint:CGPointMake(300, 500)]; 105 | // 106 | // self.animator.moveOnPath(path).easeIn.rotate(360).bounce.animate(2.0); 107 | 108 | /* OTHER COOL EFFECTS TO TRY: Bounce around 4 corners */ 109 | // self.animator.makeOrigin(0, 0).bounce. 110 | // thenAfter(1.0).makeY(self.view.bounds.size.height-50).bounce. 111 | // thenAfter(1.0).makeX(self.view.bounds.size.width-50).bounce. 112 | // thenAfter(1.0).makeY(0).bounce.animate(0.5); 113 | 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Example/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // JHChainableAnimations iOS Example 4 | // 5 | // Created by Jeff Hurray on 12/22/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Swift Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // JHChainableAnimations iOS Swift Example 4 | // 5 | // Created by Jeff Hurray on 1/22/17. 6 | // Copyright © 2017 jhurray. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Swift Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Swift Example/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 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Swift Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Swift Example/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations iOS Swift Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // JHChainableAnimations iOS Swift Example 4 | // 5 | // Created by Jeff Hurray on 1/22/17. 6 | // Copyright © 2017 jhurray. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ChainableAnimations 11 | 12 | class ViewController: UIViewController { 13 | 14 | let myView = UIView(frame: CGRect(x: 100, y: 150, width: 50, height: 50)) 15 | let button: UIButton = UIButton(type: .custom) 16 | let pauseButton: UIButton = UIButton(type: .custom) 17 | var animator: ChainableAnimator? 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | myView.backgroundColor = .blue 23 | view.addSubview(myView) 24 | 25 | button.backgroundColor = .blue 26 | button.setTitle("Action!", for: .normal) 27 | button.setTitleColor(.white, for: .normal) 28 | button.frame = CGRect(x: 0, y: view.bounds.height-50, width: view.bounds.width, height: 50) 29 | button.addTarget(self, action: #selector(animateView), for: .touchUpInside) 30 | view.addSubview(button) 31 | 32 | pauseButton.backgroundColor = .blue 33 | pauseButton.setTitle("Pause", for: .normal) 34 | pauseButton.setTitleColor(.white, for: .normal) 35 | pauseButton.frame = CGRect(x: 16, y: 32, width: 100, height: 50) 36 | pauseButton.addTarget(self, action: #selector(pauseSelected), for: .touchUpInside) 37 | view.addSubview(pauseButton) 38 | 39 | animator = ChainableAnimator(view: myView) 40 | } 41 | 42 | @objc func pauseSelected() { 43 | guard let animator = animator else { 44 | return 45 | } 46 | if !animator.isPaused { 47 | pauseButton.setTitle("Resume", for: .normal) 48 | animator.pause() 49 | } 50 | else { 51 | pauseButton.setTitle("Pause", for: .normal) 52 | animator.resume() 53 | } 54 | } 55 | 56 | @objc func animateView() { 57 | 58 | guard let animator = animator else { 59 | return 60 | } 61 | 62 | button.isUserInteractionEnabled = false 63 | let buttonAnimator = ChainableAnimator(view: button) 64 | 65 | animator.completion = { [unowned self] in 66 | self.myView.layer.transform = CATransform3DIdentity 67 | self.myView.frame = CGRect(x: 100, y: 150, width: 50, height: 50) 68 | self.animator?.transformIdentity.make(alpha: 1).make(backgroundColor: .blue).animate(t: 1.0) 69 | buttonAnimator.move(y: -50).easeInOutExpo.animate(t: 1.1, completion: { 70 | self.button.isUserInteractionEnabled = true 71 | }) 72 | } 73 | 74 | buttonAnimator.move(y: 50).easeInOutExpo.animate(t: 0.5) 75 | 76 | animator.move(width: 30).bounce.make(backgroundColor: .purple).easeIn.anchor(.topLeft) 77 | .repeat(t: 0.5, count: 3).rotateZ(angle: 95).easeBack.wait(t: 0.2) 78 | .thenAfter(t: 0.5).move(y: 300).easeIn.make(alpha: 0.0).animate(t: 0.4); 79 | 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // JHChainableAnimations tvOS Example 4 | // 5 | // Created by Jeff Hurray on 12/26/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // JHChainableAnimations tvOS Example 4 | // 5 | // Created by Jeff Hurray on 12/26/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // 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. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - Large.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon - Small.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "2320x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image Wide.imageset", 19 | "role" : "top-shelf-image-wide" 20 | }, 21 | { 22 | "size" : "1920x720", 23 | "idiom" : "tv", 24 | "filename" : "Top Shelf Image.imageset", 25 | "role" : "top-shelf-image" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "9.0", 8 | "scale" : "1x" 9 | } 10 | ], 11 | "info" : { 12 | "version" : 1, 13 | "author" : "xcode" 14 | } 15 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIMainStoryboardFile 24 | Main 25 | UIRequiredDeviceCapabilities 26 | 27 | arm64 28 | 29 | UIUserInterfaceStyle 30 | Automatic 31 | 32 | 33 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // JHChainableAnimations tvOS Example 4 | // 5 | // Created by Jeff Hurray on 12/26/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // JHChainableAnimations tvOS Example 4 | // 5 | // Created by Jeff Hurray on 12/26/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import 11 | 12 | 13 | @interface ViewController () 14 | 15 | @property (nonatomic, weak) UIView *myView; 16 | @property (nonatomic, weak) UIButton *pauseButton; 17 | @property (nonatomic, strong) JHChainableAnimator *animator; 18 | 19 | @end 20 | 21 | @implementation ViewController 22 | 23 | - (void)viewDidLoad 24 | { 25 | [super viewDidLoad]; 26 | UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(100, 150, 50, 50)]; 27 | myView.backgroundColor = [UIColor blueColor]; 28 | [self.view addSubview:myView]; 29 | self.myView = myView; 30 | 31 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 32 | button.frame = CGRectMake(0, self.view.bounds.size.height-50.0, self.view.bounds.size.width, 50.0); 33 | button.backgroundColor = [UIColor blueColor]; 34 | [button setTitle:@"Action!" forState:UIControlStateNormal]; 35 | [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 36 | [button addTarget:self action:@selector(animateView:) forControlEvents:UIControlEventPrimaryActionTriggered]; 37 | [self.view addSubview:button]; 38 | 39 | UIButton *pauseButton = [UIButton buttonWithType:UIButtonTypeCustom]; 40 | pauseButton.frame = CGRectMake(16, 32, 200, 50); 41 | pauseButton.backgroundColor = [UIColor blueColor]; 42 | [pauseButton setTitle:@"Pause" forState:UIControlStateNormal]; 43 | [pauseButton addTarget:self action:@selector(pause:) forControlEvents:UIControlEventPrimaryActionTriggered]; 44 | [self.view addSubview:pauseButton]; 45 | self.pauseButton = pauseButton; 46 | 47 | JHChainableAnimator *animator = [[JHChainableAnimator alloc] initWithView:self.myView]; 48 | self.animator = animator; 49 | } 50 | 51 | 52 | - (void)pause:(UIButton *)sender 53 | { 54 | if (!self.animator.isPaused) { 55 | [sender setTitle:@"Resume" forState:UIControlStateNormal]; 56 | [self.animator pause]; 57 | } 58 | else { 59 | [sender setTitle:@"Pause" forState:UIControlStateNormal]; 60 | [self.animator resume]; 61 | } 62 | } 63 | 64 | - (void)animateView:(UIButton *)sender 65 | { 66 | 67 | sender.userInteractionEnabled = NO; 68 | 69 | JHChainableAnimator *buttonAnimator = [[JHChainableAnimator alloc] initWithView:sender]; 70 | 71 | __weak JHChainableAnimator *weakAnimator = self.animator; 72 | __weak UIView *weakView = self.myView; 73 | self.animator.completionBlock = ^{ 74 | weakView.layer.transform = CATransform3DIdentity; 75 | weakView.frame = CGRectMake(100, 150, 50, 50); 76 | weakAnimator.transformIdentity.makeOpacity(1.0).makeBackground([UIColor blueColor]).animate(1.0); 77 | 78 | buttonAnimator.moveY(-50).easeInOutExpo.animateWithCompletion(1.1, ^{ 79 | sender.userInteractionEnabled = YES; 80 | }); 81 | }; 82 | 83 | UIColor *purple = [UIColor purpleColor]; 84 | self.animator.moveWidth(30).bounce.makeBackground(purple).easeIn.anchorTopLeft. 85 | repeat(0.5, 5).rotateZ(95).easeBack.wait(0.2). 86 | thenAfter(0.5).moveY(300).easeIn.makeOpacity(0.0).animate(0.4); 87 | 88 | buttonAnimator.moveY(50).easeInOutExpo.animate(0.5); 89 | 90 | 91 | /* OTHER COOL SHIT TO TRY: Animate on a bezier path */ 92 | // UIBezierPath *path = [UIBezierPath bezierPath]; 93 | // [path moveToPoint:self.myView.center]; 94 | // [path addCurveToPoint:CGPointMake(250, 200) controlPoint1:CGPointMake(150, 100) controlPoint2:CGPointMake(200, 250)]; 95 | // [path addLineToPoint:CGPointMake(25, 400)]; 96 | // [path addLineToPoint:CGPointMake(300, 500)]; 97 | // 98 | // self.myView.moveOnPath(path).easeIn.rotate(360).bounce.animate(2.0); 99 | 100 | /* OTHER COOL SHIT TO TRY: Bounce around 4 corners */ 101 | // self.myView.makeOrigin(0, 0).bounce. 102 | // thenAfter(0.5).makeY(self.view.bounds.size.height-100).bounce. 103 | // thenAfter(0.5).makeX(self.view.bounds.size.width-50).bounce. 104 | // thenAfter(0.5).makeY(0).bounce.animate(0.5); 105 | 106 | } 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Example/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // JHChainableAnimations tvOS Example 4 | // 5 | // Created by Jeff Hurray on 12/26/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // JHChainableAnimations tvOS Swift Example 4 | // 5 | // Created by Jeff Hurray on 1/22/17. 6 | // Copyright © 2017 jhurray. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - Large.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon - Small.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "2320x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image Wide.imageset", 19 | "role" : "top-shelf-image-wide" 20 | }, 21 | { 22 | "size" : "1920x720", 23 | "idiom" : "tv", 24 | "filename" : "Top Shelf Image.imageset", 25 | "role" : "top-shelf-image" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "9.0", 8 | "scale" : "1x" 9 | } 10 | ], 11 | "info" : { 12 | "version" : 1, 13 | "author" : "xcode" 14 | } 15 | } -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIMainStoryboardFile 24 | Main 25 | UIRequiredDeviceCapabilities 26 | 27 | arm64 28 | 29 | UIUserInterfaceStyle 30 | Automatic 31 | 32 | 33 | -------------------------------------------------------------------------------- /Examples/JHChainableAnimations tvOS Swift Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // JHChainableAnimations tvOS Swift Example 4 | // 5 | // Created by Jeff Hurray on 1/22/17. 6 | // Copyright © 2017 jhurray. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ChainableAnimations 11 | 12 | class ViewController: UIViewController { 13 | 14 | let myView = UIView(frame: CGRect(x: 100, y: 150, width: 50, height: 50)) 15 | let button: UIButton = UIButton(type: .custom) 16 | let pauseButton: UIButton = UIButton(type: .custom) 17 | var animator: ChainableAnimator? 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | myView.backgroundColor = .blue 23 | view.addSubview(myView) 24 | 25 | button.backgroundColor = .blue 26 | button.setTitle("Action!", for: .normal) 27 | button.setTitleColor(.white, for: .normal) 28 | button.frame = CGRect(x: 0, y: view.bounds.height-50, width: view.bounds.width, height: 50) 29 | button.addTarget(self, action: #selector(animateView), for: .touchUpInside) 30 | view.addSubview(button) 31 | 32 | pauseButton.backgroundColor = .blue 33 | pauseButton.setTitle("Pause", for: .normal) 34 | pauseButton.setTitleColor(.white, for: .normal) 35 | pauseButton.frame = CGRect(x: 16, y: 32, width: 100, height: 50) 36 | pauseButton.addTarget(self, action: #selector(pauseSelected), for: .touchUpInside) 37 | view.addSubview(pauseButton) 38 | 39 | animator = ChainableAnimator(view: myView) 40 | } 41 | 42 | func pauseSelected() { 43 | guard let animator = animator else { 44 | return 45 | } 46 | if !animator.isPaused { 47 | pauseButton.setTitle("Resume", for: .normal) 48 | animator.pause() 49 | } 50 | else { 51 | pauseButton.setTitle("Pause", for: .normal) 52 | animator.resume() 53 | } 54 | } 55 | 56 | func animateView() { 57 | 58 | guard let animator = animator else { 59 | return 60 | } 61 | 62 | button.isUserInteractionEnabled = false 63 | let buttonAnimator = ChainableAnimator(view: button) 64 | 65 | animator.completion = { [unowned self] in 66 | self.myView.layer.transform = CATransform3DIdentity 67 | self.myView.frame = CGRect(x: 100, y: 150, width: 50, height: 50) 68 | self.animator?.transformIdentity.make(alpha: 1).make(backgroundColor: .blue).animate(t: 1.0) 69 | buttonAnimator.move(y: -50).easeInOutExpo.animate(t: 1.1, completion: { 70 | self.button.isUserInteractionEnabled = true 71 | }) 72 | } 73 | 74 | buttonAnimator.move(y: 50).easeInOutExpo.animate(t: 0.5) 75 | 76 | animator.move(width: 30).bounce.make(backgroundColor: .purple).easeIn.anchor(.topLeft) 77 | .repeat(t: 0.5, count: 3).rotateZ(angle: 95).easeBack.wait(t: 0.2) 78 | .thenAfter(t: 0.5).move(y: 300).easeIn.make(alpha: 0.0).animate(t: 0.4); 79 | 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Frameworks-Swift/ChainableAnimations.h: -------------------------------------------------------------------------------- 1 | // 2 | // JHChainableAnimations-iOS-Swift.h 3 | // JHChainableAnimations-iOS-Swift 4 | // 5 | // Created by Jeff Hurray on 1/17/17. 6 | // Copyright © 2017 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | FOUNDATION_EXPORT double JHChainableAnimations_iOS_SwiftVersionNumber; 12 | 13 | FOUNDATION_EXPORT const unsigned char JHChainableAnimations_iOS_SwiftVersionString[]; 14 | 15 | #import "JHChainableAnimator.h" 16 | -------------------------------------------------------------------------------- /Frameworks-Swift/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 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Frameworks-iOS/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 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Frameworks-iOS/JHChainableAnimations.h: -------------------------------------------------------------------------------- 1 | // 2 | // JHChainableAnimations.h 3 | // JHChainableAnimations 4 | // 5 | // Created by Jeff Hurray on 12/21/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for JHChainableAnimations. 12 | FOUNDATION_EXPORT double JHChainableAnimationsVersionNumber; 13 | 14 | //! Project version string for JHChainableAnimations. 15 | FOUNDATION_EXPORT const unsigned char JHChainableAnimationsVersionString[]; 16 | 17 | #import 18 | -------------------------------------------------------------------------------- /Gifs/JHChainableAnimationsExample1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/JHChainableAnimations/14e2248a93514a35279b1e73c7d3b9970e42c8d3/Gifs/JHChainableAnimationsExample1.gif -------------------------------------------------------------------------------- /Gifs/JHChainableAnimationsExample2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/JHChainableAnimations/14e2248a93514a35279b1e73c7d3b9970e42c8d3/Gifs/JHChainableAnimationsExample2.gif -------------------------------------------------------------------------------- /Gifs/JHChainableAnimationsExample3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/JHChainableAnimations/14e2248a93514a35279b1e73c7d3b9970e42c8d3/Gifs/JHChainableAnimationsExample3.gif -------------------------------------------------------------------------------- /JHChainableAnimations.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'JHChainableAnimations' 3 | s.version = '3.0.1' 4 | s.author = { 'Jeff Hurray' => 'jhurray33@gmail.com' } 5 | s.homepage = 'https://github.com/jhurray/JHChainableAnimations' 6 | s.summary = 'Easy to read and write chainable Animations in Objective-C' 7 | s.license = 'MIT' 8 | s.source = { :git => 'https://github.com/jhurray/JHChainableAnimations.git', :tag => s.version.to_s } 9 | s.source_files = 'JHChainableAnimations/*{.h,.c,.m}', 'Frameworks-iOS/JHChainableAnimations.h' 10 | s.platform = :ios 11 | s.ios.deployment_target = '7.0' 12 | s.requires_arc = true 13 | end 14 | -------------------------------------------------------------------------------- /JHChainableAnimations.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JHChainableAnimations.xcodeproj/xcshareddata/xcschemes/JHChainableAnimations-iOS-Swift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /JHChainableAnimations.xcodeproj/xcshareddata/xcschemes/JHChainableAnimations-iOS.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 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /JHChainableAnimations.xcodeproj/xcshareddata/xcschemes/JHChainableAnimations-tvOS-Swift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /JHChainableAnimations.xcodeproj/xcshareddata/xcschemes/JHChainableAnimations-tvOS.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 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /JHChainableAnimations/ChainableAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChainableAnimator.swift 3 | // JHChainableAnimations 4 | // 5 | // Created by Jeff Hurray on 1/17/17. 6 | // Copyright © 2017 jhurray. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | internal extension Float { 13 | 14 | var toCG: CGFloat { 15 | return CGFloat(self) 16 | } 17 | } 18 | 19 | 20 | public final class ChainableAnimator { 21 | 22 | fileprivate let animator: JHChainableAnimator! 23 | 24 | public init(view: UIView) { 25 | animator = JHChainableAnimator(view: view) 26 | } 27 | 28 | public var completion: () -> Void { 29 | get { 30 | return animator.completionBlock 31 | } 32 | set (newCompletion) { 33 | animator.completionBlock = newCompletion 34 | } 35 | } 36 | 37 | public var isAnimating: Bool { 38 | return animator.isAnimating 39 | } 40 | 41 | public var isPaused: Bool { 42 | return animator.isPaused 43 | } 44 | 45 | public var view: UIView { 46 | return animator.view 47 | } 48 | 49 | public func pause() { 50 | animator.pause() 51 | } 52 | 53 | public func resume() { 54 | animator.resume() 55 | } 56 | 57 | public func stop() { 58 | animator.stop() 59 | } 60 | } 61 | 62 | 63 | public extension ChainableAnimator { 64 | 65 | public func make(frame: CGRect) -> Self { 66 | animator.makeFrame()(frame) 67 | return self; 68 | } 69 | 70 | public func make(bounds: CGRect) -> Self { 71 | animator.makeBounds()(bounds) 72 | return self 73 | } 74 | 75 | public func make(height: Float, width: Float) -> Self { 76 | animator.makeSize()(height.toCG, width.toCG) 77 | return self 78 | } 79 | 80 | public func makeOrigin(x: Float, y: Float) -> Self { 81 | animator.makeOrigin()(x.toCG, y.toCG) 82 | return self 83 | } 84 | 85 | public func makeCenter(x: Float, y: Float) -> Self { 86 | animator.makeCenter()(x.toCG, y.toCG) 87 | return self 88 | } 89 | 90 | public func make(x: Float) -> Self { 91 | animator.makeX()(x.toCG) 92 | return self 93 | } 94 | 95 | public func make(y: Float) -> Self { 96 | animator.makeY()(y.toCG) 97 | return self 98 | } 99 | 100 | public func make(width: Float) -> Self { 101 | animator.makeWidth()(width.toCG) 102 | return self 103 | } 104 | 105 | public func make(height: Float) -> Self { 106 | animator.makeHeight()(height.toCG) 107 | return self 108 | } 109 | 110 | public func make(alpha: Float) -> Self { 111 | animator.makeOpacity()(alpha.toCG) 112 | return self 113 | } 114 | 115 | public func make(backgroundColor color: UIColor) -> Self { 116 | animator.makeBackground()(color) 117 | return self 118 | } 119 | 120 | public func make(borderColor color: UIColor) -> Self { 121 | animator.makeBorderColor()(color) 122 | return self 123 | } 124 | 125 | public func make(borderWidth width: Float) -> Self { 126 | animator.makeBorderWidth()(width.toCG) 127 | return self 128 | } 129 | 130 | public func make(cornerRadius: Float) -> Self { 131 | animator.makeCornerRadius()(cornerRadius.toCG) 132 | return self 133 | } 134 | 135 | public func make(scale: Float) -> Self { 136 | animator.makeScale()(scale.toCG) 137 | return self 138 | } 139 | 140 | public func make(scaleY: Float) -> Self { 141 | animator.makeScaleY()(scaleY.toCG) 142 | return self 143 | } 144 | 145 | public func make(scaleX: Float) -> Self { 146 | animator.makeScaleX()(scaleX.toCG) 147 | return self 148 | } 149 | 150 | public func makeAnchor(x: Float, y: Float) -> Self { 151 | animator.makeAnchor()(x.toCG, y.toCG) 152 | return self 153 | } 154 | 155 | public func move(x: Float) -> Self { 156 | animator.moveX()(x.toCG) 157 | return self 158 | } 159 | 160 | public func move(y: Float) -> Self { 161 | animator.moveY()(y.toCG) 162 | return self 163 | } 164 | 165 | public func move(x: Float, y: Float) -> Self { 166 | animator.moveXY()(x.toCG, y.toCG) 167 | return self 168 | } 169 | 170 | public func move(width: Float) -> Self { 171 | animator.moveWidth()(width.toCG) 172 | return self 173 | } 174 | 175 | public func move(height: Float) -> Self { 176 | animator.moveHeight()(height.toCG) 177 | return self 178 | } 179 | 180 | public func movePolar(radius: Float, angle: Float) -> Self { 181 | animator.movePolar()(radius.toCG, angle.toCG) 182 | return self 183 | } 184 | 185 | public var transformIdentity: ChainableAnimator { 186 | animator.transformIdentity() 187 | return self 188 | } 189 | 190 | public func rotate(angle: Float) -> Self { 191 | animator.rotate()(angle.toCG) 192 | return self 193 | } 194 | 195 | public func rotateX(angle: Float) -> Self { 196 | animator.rotateX()(angle.toCG) 197 | return self 198 | } 199 | 200 | public func rotateY(angle: Float) -> Self { 201 | animator.rotateY()(angle.toCG) 202 | return self 203 | } 204 | 205 | public func rotateZ(angle: Float) -> Self { 206 | animator.rotateZ()(angle.toCG) 207 | return self 208 | } 209 | 210 | public func transform(x: Float) -> Self { 211 | animator.transformX()(x.toCG) 212 | return self 213 | } 214 | 215 | public func transform(y: Float) -> Self { 216 | animator.transformY()(y.toCG) 217 | return self 218 | } 219 | 220 | public func transform(x: Float, y: Float) -> Self { 221 | animator.transformXY()(x.toCG, y.toCG) 222 | return self 223 | } 224 | 225 | public func transform(scale: Float) -> Self { 226 | animator.transformScale()(scale.toCG) 227 | return self 228 | } 229 | 230 | public func transform(scaleX: Float) -> Self { 231 | animator.transformScaleX()(scaleX.toCG) 232 | return self 233 | } 234 | 235 | public func transform(scaleY: Float) -> Self { 236 | animator.transformScaleY()(scaleY.toCG) 237 | return self 238 | } 239 | 240 | public func move(onPath path: UIBezierPath, rotate: Bool = false, isReversed: Bool = false) -> Self { 241 | if rotate { 242 | if isReversed { 243 | animator.moveAndReverseRotateOnPath()(path) 244 | } else { 245 | animator.moveAndRotateOnPath()(path) 246 | } 247 | } else { 248 | animator.moveOnPath()(path) 249 | } 250 | return self 251 | } 252 | 253 | public enum AnchorPosition { 254 | case normal 255 | case center 256 | case topLeft 257 | case topRight 258 | case bottomLeft 259 | case bottomRight 260 | case top 261 | case bottom 262 | case left 263 | case right 264 | } 265 | 266 | public func anchor(_ position: AnchorPosition) -> ChainableAnimator { 267 | switch position { 268 | case .normal: 269 | animator.anchorDefault() 270 | case .center: 271 | animator.anchorCenter() 272 | case .topLeft: 273 | animator.anchorTopLeft() 274 | case .topRight: 275 | animator.anchorTopRight() 276 | case .bottomLeft: 277 | animator.anchorBottomLeft() 278 | case .bottomRight: 279 | animator.anchorBottomRight() 280 | case .top: 281 | animator.anchorTop() 282 | case .bottom: 283 | animator.anchorBottom() 284 | case .left: 285 | animator.anchorLeft() 286 | case .right: 287 | animator.anchorRight() 288 | } 289 | return self 290 | } 291 | 292 | public func delay(t: TimeInterval) -> Self { 293 | animator.delay()(t) 294 | return self 295 | } 296 | 297 | public func wait(t: TimeInterval) -> Self { 298 | animator.wait()(t) 299 | return self 300 | } 301 | 302 | public var easeIn: ChainableAnimator { 303 | animator.easeIn() 304 | return self 305 | } 306 | 307 | public var easeOut: ChainableAnimator { 308 | animator.easeOut() 309 | return self 310 | } 311 | 312 | public var easeInOut: ChainableAnimator { 313 | animator.easeInOut() 314 | return self 315 | } 316 | 317 | public var easeBack: ChainableAnimator { 318 | animator.easeBack() 319 | return self 320 | } 321 | 322 | public var spring: ChainableAnimator { 323 | animator.spring() 324 | return self 325 | } 326 | 327 | public var bounce: ChainableAnimator { 328 | animator.bounce() 329 | return self 330 | } 331 | 332 | public var easeInQuad: ChainableAnimator { 333 | animator.easeInQuad() 334 | return self 335 | } 336 | 337 | public var easeOutQuad: ChainableAnimator { 338 | animator.easeOutQuad() 339 | return self 340 | } 341 | 342 | public var easeInOutQuad: ChainableAnimator { 343 | animator.easeInOutQuad() 344 | return self 345 | } 346 | 347 | public var easeInCubic: ChainableAnimator { 348 | animator.easeInCubic() 349 | return self 350 | } 351 | 352 | public var easeOutCubic: ChainableAnimator { 353 | animator.easeOutCubic() 354 | return self 355 | } 356 | 357 | public var easeInOutCubic: ChainableAnimator { 358 | animator.easeInOutCubic() 359 | return self 360 | } 361 | 362 | public var easeInQuart: ChainableAnimator { 363 | animator.easeInQuart() 364 | return self 365 | } 366 | 367 | public var easeOutQuart: ChainableAnimator { 368 | animator.easeOutQuart() 369 | return self 370 | } 371 | 372 | public var easeInOutQuart: ChainableAnimator { 373 | animator.easeInOutQuart() 374 | return self 375 | } 376 | 377 | public var easeInQuint: ChainableAnimator { 378 | animator.easeInQuint() 379 | return self 380 | } 381 | 382 | public var easeOutQuint: ChainableAnimator { 383 | animator.easeOutQuint() 384 | return self 385 | } 386 | 387 | public var easeInOutQuint: ChainableAnimator { 388 | animator.easeInOutQuint() 389 | return self 390 | } 391 | 392 | public var easeInSine: ChainableAnimator { 393 | animator.easeInSine() 394 | return self 395 | } 396 | 397 | public var easeOutSine: ChainableAnimator { 398 | animator.easeOutSine() 399 | return self 400 | } 401 | 402 | public var easeInOutSine: ChainableAnimator { 403 | animator.easeInOutSine() 404 | return self 405 | } 406 | 407 | public var easeInExpo: ChainableAnimator { 408 | animator.easeInExpo() 409 | return self 410 | } 411 | 412 | public var easeOutExpo: ChainableAnimator { 413 | animator.easeOutExpo() 414 | return self 415 | } 416 | 417 | public var easeInOutExpo: ChainableAnimator { 418 | animator.easeInOutExpo() 419 | return self 420 | } 421 | 422 | public var easeInCirc: ChainableAnimator { 423 | animator.easeInCirc() 424 | return self 425 | } 426 | 427 | public var easeOutCirc: ChainableAnimator { 428 | animator.easeOutCirc() 429 | return self 430 | } 431 | 432 | public var easeInOutCirc: ChainableAnimator { 433 | animator.easeInOutCirc() 434 | return self 435 | } 436 | 437 | public var easeInElastic: ChainableAnimator { 438 | animator.easeInElastic() 439 | return self 440 | } 441 | 442 | public var easeOutElastic: ChainableAnimator { 443 | animator.easeOutElastic() 444 | return self 445 | } 446 | 447 | public var easeInOutElastic: ChainableAnimator { 448 | animator.easeInOutElastic() 449 | return self 450 | } 451 | 452 | public var easeInBack: ChainableAnimator { 453 | animator.easeInBack() 454 | return self 455 | } 456 | 457 | public var easeOutBack: ChainableAnimator { 458 | animator.easeOutBack() 459 | return self 460 | } 461 | 462 | public var easeInOutBack: ChainableAnimator { 463 | animator.easeInOutBack() 464 | return self 465 | } 466 | 467 | public var easeInBounce: ChainableAnimator { 468 | animator.easeInBounce() 469 | return self 470 | } 471 | 472 | public var easeOutBounce: ChainableAnimator { 473 | animator.easeOutBounce() 474 | return self 475 | } 476 | 477 | public var easeInOutBounce: ChainableAnimator { 478 | animator.easeInOutBounce() 479 | return self 480 | } 481 | 482 | public func customAnimationFunction(function: @escaping (Double, Double, Double, Double) -> (Double)) -> Self { 483 | animator.customAnimationFunction()(function) 484 | return self 485 | } 486 | 487 | public func preAnimationBlock(block: @escaping () -> ()) -> Self { 488 | animator.preAnimationBlock()(block) 489 | return self 490 | } 491 | 492 | public func postAnimationBlock(block: @escaping () -> ()) -> Self { 493 | animator.postAnimationBlock()(block) 494 | return self 495 | } 496 | 497 | public func `repeat`(t: TimeInterval, count: Int) -> Self { 498 | animator.`repeat`()(t, count) 499 | return self 500 | } 501 | 502 | public func thenAfter(t: TimeInterval) -> Self { 503 | animator.thenAfter()(t) 504 | return self 505 | } 506 | 507 | public func animate(t: TimeInterval) { 508 | animator.animate()(t) 509 | } 510 | 511 | public func animateWithRepeat(t: TimeInterval, count: Int) { 512 | animator.animateWithRepeat()(t, count) 513 | } 514 | 515 | public func animate(t: TimeInterval, completion: @escaping () -> ()) { 516 | animator.animateWithCompletion()(t, completion) 517 | } 518 | } 519 | -------------------------------------------------------------------------------- /JHChainableAnimations/JHAnimationChainLink.h: -------------------------------------------------------------------------------- 1 | // 2 | // JHAnimationChainLink.h 3 | // JHChainableAnimations 4 | // 5 | // Created by Jeff Hurray on 12/21/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "JHKeyframeAnimation.h" 12 | 13 | @class JHChainableAnimator; 14 | typedef void (^JHAnimationCalculationAction)(__weak UIView *view, __weak JHChainableAnimator *weakSelf); 15 | typedef void (^JHAnimationCompletionAction)(__weak UIView *view, __weak JHChainableAnimator *weakSelf); 16 | 17 | @interface JHAnimationChainLink : NSObject 18 | 19 | @property (nonatomic, copy) JHAnimationCalculationAction anchorCalculationAction; 20 | @property (nonatomic, assign) NSTimeInterval animationDelay; 21 | 22 | - (instancetype)initWithView:(UIView *)view animator:(JHChainableAnimator *)animator NS_DESIGNATED_INITIALIZER; 23 | 24 | - (void)animateWithDuration:(NSTimeInterval)duration completion:(void(^)(void))completion; 25 | - (void)addAnimationFromCalculationBlock:(JHKeyframeAnimation *)animation; 26 | - (void)addAnimationKeyframeCalculation:(JHKeyframeAnimationCalculationBlock)functionBlock; 27 | - (void)addAnimationCalculationAction:(JHAnimationCalculationAction)action; 28 | - (void)addAnimationCompletionAction:(JHAnimationCompletionAction)action; 29 | - (void)addPreAnimationBlock:(void(^)(void))preAnimationBlock; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /JHChainableAnimations/JHAnimationChainLink.m: -------------------------------------------------------------------------------- 1 | // 2 | // JHAnimationChainLink.m 3 | // JHChainableAnimations 4 | // 5 | // Created by Jeff Hurray on 12/21/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import "JHAnimationChainLink.h" 10 | #import "JHChainableAnimator.h" 11 | #import "JHKeyframeAnimation.h" 12 | #import "JHKeyframeAnimationFunctions.h" 13 | 14 | static NSString * const kJHAnimationGroupKey = @"kJHAnimationGroupKey"; 15 | 16 | @interface JHAnimationChainLink () 17 | 18 | @property (nonatomic, weak) UIView *view; 19 | @property (nonatomic, weak) JHChainableAnimator *animator; 20 | @property (nonatomic, strong) NSMutableArray *animations; 21 | @property (nonatomic, strong) CAAnimationGroup *animationGroup; 22 | @property (nonatomic, strong) NSMutableArray *preAnimationBlocks; 23 | @property (nonatomic, strong) NSMutableArray *animationCalculationActions; 24 | @property (nonatomic, strong) NSMutableArray *animationCompletionActions; 25 | 26 | @end 27 | 28 | 29 | @implementation JHAnimationChainLink 30 | 31 | - (instancetype)init 32 | { 33 | NSAssert(NO, @"JHChainableAnimator: Must use `- (instancetype)initWithView:(UIView *)view animator:animator` method."); 34 | [self doesNotRecognizeSelector:_cmd]; 35 | return [self initWithView:nil animator:nil]; 36 | } 37 | 38 | 39 | - (instancetype)initWithView:(UIView *)view animator:(JHChainableAnimator *)animator 40 | { 41 | self = [super init]; 42 | if (self) { 43 | _view = view; 44 | _animator = animator; 45 | _animationDelay = 0; 46 | _animations = [NSMutableArray new]; 47 | _animationGroup = [CAAnimationGroup animation]; 48 | _animationCalculationActions = [NSMutableArray new]; 49 | _animationCompletionActions = [NSMutableArray new]; 50 | _preAnimationBlocks = [NSMutableArray new]; 51 | } 52 | return self; 53 | } 54 | 55 | 56 | - (id)copyWithZone:(nullable NSZone *)zone 57 | { 58 | JHAnimationChainLink *copy = [[JHAnimationChainLink allocWithZone:zone] initWithView:self.view animator:self.animator]; 59 | copy.anchorCalculationAction = [self.anchorCalculationAction copy]; 60 | copy.animationDelay = self.animationDelay; 61 | copy.animations = [self.animations mutableCopy]; 62 | copy.animationGroup = [self.animationGroup copy]; 63 | copy.animationCalculationActions = [self.animationCalculationActions mutableCopy]; 64 | copy.animationCompletionActions = [self.animationCompletionActions mutableCopy]; 65 | copy.preAnimationBlocks = [self.preAnimationBlocks mutableCopy]; 66 | return copy; 67 | } 68 | 69 | 70 | - (JHKeyframeAnimation *)basicAnimationForKeyPath:(NSString *)keypath 71 | { 72 | JHKeyframeAnimation * animation = [JHKeyframeAnimation animationWithKeyPath:keypath]; 73 | animation.repeatCount = 0; 74 | animation.autoreverses = NO; 75 | return animation; 76 | } 77 | 78 | - (void)addAnimationFromCalculationBlock:(JHKeyframeAnimation *)animation 79 | { 80 | [self.animations addObject:animation]; 81 | } 82 | 83 | 84 | - (void)addAnimationKeyframeCalculation:(JHKeyframeAnimationCalculationBlock)functionBlock 85 | { 86 | JHKeyframeAnimation *animation = [self.animations lastObject]; 87 | animation.functionBlock = [functionBlock copy]; 88 | } 89 | 90 | 91 | - (void)addAnimationCalculationAction:(JHAnimationCalculationAction)action 92 | { 93 | [self.animationCalculationActions addObject:[action copy]]; 94 | } 95 | 96 | 97 | - (void)addAnimationCompletionAction:(JHAnimationCompletionAction)action 98 | { 99 | [self.animationCompletionActions addObject:[action copy]]; 100 | } 101 | 102 | 103 | - (void)addPreAnimationBlock:(void(^)(void))preAnimationBlock 104 | { 105 | [self.preAnimationBlocks addObject:[preAnimationBlock copy]]; 106 | } 107 | 108 | 109 | - (void)animateWithDuration:(NSTimeInterval)duration completion:(void (^)(void))completion 110 | { 111 | [CATransaction begin]; 112 | [CATransaction setDisableActions:YES]; 113 | [CATransaction setCompletionBlock:^{ 114 | [self.view.layer removeAnimationForKey:kJHAnimationGroupKey]; 115 | [self executeCompletionActions]; 116 | if (completion != nil) { 117 | completion(); 118 | } 119 | }]; 120 | 121 | self.animationGroup.duration = duration; 122 | [self beginExecution]; 123 | 124 | [CATransaction commit]; 125 | } 126 | 127 | 128 | - (void)beginExecution 129 | { 130 | for (void(^block)(void) in self.preAnimationBlocks) { 131 | block(); 132 | } 133 | 134 | if (self.anchorCalculationAction != nil) { 135 | self.anchorCalculationAction(self.view, self.animator); 136 | } 137 | 138 | for (JHAnimationCalculationAction action in self.animationCalculationActions) { 139 | action(self.view, self.animator); 140 | } 141 | for (JHKeyframeAnimation *animation in self.animations) { 142 | animation.duration = self.animationGroup.duration; 143 | [animation calculate]; 144 | } 145 | self.animationGroup.beginTime = CACurrentMediaTime() + self.animationDelay; 146 | self.animationGroup.animations = self.animations; 147 | self.animationGroup.fillMode = kCAFillModeForwards; 148 | self.animationGroup.removedOnCompletion = NO; 149 | [self.view.layer addAnimation:self.animationGroup forKey:kJHAnimationGroupKey]; 150 | } 151 | 152 | 153 | - (void)executeCompletionActions 154 | { 155 | for (JHAnimationCompletionAction action in self.animationCompletionActions) { 156 | action(self.view, self.animator); 157 | } 158 | } 159 | 160 | @end 161 | -------------------------------------------------------------------------------- /JHChainableAnimations/JHChainableAnimator.h: -------------------------------------------------------------------------------- 1 | // 2 | // JHChainableAnimator.h 3 | // JHChainableAnimations 4 | // 5 | // Created by Jeff Hurray on 12/21/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "JHChainableBlocks.h" 12 | 13 | @interface JHChainableAnimator : NSObject 14 | 15 | /// Called after the last animation in the chain has completed 16 | @property (nonatomic, copy) void(^completionBlock)(void); 17 | 18 | /// If the animatoris paused, the view will still be animating. animating will only be false if the animation ends or is stopped 19 | @property (atomic, assign, getter=isAnimating, readonly) BOOL animating; 20 | @property (atomic, assign, getter=isPaused, readonly) BOOL paused; 21 | 22 | @property (nonatomic, weak, readonly) UIView *view; 23 | 24 | - (instancetype)initWithView:(UIView *)view NS_DESIGNATED_INITIALIZER; 25 | 26 | /// Will pause animations but retain state. `isAnimating` will remain true. 27 | - (void)pause; 28 | /// Will resume animations if the animator is paused and animating 29 | - (void)resume; 30 | /// Will stop animations and clear state 31 | - (void)stop; 32 | 33 | @end 34 | 35 | 36 | @interface JHChainableAnimator (JH_ChainableMethods) 37 | 38 | #pragma mark - Chainable Properties 39 | // Makers 40 | // Affects views position and bounds 41 | - (JHChainableRect)makeFrame; 42 | - (JHChainableRect)makeBounds; 43 | - (JHChainableSize)makeSize; 44 | - (JHChainablePoint)makeOrigin; 45 | - (JHChainablePoint)makeCenter; 46 | - (JHChainableFloat)makeX; 47 | - (JHChainableFloat)makeY; 48 | - (JHChainableFloat)makeWidth; 49 | - (JHChainableFloat)makeHeight; 50 | - (JHChainableFloat)makeOpacity; 51 | - (JHChainableColor)makeBackground; 52 | - (JHChainableColor)makeBorderColor; 53 | - (JHChainableFloat)makeBorderWidth; 54 | - (JHChainableFloat)makeCornerRadius; 55 | - (JHChainableFloat)makeScale; 56 | - (JHChainableFloat)makeScaleX; 57 | - (JHChainableFloat)makeScaleY; 58 | - (JHChainablePoint)makeAnchor; 59 | 60 | // Movers 61 | // Affects views position and bounds 62 | - (JHChainableFloat)moveX; 63 | - (JHChainableFloat)moveY; 64 | - (JHChainablePoint)moveXY; 65 | - (JHChainableFloat)moveHeight; 66 | - (JHChainableFloat)moveWidth; 67 | - (JHChainablePolarCoordinate)movePolar; 68 | 69 | // Transforms 70 | // Affects views transform property NOT position and bounds 71 | // These should be used for AutoLayout 72 | // These should NOT be mixed with properties that affect position and bounds 73 | - (JHChainableAnimator *)transformIdentity; 74 | - (JHChainableDegrees)rotate; // Same as rotateZ 75 | - (JHChainableDegrees)rotateX; 76 | - (JHChainableDegrees)rotateY; 77 | - (JHChainableDegrees)rotateZ; 78 | - (JHChainableFloat)transformX; 79 | - (JHChainableFloat)transformY; 80 | - (JHChainableFloat)transformZ; 81 | - (JHChainablePoint)transformXY; 82 | - (JHChainableFloat)transformScale; // x and y equal 83 | - (JHChainableFloat)transformScaleX; 84 | - (JHChainableFloat)transformScaleY; 85 | 86 | 87 | #pragma mark - Bezier Paths 88 | // Animation effects dont apply 89 | - (JHChainableBezierPath)moveOnPath; 90 | - (JHChainableBezierPath)moveAndRotateOnPath; 91 | - (JHChainableBezierPath)moveAndReverseRotateOnPath; 92 | 93 | 94 | #pragma mark - Anchors 95 | - (JHChainableAnimator *)anchorDefault; 96 | - (JHChainableAnimator *)anchorCenter; 97 | - (JHChainableAnimator *)anchorTopLeft; 98 | - (JHChainableAnimator *)anchorTopRight; 99 | - (JHChainableAnimator *)anchorBottomLeft; 100 | - (JHChainableAnimator *)anchorBottomRight; 101 | - (JHChainableAnimator *)anchorTop; 102 | - (JHChainableAnimator *)anchorBottom; 103 | - (JHChainableAnimator *)anchorLeft; 104 | - (JHChainableAnimator *)anchorRight; 105 | 106 | 107 | #pragma mark - Delays 108 | - (JHChainableTimeInterval)delay; 109 | - (JHChainableTimeInterval)wait; 110 | 111 | 112 | #pragma mark - Keyframe Calculation Functions 113 | - (JHChainableAnimator *)easeIn; 114 | - (JHChainableAnimator *)easeOut; 115 | - (JHChainableAnimator *)easeInOut; 116 | - (JHChainableAnimator *)easeBack; 117 | - (JHChainableAnimator *)spring; 118 | - (JHChainableAnimator *)bounce; 119 | - (JHChainableAnimator *)easeInQuad; 120 | - (JHChainableAnimator *)easeOutQuad; 121 | - (JHChainableAnimator *)easeInOutQuad; 122 | - (JHChainableAnimator *)easeInCubic; 123 | - (JHChainableAnimator *)easeOutCubic; 124 | - (JHChainableAnimator *)easeInOutCubic; 125 | - (JHChainableAnimator *)easeInQuart; 126 | - (JHChainableAnimator *)easeOutQuart; 127 | - (JHChainableAnimator *)easeInOutQuart; 128 | - (JHChainableAnimator *)easeInQuint; 129 | - (JHChainableAnimator *)easeOutQuint; 130 | - (JHChainableAnimator *)easeInOutQuint; 131 | - (JHChainableAnimator *)easeInSine; 132 | - (JHChainableAnimator *)easeOutSine; 133 | - (JHChainableAnimator *)easeInOutSine; 134 | - (JHChainableAnimator *)easeInExpo; 135 | - (JHChainableAnimator *)easeOutExpo; 136 | - (JHChainableAnimator *)easeInOutExpo; 137 | - (JHChainableAnimator *)easeInCirc; 138 | - (JHChainableAnimator *)easeOutCirc; 139 | - (JHChainableAnimator *)easeInOutCirc; 140 | - (JHChainableAnimator *)easeInElastic; 141 | - (JHChainableAnimator *)easeOutElastic; 142 | - (JHChainableAnimator *)easeInOutElastic; 143 | - (JHChainableAnimator *)easeInBack; 144 | - (JHChainableAnimator *)easeOutBack; 145 | - (JHChainableAnimator *)easeInOutBack; 146 | - (JHChainableAnimator *)easeInBounce; 147 | - (JHChainableAnimator *)easeOutBounce; 148 | - (JHChainableAnimator *)easeInOutBounce; 149 | - (JHChainableCustomKeyframeAnimationCalculation)customAnimationFunction; 150 | 151 | #pragma mark - Blocks 152 | // Allows handling in in context of the animation state 153 | - (JHChainableBlock)preAnimationBlock; 154 | - (JHChainableBlock)postAnimationBlock; 155 | 156 | 157 | #pragma mark - Animations 158 | - (JHChainableRepeatAnimation)repeat; 159 | - (JHChainableTimeInterval)thenAfter; 160 | - (JHChainableAnimation)animate; 161 | - (JHChainableRepeatAnimation)animateWithRepeat; 162 | - (JHChainableAnimationWithCompletion)animateWithCompletion; 163 | 164 | @end 165 | 166 | 167 | -------------------------------------------------------------------------------- /JHChainableAnimations/JHChainableBlocks.h: -------------------------------------------------------------------------------- 1 | // 2 | // JHChainableBlocks.h 3 | // JHChainableAnimations 4 | // 5 | // Created by Jeff Hurray on 12/21/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | @class JHChainableAnimator; 10 | 11 | typedef JHChainableAnimator * (^JHChainable)(void); 12 | #define JHChainable() ^JHChainableAnimator * (void) 13 | 14 | typedef JHChainableAnimator * (^JHChainableTimeInterval)(NSTimeInterval t); 15 | #define JHChainableTimeInterval(t) ^JHChainableAnimator * (NSTimeInterval t) 16 | 17 | typedef JHChainableAnimator * (^JHChainableRect)(CGRect rect); 18 | #define JHChainableRect(rect) ^JHChainableAnimator * (CGRect rect) 19 | 20 | typedef JHChainableAnimator * (^JHChainableSize)(CGFloat width, CGFloat height); 21 | #define JHChainableSize(width,height) ^JHChainableAnimator * (CGFloat width, CGFloat height) 22 | 23 | typedef JHChainableAnimator * (^JHChainablePoint)(CGFloat x, CGFloat y); 24 | #define JHChainablePoint(x,y) ^JHChainableAnimator * (CGFloat x, CGFloat y) 25 | 26 | typedef JHChainableAnimator * (^JHChainableFloat)(CGFloat f); 27 | #define JHChainableFloat(f) ^JHChainableAnimator * (CGFloat f) 28 | 29 | typedef JHChainableAnimator * (^JHChainableDegrees)(CGFloat angle); 30 | #define JHChainableDegrees(angle) ^JHChainableAnimator * (CGFloat angle) 31 | 32 | typedef JHChainableAnimator * (^JHChainablePolarCoordinate)(CGFloat radius, CGFloat angle); 33 | #define JHChainablePolarCoordinate(radius,angle) ^JHChainableAnimator * (CGFloat radius, CGFloat angle) 34 | 35 | typedef JHChainableAnimator * (^JHChainableColor)(UIColor *color); 36 | #define JHChainableColor(color) ^JHChainableAnimator * (UIColor *color) 37 | 38 | typedef JHChainableAnimator * (^JHChainableBlock)(void(^)(void)); 39 | #define JHChainableBlock(block) ^JHChainableAnimator * (void(^block)(void)) 40 | 41 | typedef JHChainableAnimator * (^JHChainableBezierPath)(UIBezierPath *path); 42 | #define JHChainableBezierPath(path) ^JHChainableAnimator * (UIBezierPath *path) 43 | 44 | typedef JHChainableAnimator * (^JHChainableCustomKeyframeAnimationCalculation)(double(^keyframeAnimationCalculationBlock)(double t, double b, double c, double d)); 45 | #define JHChainableCustomKeyframeAnimationCalculation(block) ^JHChainableAnimator * (double(^block)(double t, double b, double c, double d)) 46 | 47 | typedef JHChainableAnimator * (^JHChainableAnimation)(NSTimeInterval duration); 48 | #define JHChainableAnimation(duration) ^JHChainableAnimator * (NSTimeInterval duration) 49 | 50 | typedef JHChainableAnimator * (^JHChainableAnimationWithCompletion)(NSTimeInterval duration, void(^completion)(void)); 51 | #define JHChainableAnimationWithCompletion(duration,completion) ^JHChainableAnimator * (NSTimeInterval duration, void(^completion)(void)) 52 | 53 | typedef JHChainableAnimator * (^JHChainableRepeatAnimation)(NSTimeInterval t, NSInteger count); 54 | #define JHChainableRepeatAnimation(t, count) ^JHChainableAnimator * (NSTimeInterval t, NSInteger count) 55 | -------------------------------------------------------------------------------- /JHChainableAnimations/JHKeyframeAnimation.h: -------------------------------------------------------------------------------- 1 | // 2 | // JHKeyframeAnimation.h 3 | // JHChainableAnimations 4 | // 5 | // Created by Jeff Hurray on 12/21/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef double(^JHKeyframeAnimationCalculationBlock)(double t, double b, double c, double d); 12 | 13 | @interface JHKeyframeAnimation : CAKeyframeAnimation 14 | 15 | @property (nonatomic, copy) JHKeyframeAnimationCalculationBlock functionBlock; 16 | 17 | @property (nonatomic, strong) id fromValue; 18 | @property (nonatomic, strong) id toValue; 19 | 20 | - (void)calculate; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /JHChainableAnimations/JHKeyframeAnimation.m: -------------------------------------------------------------------------------- 1 | // 2 | // JHKeyframeAnimation.m 3 | // JHChainableAnimations 4 | // 5 | // Created by Jeff Hurray on 12/21/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | // Credits to https://github.com/NachoSoto/NSBKeyframeAnimation 9 | // Credits to https://github.com/khanlou/SKBounceAnimation 10 | 11 | #import "JHKeyframeAnimation.h" 12 | #import "JHKeyframeAnimationFunctions.h" 13 | #import 14 | 15 | #define kFPS 60 16 | 17 | @interface JHKeyframeAnimation() 18 | 19 | - (NSArray *)valueArrayForStartValue:(CGFloat)startValue endValue:(CGFloat)endValue; 20 | - (void)createValueArray; 21 | - (NSArray *)createRectArrayFromXValues:(NSArray *)xValues yValues:(NSArray *)yValues widths:(NSArray *)widths heights:(NSArray *)heights; 22 | - (NSArray *)createPointArrayFromXValues:(NSArray *)xValues yValues:(NSArray *)yValues; 23 | - (NSArray *)createSizeArrayFromWidths:(NSArray *)widths heights:(NSArray *)heights; 24 | - (NSArray *)createColorArrayFromRed:(NSArray *)redValues green:(NSArray *)greenValues blue:(NSArray *)blueValues alpha:(NSArray *)alphaValues; 25 | - (NSArray *)createTransformArrayFromM11:(NSArray *)m11 M12:(NSArray *)m12 M13:(NSArray *)m13 M14:(NSArray *)m14 26 | M21:(NSArray *)m21 M22:(NSArray *)m22 M23:(NSArray *)m23 M24:(NSArray *)m24 27 | M31:(NSArray *)m31 M32:(NSArray *)m32 M33:(NSArray *)m33 M34:(NSArray *)m34 28 | M41:(NSArray *)m41 M42:(NSArray *)m42 M43:(NSArray *)m43 M44:(NSArray *)m44; 29 | 30 | 31 | @end 32 | 33 | @implementation JHKeyframeAnimation 34 | 35 | + (instancetype)animationWithKeyPath:(NSString *)path 36 | { 37 | JHKeyframeAnimation *animation = [super animationWithKeyPath:path]; 38 | if (animation) { 39 | animation.functionBlock = ^double(double t, double b, double c, double d) { 40 | return JHKeyframeAnimationFunctionLinear(t, b, c, d); 41 | }; 42 | } 43 | return animation; 44 | } 45 | 46 | 47 | - (id)copyWithZone:(nullable NSZone *)zone 48 | { 49 | JHKeyframeAnimation *copy = [[JHKeyframeAnimation allocWithZone:zone] init]; 50 | copy.functionBlock = [self.functionBlock copy]; 51 | if ([self.fromValue isKindOfClass:NSObject.class] && [self.fromValue conformsToProtocol:@protocol(NSCopying)]) { 52 | NSObject *fromValue = self.fromValue; 53 | copy.fromValue = [fromValue copy]; 54 | } 55 | else { 56 | copy.fromValue = self.fromValue; 57 | } 58 | if ([self.toValue isKindOfClass:NSObject.class] && [self.toValue conformsToProtocol:@protocol(NSCopying)]) { 59 | NSObject *toValue = self.toValue; 60 | copy.toValue = [toValue copy]; 61 | } 62 | else { 63 | copy.toValue = self.toValue; 64 | } 65 | return copy; 66 | } 67 | 68 | 69 | - (void)calculate 70 | { 71 | [self createValueArray]; 72 | } 73 | 74 | 75 | - (void)createValueArray 76 | { 77 | if (self.fromValue && self.toValue && self.duration) { 78 | if ([self.fromValue isKindOfClass:[NSNumber class]] && [self.toValue isKindOfClass:[NSNumber class]]) { 79 | self.values = [self valueArrayForStartValue:[self.fromValue floatValue] endValue:[self.toValue floatValue]]; 80 | } 81 | else if ([self.fromValue isKindOfClass:[UIColor class]] && [self.toValue isKindOfClass:[UIColor class]]) { 82 | const CGFloat *fromComponents = CGColorGetComponents(((UIColor *)self.fromValue).CGColor); 83 | const CGFloat *toComponents = CGColorGetComponents(((UIColor *)self.toValue).CGColor); 84 | self.values = [self createColorArrayFromRed: 85 | [self valueArrayForStartValue:fromComponents[0] endValue:toComponents[0]] 86 | green: 87 | [self valueArrayForStartValue:fromComponents[1] endValue:toComponents[1]] 88 | blue: 89 | [self valueArrayForStartValue:fromComponents[2] endValue:toComponents[2]] 90 | alpha: 91 | [self valueArrayForStartValue:fromComponents[3] endValue:toComponents[3]]]; 92 | } 93 | else if ([self.fromValue isKindOfClass:[NSValue class]] && [self.toValue isKindOfClass:[NSValue class]]) { 94 | NSString *valueType = [NSString stringWithCString:[self.fromValue objCType] encoding:NSStringEncodingConversionAllowLossy]; 95 | if ([valueType rangeOfString:@"CGRect"].location == 1) { 96 | CGRect fromRect = [self.fromValue CGRectValue]; 97 | CGRect toRect = [self.toValue CGRectValue]; 98 | self.values = [self createRectArrayFromXValues: 99 | [self valueArrayForStartValue:fromRect.origin.x endValue:toRect.origin.x] 100 | yValues: 101 | [self valueArrayForStartValue:fromRect.origin.y endValue:toRect.origin.y] 102 | widths: 103 | [self valueArrayForStartValue:fromRect.size.width endValue:toRect.size.width] 104 | heights: 105 | [self valueArrayForStartValue:fromRect.size.height endValue:toRect.size.height]]; 106 | 107 | } 108 | else if ([valueType rangeOfString:@"CGPoint"].location == 1) { 109 | CGPoint fromPoint = [self.fromValue CGPointValue]; 110 | CGPoint toPoint = [self.toValue CGPointValue]; 111 | self.values = [self createPointArrayFromXValues:[self valueArrayForStartValue:fromPoint.x endValue:toPoint.x] 112 | yValues:[self valueArrayForStartValue:fromPoint.y endValue:toPoint.y]]; 113 | } 114 | else if ([valueType rangeOfString:@"CATransform3D"].location == 1) { 115 | CATransform3D fromTransform = [self.fromValue CATransform3DValue]; 116 | CATransform3D toTransform = [self.toValue CATransform3DValue]; 117 | 118 | self.values = [self createTransformArrayFromM11: 119 | [self valueArrayForStartValue:fromTransform.m11 endValue:toTransform.m11] 120 | M12: 121 | [self valueArrayForStartValue:fromTransform.m12 endValue:toTransform.m12] 122 | M13: 123 | [self valueArrayForStartValue:fromTransform.m13 endValue:toTransform.m13] 124 | M14: 125 | [self valueArrayForStartValue:fromTransform.m14 endValue:toTransform.m14] 126 | M21: 127 | [self valueArrayForStartValue:fromTransform.m21 endValue:toTransform.m21] 128 | M22: 129 | [self valueArrayForStartValue:fromTransform.m22 endValue:toTransform.m22] 130 | M23: 131 | [self valueArrayForStartValue:fromTransform.m23 endValue:toTransform.m23] 132 | M24: 133 | [self valueArrayForStartValue:fromTransform.m24 endValue:toTransform.m24] 134 | M31: 135 | [self valueArrayForStartValue:fromTransform.m31 endValue:toTransform.m31] 136 | M32: 137 | [self valueArrayForStartValue:fromTransform.m32 endValue:toTransform.m32] 138 | M33: 139 | [self valueArrayForStartValue:fromTransform.m33 endValue:toTransform.m33] 140 | M34: 141 | [self valueArrayForStartValue:fromTransform.m34 endValue:toTransform.m34] 142 | M41: 143 | [self valueArrayForStartValue:fromTransform.m41 endValue:toTransform.m41] 144 | M42: 145 | [self valueArrayForStartValue:fromTransform.m42 endValue:toTransform.m42] 146 | M43: 147 | [self valueArrayForStartValue:fromTransform.m43 endValue:toTransform.m43] 148 | M44: 149 | [self valueArrayForStartValue:fromTransform.m44 endValue:toTransform.m44] 150 | ]; 151 | } 152 | else if ([valueType rangeOfString:@"CGSize"].location == 1) { 153 | CGSize fromSize = [self.fromValue CGSizeValue]; 154 | CGSize toSize = [self.toValue CGSizeValue]; 155 | self.values = [self createSizeArrayFromWidths:[self valueArrayForStartValue:fromSize.width endValue:toSize.width] 156 | heights:[self valueArrayForStartValue:fromSize.height endValue:toSize.height]]; 157 | } 158 | 159 | } 160 | self.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 161 | } 162 | } 163 | 164 | - (NSArray *)createRectArrayFromXValues:(NSArray *)xValues yValues:(NSArray *)yValues widths:(NSArray *)widths heights:(NSArray *)heights 165 | { 166 | NSAssert(xValues.count == yValues.count && xValues.count == widths.count && xValues.count == heights.count, @"array must have arrays of equal size"); 167 | 168 | NSUInteger numberOfRects = xValues.count; 169 | NSMutableArray *values = [NSMutableArray arrayWithCapacity:numberOfRects]; 170 | CGRect value; 171 | 172 | for (NSInteger i = 1; i < numberOfRects; i++) { 173 | value = CGRectMake( 174 | [[xValues objectAtIndex:i] floatValue], 175 | [[yValues objectAtIndex:i] floatValue], 176 | [[widths objectAtIndex:i] floatValue], 177 | [[heights objectAtIndex:i] floatValue] 178 | ); 179 | [values addObject:[NSValue valueWithCGRect:value]]; 180 | } 181 | return values; 182 | } 183 | 184 | 185 | - (NSArray *)createPointArrayFromXValues:(NSArray *)xValues yValues:(NSArray *)yValues 186 | { 187 | NSAssert(xValues.count == yValues.count, @"array must have arrays of equal size"); 188 | 189 | NSUInteger numberOfRects = xValues.count; 190 | NSMutableArray *values = [NSMutableArray arrayWithCapacity:numberOfRects]; 191 | 192 | for (NSInteger i = 1; i < numberOfRects; i++) { 193 | CGPoint value = CGPointMake( 194 | [[xValues objectAtIndex:i] floatValue], 195 | [[yValues objectAtIndex:i] floatValue] 196 | ); 197 | [values addObject:[NSValue valueWithCGPoint:value]]; 198 | } 199 | return values; 200 | } 201 | 202 | 203 | - (NSArray *)createSizeArrayFromWidths:(NSArray *)widths heights:(NSArray *)heights 204 | { 205 | NSAssert(widths.count == heights.count, @"array must have arrays of equal size"); 206 | 207 | NSUInteger numberOfRects = widths.count; 208 | NSMutableArray *values = [NSMutableArray arrayWithCapacity:numberOfRects]; 209 | 210 | for (NSInteger i = 1; i < numberOfRects; i++) { 211 | CGSize value = CGSizeMake( 212 | [[widths objectAtIndex:i] floatValue], 213 | [[heights objectAtIndex:i] floatValue] 214 | ); 215 | [values addObject:[NSValue valueWithCGSize:value]]; 216 | } 217 | return values; 218 | } 219 | 220 | 221 | - (NSArray *)createColorArrayFromRed:(NSArray*)redValues green:(NSArray*)greenValues blue:(NSArray*)blueValues alpha:(NSArray*)alphaValues 222 | { 223 | NSAssert(redValues.count == blueValues.count && redValues.count == greenValues.count && redValues.count == alphaValues.count, @"arrays must have arrays of equal size"); 224 | 225 | NSUInteger numberOfColors = redValues.count; 226 | NSMutableArray *values = [NSMutableArray arrayWithCapacity:numberOfColors]; 227 | UIColor *value; 228 | 229 | for (NSInteger i = 1; i < numberOfColors; i++) { 230 | value = [UIColor colorWithRed:[[redValues objectAtIndex:i] floatValue] 231 | green:[[greenValues objectAtIndex:i] floatValue] 232 | blue:[[blueValues objectAtIndex:i] floatValue] 233 | alpha:[[alphaValues objectAtIndex:i] floatValue]]; 234 | [values addObject:(id)value.CGColor]; 235 | } 236 | return values; 237 | } 238 | 239 | - (NSArray *)valueArrayForStartValue:(CGFloat)startValue endValue:(CGFloat)endValue 240 | { 241 | NSUInteger steps = ceil(kFPS * self.duration) + 2; 242 | NSMutableArray *valueArray = [NSMutableArray arrayWithCapacity:steps]; 243 | 244 | const double increment = 1.0 / (double)(steps - 1); 245 | double progress = 0.0, 246 | v = 0.0, 247 | value = 0.0; 248 | 249 | NSUInteger i; 250 | for (i = 0; i < steps; i++) 251 | { 252 | v = self.functionBlock(self.duration * progress * 1000, 0, 1, self.duration * 1000); 253 | value = startValue + v * (endValue - startValue); 254 | [valueArray addObject:@(value)]; 255 | progress += increment; 256 | } 257 | 258 | return [NSArray arrayWithArray:valueArray]; 259 | } 260 | 261 | - (NSArray *)createTransformArrayFromM11:(NSArray *)m11 M12:(NSArray *)m12 M13:(NSArray *)m13 M14:(NSArray *)m14 262 | M21:(NSArray *)m21 M22:(NSArray *)m22 M23:(NSArray *)m23 M24:(NSArray *)m24 263 | M31:(NSArray *)m31 M32:(NSArray *)m32 M33:(NSArray *)m33 M34:(NSArray *)m34 264 | M41:(NSArray *)m41 M42:(NSArray *)m42 M43:(NSArray *)m43 M44:(NSArray *)m44 265 | { 266 | NSUInteger numberOfTransforms = m11.count; 267 | NSMutableArray *values = [NSMutableArray arrayWithCapacity:numberOfTransforms]; 268 | CATransform3D value; 269 | 270 | for (NSInteger i = 1; i < numberOfTransforms; i++) { 271 | value = CATransform3DIdentity; 272 | value.m11 = [[m11 objectAtIndex:i] floatValue]; 273 | value.m12 = [[m12 objectAtIndex:i] floatValue]; 274 | value.m13 = [[m13 objectAtIndex:i] floatValue]; 275 | value.m14 = [[m14 objectAtIndex:i] floatValue]; 276 | 277 | value.m21 = [[m21 objectAtIndex:i] floatValue]; 278 | value.m22 = [[m22 objectAtIndex:i] floatValue]; 279 | value.m23 = [[m23 objectAtIndex:i] floatValue]; 280 | value.m24 = [[m24 objectAtIndex:i] floatValue]; 281 | 282 | value.m31 = [[m31 objectAtIndex:i] floatValue]; 283 | value.m32 = [[m32 objectAtIndex:i] floatValue]; 284 | value.m33 = [[m33 objectAtIndex:i] floatValue]; 285 | value.m44 = [[m34 objectAtIndex:i] floatValue]; 286 | 287 | value.m41 = [[m41 objectAtIndex:i] floatValue]; 288 | value.m42 = [[m42 objectAtIndex:i] floatValue]; 289 | value.m43 = [[m43 objectAtIndex:i] floatValue]; 290 | value.m44 = [[m44 objectAtIndex:i] floatValue]; 291 | 292 | [values addObject:[NSValue valueWithCATransform3D:value]]; 293 | } 294 | return values; 295 | } 296 | 297 | @end 298 | -------------------------------------------------------------------------------- /JHChainableAnimations/JHKeyframeAnimationFunctions.c: -------------------------------------------------------------------------------- 1 | // 2 | // JHKeyframeAnimationFunctions.c 3 | // JHChainableAnimations 4 | // 5 | // Created by Jeff Hurray on 12/21/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #include "JHKeyframeAnimationFunctions.h" 10 | #include 11 | #include 12 | 13 | // Source: http://gsgd.co.uk/sandbox/jquery/easing/jquery.easing.1.3.js 14 | // Credits to https://github.com/NachoSoto/NSBKeyframeAnimation 15 | 16 | #pragma clang diagnostic push 17 | #pragma clang diagnostic ignored "-Wunsequenced" 18 | 19 | double JHKeyframeAnimationFunctionLinear(double t,double b, double c, double d) 20 | { 21 | return c*(t/=d) + b; 22 | } 23 | 24 | double JHKeyframeAnimationFunctionEaseInQuad(double t,double b, double c, double d) 25 | { 26 | return c*(t/=d)*t + b; 27 | } 28 | 29 | double JHKeyframeAnimationFunctionEaseOutQuad(double t,double b, double c, double d) 30 | { 31 | return -c *(t/=d)*(t-2) + b; 32 | } 33 | 34 | double JHKeyframeAnimationFunctionEaseInOutQuad(double t,double b, double c, double d) 35 | { 36 | if ((t/=d/2) < 1) return c/2*t*t + b; 37 | return -c/2 * ((--t)*(t-2) - 1) + b; 38 | } 39 | 40 | double JHKeyframeAnimationFunctionEaseInCubic(double t,double b, double c, double d) 41 | { 42 | return c*(t/=d)*t*t + b; 43 | } 44 | 45 | double JHKeyframeAnimationFunctionEaseOutCubic(double t,double b, double c, double d) 46 | { 47 | return c*((t=t/d-1)*t*t + 1) + b; 48 | } 49 | 50 | double JHKeyframeAnimationFunctionEaseInOutCubic(double t, double b, double c, double d) 51 | { 52 | if ((t/=d/2) < 1) return c/2*t*t*t + b; 53 | return c/2*((t-=2)*t*t + 2) + b; 54 | } 55 | 56 | double JHKeyframeAnimationFunctionEaseInQuart(double t, double b, double c, double d) 57 | { 58 | return c*(t/=d)*t*t*t + b; 59 | } 60 | 61 | double JHKeyframeAnimationFunctionEaseOutQuart(double t, double b, double c, double d) 62 | { 63 | return -c * ((t=t/d-1)*t*t*t - 1) + b; 64 | } 65 | 66 | double JHKeyframeAnimationFunctionEaseInOutQuart(double t, double b, double c, double d) 67 | { 68 | if ((t/=d/2) < 1) return c/2*t*t*t*t + b; 69 | return -c/2 * ((t-=2)*t*t*t - 2) + b; 70 | } 71 | 72 | double JHKeyframeAnimationFunctionEaseInQuint(double t, double b, double c, double d) 73 | { 74 | return c*(t/=d)*t*t*t*t + b; 75 | } 76 | 77 | double JHKeyframeAnimationFunctionEaseOutQuint(double t, double b, double c, double d) 78 | { 79 | return c*((t=t/d-1)*t*t*t*t + 1) + b; 80 | } 81 | 82 | double JHKeyframeAnimationFunctionEaseInOutQuint(double t, double b, double c, double d) 83 | { 84 | if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b; 85 | return c/2*((t-=2)*t*t*t*t + 2) + b; 86 | } 87 | 88 | double JHKeyframeAnimationFunctionEaseInSine(double t, double b, double c, double d) 89 | { 90 | return -c * cos(t/d * (M_PI_2)) + c + b; 91 | } 92 | 93 | double JHKeyframeAnimationFunctionEaseOutSine(double t, double b, double c, double d) 94 | { 95 | return c * sin(t/d * (M_PI_2)) + b; 96 | } 97 | 98 | double JHKeyframeAnimationFunctionEaseInOutSine(double t, double b, double c, double d) 99 | { 100 | return -c/2 * (cos(M_PI*t/d) - 1) + b; 101 | } 102 | 103 | double JHKeyframeAnimationFunctionEaseInExpo(double t, double b, double c, double d) 104 | { 105 | return (t==0) ? b : c * pow(2, 10 * (t/d - 1)) + b; 106 | } 107 | 108 | double JHKeyframeAnimationFunctionEaseOutExpo(double t, double b, double c, double d) 109 | { 110 | return (t==d) ? b+c : c * (-pow(2, -10 * t/d) + 1) + b; 111 | } 112 | 113 | double JHKeyframeAnimationFunctionEaseInOutExpo(double t, double b, double c, double d) 114 | { 115 | if (t==0) return b; 116 | if (t==d) return b+c; 117 | if ((t/=d/2) < 1) return c/2 * pow(2, 10 * (t - 1)) + b; 118 | return c/2 * (-pow(2, -10 * --t) + 2) + b; 119 | } 120 | 121 | double JHKeyframeAnimationFunctionEaseInCirc(double t, double b, double c, double d) 122 | { 123 | return -c * (sqrt(1 - (t/=d)*t) - 1) + b; 124 | } 125 | 126 | double JHKeyframeAnimationFunctionEaseOutCirc(double t, double b, double c, double d) 127 | { 128 | return c * sqrt(1 - (t=t/d-1)*t) + b; 129 | } 130 | 131 | double JHKeyframeAnimationFunctionEaseInOutCirc(double t, double b, double c, double d) 132 | { 133 | if ((t/=d/2) < 1) return -c/2 * (sqrt(1 - t*t) - 1) + b; 134 | return c/2 * (sqrt(1 - (t-=2)*t) + 1) + b; 135 | } 136 | 137 | double JHKeyframeAnimationFunctionEaseInElastic(double t, double b, double c, double d) 138 | { 139 | double s = 1.70158; double p=0; double a=c; 140 | 141 | if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; 142 | if (a < fabs(c)) { a=c; s=p/4; } 143 | else s = p/(2*M_PI) * asin (c/a); 144 | return -(a*pow(2,10*(t-=1)) * sin( (t*d-s)*(2*M_PI)/p )) + b; 145 | } 146 | 147 | double JHKeyframeAnimationFunctionEaseOutElastic(double t, double b, double c, double d) 148 | { 149 | double s=1.70158, p=0, a=c; 150 | if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; 151 | if (a < fabs(c)) { a=c; s=p/4; } 152 | else s = p/(2*M_PI) * asin (c/a); 153 | return a*pow(2,-10*t) * sin( (t*d-s)*(2*M_PI)/p ) + c + b; 154 | } 155 | 156 | double JHKeyframeAnimationFunctionEaseInOutElastic(double t, double b, double c, double d) 157 | { 158 | double s=1.70158, p=0, a=c; 159 | if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5); 160 | if (a < fabs(c)) { a=c; s=p/4; } 161 | else s = p/(2*M_PI) * asin(c/a); 162 | if (t < 1) return -.5*(a*pow(2,10*(t-=1)) * sin( (t*d-s)*(2*M_PI)/p )) + b; 163 | return a*pow(2,-10*(t-=1)) * sin( (t*d-s)*(2*M_PI)/p )*.5 + c + b; 164 | } 165 | 166 | double JHKeyframeAnimationFunctionEaseInBack(double t, double b, double c, double d) 167 | { 168 | const double s = 1.70158; 169 | return c*(t/=d)*t*((s+1)*t - s) + b; 170 | } 171 | 172 | double JHKeyframeAnimationFunctionEaseOutBack(double t, double b, double c, double d) 173 | { 174 | const double s = 1.70158; 175 | return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; 176 | } 177 | 178 | double JHKeyframeAnimationFunctionEaseInOutBack(double t, double b, double c, double d) 179 | { 180 | double s = 1.70158; 181 | if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; 182 | return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; 183 | } 184 | 185 | double JHKeyframeAnimationFunctionEaseInBounce(double t, double b, double c, double d) 186 | { 187 | return c - JHKeyframeAnimationFunctionEaseOutBounce(d-t, 0, c, d) + b; 188 | } 189 | 190 | double JHKeyframeAnimationFunctionEaseOutBounce(double t, double b, double c, double d) 191 | { 192 | if ((t/=d) < (1/2.75)) { 193 | return c*(7.5625*t*t) + b; 194 | } else if (t < (2/2.75)) { 195 | return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; 196 | } else if (t < (2.5/2.75)) { 197 | return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; 198 | } else { 199 | return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; 200 | } 201 | } 202 | 203 | double JHKeyframeAnimationFunctionEaseInOutBounce(double t, double b, double c, double d) 204 | { 205 | if (t < d/2) 206 | return JHKeyframeAnimationFunctionEaseInBounce (t*2, 0, c, d) * .5 + b; 207 | else 208 | return JHKeyframeAnimationFunctionEaseOutBounce(t*2-d, 0, c, d) * .5 + c*.5 + b; 209 | } 210 | 211 | #pragma clang diagnostic pop 212 | -------------------------------------------------------------------------------- /JHChainableAnimations/JHKeyframeAnimationFunctions.h: -------------------------------------------------------------------------------- 1 | // 2 | // JHKeyframeAnimationFunctions.h 3 | // JHChainableAnimations 4 | // 5 | // Created by Jeff Hurray on 12/21/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | typedef double (*JHKeyframeAnimationFunction)(double, double, double, double); 10 | 11 | double JHKeyframeAnimationFunctionLinear(double t,double b, double c, double d); 12 | 13 | double JHKeyframeAnimationFunctionEaseInQuad(double t,double b, double c, double d); 14 | double JHKeyframeAnimationFunctionEaseOutQuad(double t,double b, double c, double d); 15 | double JHKeyframeAnimationFunctionEaseInOutQuad(double t,double b, double c, double d); 16 | 17 | double JHKeyframeAnimationFunctionEaseInCubic(double t,double b, double c, double d); 18 | double JHKeyframeAnimationFunctionEaseOutCubic(double t,double b, double c, double d); 19 | double JHKeyframeAnimationFunctionEaseInOutCubic(double t, double b, double c, double d); 20 | 21 | double JHKeyframeAnimationFunctionEaseInQuart(double t, double b, double c, double d); 22 | double JHKeyframeAnimationFunctionEaseOutQuart(double t, double b, double c, double d); 23 | double JHKeyframeAnimationFunctionEaseInOutQuart(double t, double b, double c, double d); 24 | 25 | double JHKeyframeAnimationFunctionEaseInQuint(double t, double b, double c, double d); 26 | double JHKeyframeAnimationFunctionEaseOutQuint(double t, double b, double c, double d); 27 | double JHKeyframeAnimationFunctionEaseInOutQuint(double t, double b, double c, double d); 28 | 29 | double JHKeyframeAnimationFunctionEaseInSine(double t, double b, double c, double d); 30 | double JHKeyframeAnimationFunctionEaseOutSine(double t, double b, double c, double d); 31 | double JHKeyframeAnimationFunctionEaseInOutSine(double t, double b, double c, double d); 32 | 33 | double JHKeyframeAnimationFunctionEaseInExpo(double t, double b, double c, double d); 34 | double JHKeyframeAnimationFunctionEaseOutExpo(double t, double b, double c, double d); 35 | double JHKeyframeAnimationFunctionEaseInOutExpo(double t, double b, double c, double d); 36 | 37 | double JHKeyframeAnimationFunctionEaseInCirc(double t, double b, double c, double d); 38 | double JHKeyframeAnimationFunctionEaseOutCirc(double t, double b, double c, double d); 39 | double JHKeyframeAnimationFunctionEaseInOutCirc(double t, double b, double c, double d); 40 | 41 | double JHKeyframeAnimationFunctionEaseInElastic(double t, double b, double c, double d); 42 | double JHKeyframeAnimationFunctionEaseOutElastic(double t, double b, double c, double d); 43 | double JHKeyframeAnimationFunctionEaseInOutElastic(double t, double b, double c, double d); 44 | 45 | double JHKeyframeAnimationFunctionEaseInBack(double t, double b, double c, double d); 46 | double JHKeyframeAnimationFunctionEaseOutBack(double t, double b, double c, double d); 47 | double JHKeyframeAnimationFunctionEaseInOutBack(double t, double b, double c, double d); 48 | 49 | double JHKeyframeAnimationFunctionEaseInBounce(double t, double b, double c, double d); 50 | double JHKeyframeAnimationFunctionEaseOutBounce(double t, double b, double c, double d); 51 | double JHKeyframeAnimationFunctionEaseInOutBounce(double t, double b, double c, double d); 52 | -------------------------------------------------------------------------------- /JHChainableAnimations/UIView+ChainableAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+ChainableAnimator.swift 3 | // JHChainableAnimations-iOS 4 | // 5 | // Created by Jeffrey Hurray on 10/17/17. 6 | // Copyright © 2017 jhurray. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public extension UIView { 13 | 14 | public var animator: ChainableAnimator { 15 | return ChainableAnimator(view: self) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /JHChainableAnimationsTests/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 | -------------------------------------------------------------------------------- /JHChainableAnimationsTests/JHChainableAnimationsTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // JHChainableAnimationsTests.m 3 | // JHChainableAnimationsTests 4 | // 5 | // Created by Jeff Hurray on 12/21/16. 6 | // Copyright © 2016 jhurray. All rights reserved. 7 | // 8 | 9 | #import 10 | @import JHChainableAnimations; 11 | 12 | 13 | #import "JHChainableAnimator.h" 14 | #import "JHAnimationChainLink.h" 15 | 16 | typedef NS_ENUM(NSInteger, JHChainableAnimatorContinuationMode) { 17 | JHChainableAnimatorContinuationModeContinue, 18 | JHChainableAnimatorContinuationModePause, 19 | JHChainableAnimatorContinuationModeStop, 20 | }; 21 | 22 | @interface JHChainableAnimator (JH_Test) 23 | 24 | @property (nonatomic, weak) UIView *view; 25 | @property (strong, nonatomic) NSMutableArray *animationChainLinks; 26 | @property (strong, nonatomic) NSMapTable *animationDurationMapping; 27 | @property (atomic, assign, getter=isAnimating) BOOL animating; 28 | @property (atomic, assign, getter=isPaused) BOOL paused; 29 | @property (nonatomic, assign) JHChainableAnimatorContinuationMode continuationMode; 30 | @property (nonatomic, readonly) JHAnimationChainLink *currentAnimationLink; 31 | 32 | @end 33 | 34 | 35 | @interface JHChainableAnimationsTests : XCTestCase 36 | 37 | @property (nonatomic, strong) UIView *superView; 38 | @property (nonatomic, strong) JHChainableAnimator *animator; 39 | @property (nonatomic, weak, readonly) JHChainableAnimator *weakAnimator; 40 | 41 | @end 42 | 43 | #define kDuration 0.01 44 | 45 | @implementation JHChainableAnimationsTests 46 | 47 | - (__weak JHChainableAnimator *)weakAnimator 48 | { 49 | __weak JHChainableAnimator *weakAnimator = self.animator; 50 | return weakAnimator; 51 | } 52 | 53 | 54 | - (void)setUp 55 | { 56 | [super setUp]; 57 | UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)]; 58 | UIView *superView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; 59 | [superView addSubview:view]; 60 | self.superView = superView; 61 | JHChainableAnimator *animator = [[JHChainableAnimator alloc] initWithView:view]; 62 | self.animator = animator; 63 | } 64 | 65 | 66 | - (void)tearDown 67 | { 68 | [self.animator.view removeFromSuperview]; 69 | [super tearDown]; 70 | } 71 | 72 | 73 | - (void)testWithBlock:(void(^)(void(^finished)()))block 74 | { 75 | static NSString * const kJHTestDescription = @"JHTest"; 76 | XCTestExpectation *expectation = [self expectationWithDescription:kJHTestDescription]; 77 | block(^{ 78 | [expectation fulfill]; 79 | }); 80 | [self waitForExpectationsWithTimeout:1.0 handler:^(NSError * _Nullable error) { 81 | XCTAssertNil(error); 82 | }]; 83 | } 84 | 85 | 86 | - (void)testMoveX 87 | { 88 | [self testWithBlock:^(void (^finished)(void)) { 89 | self.animator.moveX(20).animateWithCompletion(kDuration, ^{ 90 | XCTAssertEqual(self.weakAnimator.view.frame.origin.x, 20); 91 | finished(); 92 | }); 93 | }]; 94 | } 95 | 96 | 97 | - (void)testMakeX 98 | { 99 | [self testWithBlock:^(void (^finished)(void)) { 100 | self.animator.makeX(50).animateWithCompletion(kDuration, ^{ 101 | XCTAssertEqual(self.weakAnimator.view.frame.origin.x, 50); 102 | finished(); 103 | }); 104 | }]; 105 | } 106 | 107 | 108 | - (void)testContinuationMode 109 | { 110 | XCTAssertEqual(self.animator.continuationMode, JHChainableAnimatorContinuationModeContinue); 111 | [self testWithBlock:^(void (^finished)(void)) { 112 | self.animator.moveX(10).easeIn.thenAfter(kDuration).makeSize(12, 12).preAnimationBlock(^{ 113 | XCTAssertEqual(self.weakAnimator.continuationMode, JHChainableAnimatorContinuationModeContinue); 114 | [self.weakAnimator pause]; 115 | }).thenAfter(kDuration).makeSize(12, 12).preAnimationBlock(^{ 116 | XCTFail(@"Shouldnt reach here"); 117 | }).animate(kDuration); 118 | 119 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDuration * 2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 120 | XCTAssertEqual(self.weakAnimator.continuationMode, JHChainableAnimatorContinuationModePause); 121 | XCTAssertTrue(self.animator.isAnimating); 122 | XCTAssertTrue(self.animator.isPaused); 123 | finished(); 124 | }); 125 | }]; 126 | } 127 | 128 | 129 | - (void)testResume 130 | { 131 | [self testWithBlock:^(void (^finished)(void)) { 132 | self.animator.moveX(10).easeIn.thenAfter(kDuration).makeSize(12, 12).preAnimationBlock(^{ 133 | [self.weakAnimator pause]; 134 | }).thenAfter(kDuration).makeSize(12, 12).animateWithCompletion(kDuration, ^{ 135 | XCTAssertTrue(self.animator.isAnimating); 136 | XCTAssertFalse(self.animator.isPaused); 137 | finished(); 138 | }); 139 | 140 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDuration * 2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 141 | XCTAssertTrue(self.animator.isAnimating); 142 | XCTAssertTrue(self.animator.isPaused); 143 | [self.animator resume]; 144 | }); 145 | }]; 146 | } 147 | 148 | @end 149 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jeff Hurray 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 11 | 12 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 28 |
6 | 7 | 9 | 10 |
14 | 15 | 17 | 18 |
22 | 23 | 25 | 26 |
29 | 30 | ![language](https://img.shields.io/badge/Language-Objective--C-8E44AD.svg) 31 | ![language](https://img.shields.io/badge/Language-Swift-8E44AD.svg) 32 | ![Version](https://img.shields.io/badge/Pod-%20v3.0.1%20-96281B.svg) 33 | ![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg) 34 | ![MIT License](https://img.shields.io/github/license/mashape/apistatus.svg) 35 | ![Platform](https://img.shields.io/badge/platform-%20iOS%20-lightgrey.svg) 36 | ![Platform](https://img.shields.io/badge/platform-%20tvOS%20-lightgrey.svg) 37 | 38 | ## Whats new in version 3.x? 39 | * Swiftier syntax 40 | * Swift 4 support 41 | * Bug fixes and improvements 42 | 43 | ## Whats new in version 2.x? 44 | * Re-architected from the ground up, no more hacking UIView 🛠 45 | * Added pre-animation and post-animation hooks for each animation step ⛓ 46 | * Added pause and resume functionality ⏯ 47 | * Added repeat animation functionality 🔂 48 | * Added friendly Swift interface in separate framework 🔥🕊 49 | 50 | ## Whats wrong with animations? 51 | 52 | CAAnimations and UIView animations are extremely powerful, but it is difficult to chain multiple animations together, especially while changing anchor points. 53 | 54 | Furthermore, complicated animations are difficult to read. 55 | 56 | Say I want to move myView 50 pixels to the right with spring and then change the background color with inward easing when the movement has finished: 57 | 58 | ### The Old Way 59 | 60 | ```objective-c 61 | [UIView animateWithDuration:1.0 62 | delay:0.0 63 | usingSpringWithDamping:0.8 64 | initialSpringVelocity:1.0 65 | options:0 animations:^{ 66 | CGPoint newPosition = self.myView.frame.origin; 67 | newPosition.x += 50; 68 | self.myView.frame.origin = newPosition; 69 | } completion:^(BOOL finished) { 70 | [UIView animateWithDuration:0.5 71 | delay:0.0 72 | options:UIViewAnimationOptionCurveEaseIn 73 | animations:^{ 74 | self.myView.backgroundColor = [UIColor purpleColor]; 75 | } completion:nil]; 76 | }]; 77 | ``` 78 | 79 | Thats pretty gross huh... With JHChainableAnimations it is one line of code. 80 | 81 | ### Using JHChainableAnimations 82 | 83 | ```objective-c 84 | JHChainableAnimator *animator = [[JHChainableAnimator alloc] initWithView:self.myView]; 85 | animator.moveX(50).spring.thenAfter(1.0).makeBackground([UIColor purpleColor]).easeIn.animate(0.5); 86 | ``` 87 | 88 | There are also a lot of really good animation libraries out there such as [RBBAnimation](https://github.com/robb/RBBAnimation), [DCAnimationKit](https://github.com/daltoniam/DCAnimationKit), and [PMTween](https://github.com/poetmountain/PMTween), but they still fall short of having powerful chainable animations AND easy to read/write syntax. 89 | 90 | ## Installation 91 | There are a few ways you can add this framework to your project. The Objective-C framework is called `JHChainableAnimations` and the Swift framework is called `ChainableAnimations`. More notes on Swift usage can be found [here](#swift) 92 | 93 | ### Cocoapods 94 | 95 | ##### Objective-C 96 | 97 | ```ruby 98 | pod 'JHChainableAnimations', '~> 3.0.1' 99 | ``` 100 | Then add the following: 101 | 102 | ```objective-c 103 | #import 104 | ``` 105 | 106 | 107 | ##### Swift 108 | ```ruby 109 | pod 'ChainableAnimations', '~> 3.0.1' 110 | ``` 111 | Then add the following: 112 | 113 | ```swift 114 | import ChainableAnimations 115 | ``` 116 | 117 | ### Carthage 118 | Add the following to your `Cartfile` 119 | 120 | ```ruby 121 | github "jhurray/JHChainableAnimations" ~> 3.0.1 122 | ``` 123 | ##### Objective-C 124 | Add the `JHChainableAnimations` framework to your project. 125 | 126 | ##### Swift 127 | Add the `ChainableAnimations` framework to your project. 128 | 129 | 130 | ### Add to project Manually 131 | Either clone the repo and manually add the Files in [JHChainableAnimations](./JHChainableAnimations) 132 | 133 | 134 | ## Usage 135 | 136 | ### Creating an Animator 137 | 138 | To create an instance of `JHChainableAnimator` you must call the `initWithView:` method. 139 | 140 | ```objective-c 141 | JHChainableAnimator *animator = [[JHChainableAnimator alloc] initWithView:self.myView]; 142 | ``` 143 | 144 | ### Animating 145 | 146 | Chainable properties like `moveX(x)` must come between the view and the `animate(t)` function 147 | 148 | Below is an example of how to double an objects size over the course of one second. 149 | 150 | ```objective-c 151 | animator.makeScale(2.0).animate(1.0); 152 | ``` 153 | 154 | ### Combining Animations 155 | 156 | If you want to move the view while you scale it, add another chainable property. Order is not important 157 | 158 | ```objective-c 159 | animator.makeScale(2.0).moveXY(100, 50).animate(1.0); 160 | // the same as animator.moveXY(100, 50).makeScale(2.0).animate(1.0); 161 | ``` 162 | 163 | A full list of chainable properties can be found [here](#chainables) 164 | 165 | ### Chaining Animations 166 | 167 | To chain animations seperate the chains with the `thenAfter(t)` function. 168 | 169 | Below is an example of how to scale and object for 0.5 seconds, and then move it for 1 second when that is done. 170 | 171 | ```objective-c 172 | animator.makeScale(2.0).thenAfter(0.5).moveXY(100, 50).animate(1.0); 173 | ``` 174 | 175 | ### Animation Effects 176 | 177 | To add an animation effect, call the effect method after the chainable property you want it to apply to. 178 | 179 | Below is an example of scaling a view with a spring effect. 180 | 181 | ```objective-c 182 | animator.makeScale(2.0).spring.animate(1.0); 183 | ``` 184 | 185 | If you add 2 to the same chainable property the second will cancel the first out. 186 | 187 | ```objective-c 188 | animator.makeScale(2.0).bounce.spring.animate(1.0); 189 | // The same as animator.makeScale(2.0).spring.animate(1.0); 190 | ``` 191 | 192 | A full list of animation effect properties can be found [here](#effects) 193 | 194 | ### Anchoring 195 | To anchor your view call an achoring method at some point in an animation chain. Like effects, calling one after another in the same chain will cancel the first out. 196 | 197 | Below is an example of rotating a view around different anchor points 198 | 199 | ```objective-c 200 | animator.rotateZ(180).anchorTopLeft.thenAfter(1.0).rotateZ(90).anchorCenter.animate(1.0); 201 | 202 | // animator.rotateZ(90).anchorTopLeft.anchorCenter == animator.rotateZ(90).anchorCenter 203 | ``` 204 | 205 | A full list of anchor properties can be found [here](#anchors) 206 | 207 | ### Delays 208 | To delay an animation call the `wait(t)` or `delay(t)` chainable property. 209 | 210 | Below is an example of moving a view after a delay of 0.5 seconds 211 | 212 | ```objective-c 213 | animator.moveXY(100, 50).wait(0.5).animate(1.0); 214 | // The same as animator.moveXY(100, 50).delay(0.5).animate(1.0); 215 | ``` 216 | 217 | ### Completion 218 | To run code after an animation finishes set the `completionBlock` property of your animator or call the `animateWithCompletion(t, completion)*`function. 219 | 220 | ```objective-c 221 | animator.makeX(0).animateWithCompletion(1.0, ^{ 222 | NSLog(@"Animation Done"); 223 | }); 224 | ``` 225 | 226 | Is the same as: 227 | 228 | ```objective-c 229 | animator.completionBlock = ^{ 230 | NSLog(@"Animation Done"); 231 | }; 232 | animator.makeX(0).animate(1.0); 233 | ``` 234 | 235 | ### Repeating Animations 236 | You can repeat an animation by replacing the `thenAfter(time)` method with the `repeat(time, count)` method. This will repeat the previously defined animations. 237 | 238 | ```objective-c 239 | // The animator will double its scale 3 times for 0.5 seconds each before it calls `moveXY` and finishes the animation 240 | animator.makeScale(2.0).repeat(0.5, 3).moveXY(100, 50).animate(1.0); 241 | ``` 242 | 243 | You can repeat the last part of an animation by calling `animateWithRepeat(time, count)`. 244 | 245 | ```objective-c 246 | // The animator will double its scale then rotate by 90 degrees 3 times for 1 second each. 247 | animator.makeScale(2.0).thenAfter(0.5).rotate(90). animateWithRepeat(1.0, 3); 248 | ``` 249 | 250 | ### Pausing and Cancelling 251 | To Pause the animation, call the `pause` method on the animator. When you call pause, the current animation in the chain will complete but nothing beyod that will be executed. You can use the `isPaused` and `isAnimating` readonly properties to inspect state. If an animation is paused but not stopped, it will still evaluate as `animating`. 252 | 253 | To resume in a paused state, call the `resume` method on the animator. 254 | 255 | To stop animation and clear state, call the `stop` method on the animator. 256 | 257 | ```objective-c 258 | // In this case the `moveX` animation will execute but the `moveY` will not 259 | // If `resume` is called `moveY` will be executed 260 | // If `stop` is called, nothing will be executed and the animator will get a fresh state 261 | animator.moveX(10).thenAfter(0.5).moveY(10).animate(0.5); 262 | [animator pause]; 263 | ``` 264 | 265 | ### Callbacks 266 | You can hook into the different steps of the animation process by calling the `preAnimationBlock(block)`, `animationBlock(block)`, and `postAnimationBlock(block)` methods. All take a simple block `void(^)()` as an argument. Order of calling these in the animation chain does not matter. 267 | 268 | ```objective-c 269 | animator.moveX(10).preAnimationBlock(^{ 270 | NSLog(@"before the first animation"); 271 | }).thenAfter(1.0).postAnimationBlock(^{ 272 | NSLog(@"After the second animation"); 273 | }).moveY(10).animate(1.0); 274 | ``` 275 | 276 | 277 | ### Bezier Paths 278 | You can also animate a view along a [UIBezierPath](https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/BezierPaths/BezierPaths.html). Create a `UIBezierPath *` instance, then add points or curves or lines to it and use it in a chainable property. 279 | 280 | ```objective-c 281 | UIBezierPath *path = [UIBezierPath bezierPath]; 282 | [path moveToPoint:self.myView.center]; 283 | [path addLineToPoint:CGPointMake(25, 400)]; 284 | [path addLineToPoint:CGPointMake(300, 500)]; 285 | animator.moveOnPath(path).animate(1.0); 286 | ``` 287 | Animation effects do not work on path movements. 288 | 289 | 290 | ## Using with Auto Layout 291 | 292 | ### Transforms 293 | 294 | Use the **transform** chainable properties. These are better for views constrained with Autolayout. You should not mix these with other chainable properties 295 | 296 | ```objective-c 297 | animatorForViewWithConstraints.transformX(50).transformScale(2).animate(1.0); 298 | ``` 299 | 300 | ## Using with Swift 301 | 302 | Using JHChainableAnimations with [Swift](https://developer.apple.com/swift/) is now a little more readable in version `2.x`. I created a separate framework for swift that provides a class called `ChainableAnimator`. This is a thin wrapper over `JHChainableAnimator` that has a slightly more readable syntax. 303 | 304 | ```swift 305 | let animator = ChainableAniamtor(view: myView) 306 | animator.moveX(x: 50).thenAfter(t: 1.0).rotate(angle: 360).bounce.animate(t:1.0) 307 | ``` 308 | All Objective-C methods map to a swift method. 309 | 310 | ## Chainable Properties 311 | 312 | 313 | 314 | 317 | 320 | 323 | 324 | 325 | 328 | 331 | 334 | 335 | 336 | 339 | 342 | 345 | 346 | 347 | 350 | 353 | 356 | 357 | 358 | 361 | 364 | 367 | 368 | 369 | 372 | 375 | 378 | 379 | 380 | 383 | 386 | 389 | 390 | 391 | 394 | 397 | 400 | 401 | 402 | 405 | 408 | 411 | 412 | 413 | 416 | 419 | 422 | 423 | 424 | 427 | 430 | 433 | 434 | 435 | 438 | 441 | 444 | 445 | 446 | 449 | 452 | 455 | 456 | 459 | 462 | 465 | 466 | 467 | 470 | 473 | 476 | 477 | 478 | 481 | 484 | 487 | 488 | 489 | 492 | 495 | 498 | 499 | 500 | 503 | 506 | 509 | 510 | 511 | 514 | 517 | 520 | 521 | 522 | 525 | 528 | 531 | 532 | 533 | 536 | 539 | 542 | 543 | 544 | 547 | 550 | 553 | 554 | 555 | 558 | 561 | 564 | 565 | 566 | 569 | 572 | 575 | 576 | 577 | 580 | 583 | 586 | 587 | 588 | 591 | 594 | 597 | 598 | 599 | 602 | 605 | 608 | 609 | 610 | 613 | 616 | 619 | 620 | 621 | 624 | 627 | 630 | 631 | 632 | 635 | 638 | 641 | 642 | 643 | 646 | 649 | 652 | 653 | 654 | 657 | 660 | 663 | 664 | 665 | 668 | 671 | 674 | 675 | 676 | 679 | 682 | 685 | 686 | 687 | 690 | 693 | 696 | 697 | 698 | 701 | 704 | 707 | 708 | 709 | 712 | 715 | 718 | 719 | 720 | 723 | 726 | 729 | 730 | 731 | 734 | 737 | 740 | 741 | 742 | 745 | 748 | 751 | 752 |
315 | Property 316 | 318 | Takes a... 319 | 321 | Usage 322 |
326 | - (JHChainableRect) makeFrame; 327 | 329 | CGRect 330 | 332 | animator.makeFrame(rect).animate(1.0); 333 |
337 | - (JHChainableRect) makeBounds; 338 | 340 | CGRect 341 | 343 | animator.makeBounds(rect).animate(1.0); 344 |
348 | - (JHChainableSize) makeSize; 349 | 351 | (CGFloat: width, CGFloat: height) 352 | 354 | animator.makeSize(10, 20).animate(1.0); 355 |
359 | - (JHChainablePoint) makeOrigin; 360 | 362 | (CGFloat: x, CGFloat: y) 363 | 365 | animator.makeOrigin(10, 20).animate(1.0); 366 |
370 | - (JHChainablePoint) makeCenter; 371 | 373 | (CGFloat: x, CGFloat: y) 374 | 376 | animator.makeCenter(10, 20).animate(1.0); 377 |
381 | - (JHChainableFloat) makeX; 382 | 384 | (CGFloat: f) 385 | 387 | animator.makeX(10).animate(1.0); 388 |
392 | - (JHChainableFloat) makeY; 393 | 395 | (CGFloat: f) 396 | 398 | animator.makeY(10).animate(1.0); 399 |
403 | - (JHChainableFloat) makeWidth; 404 | 406 | (CGFloat: f) 407 | 409 | animator.makeWidth(10).animate(1.0); 410 |
414 | - (JHChainableFloat) makeHeight; 415 | 417 | (CGFloat: f) 418 | 420 | animator.makeHeight(10).animate(1.0); 421 |
425 | - (JHChainableFloat) makeOpacity; 426 | 428 | (CGFloat: f) 429 | 431 | animator.makeOpacity(10).animate(1.0); 432 |
436 | - (JHChainableColor) makeBackground; 437 | 439 | (UIColor: color) 440 | 442 | animator.makeBackground(color).animate(1.0); 443 |
447 | - (JHChainableColor) makeBorderColor; 448 | 450 | (UIColor: color) 451 | 453 | animator.makeBorderColor(color).animate(1.0); 454 |
457 | - (JHChainableFloat) makeBorderWidth; 458 | 460 | (CGFloat: f) 461 | 463 | animator.makeBorderWidth(3.0).animate(1.0); 464 |
468 | - (JHChainableFloat) makeCornerRadius; 469 | 471 | (CGFloat: f) 472 | 474 | animator.makeCornerRadius(3.0).animate(1.0); 475 |
479 | - (JHChainableFloat) makeScale; 480 | 482 | (CGFloat: f) 483 | 485 | animator.makeScale(2.0).animate(1.0); 486 |
490 | - (JHChainableFloat) makeScaleX; 491 | 493 | (CGFloat: f) 494 | 496 | animator.makeScaleX(2.0).animate(1.0); 497 |
501 | - (JHChainableFloat) makeScaleY; 502 | 504 | (CGFloat: f) 505 | 507 | animator.makeScaleY(2.0).animate(1.0); 508 |
512 | - (JHChainablePoint) makeAnchor; 513 | 515 | (CGFloat: x, CGFloat: y) 516 | 518 | animator.makeAnchor(0.5, 0.5).animate(1.0); 519 |
523 | - (JHChainableFloat) moveX; 524 | 526 | (CGFloat: f) 527 | 529 | animator.moveX(50).animate(1.0) 530 |
534 | - (JHChainableFloat) moveY; 535 | 537 | (CGFloat: f) 538 | 540 | animator.moveY(50).animate(1.0) 541 |
545 | - (JHChainablePoint) moveXY; 546 | 548 | (CGFloat: x, CGFloat: y) 549 | 551 | animator.moveXY(100, 50).animate(1.0) 552 |
556 | - (JHChainableFloat) moveHeight; 557 | 559 | (CGFloat: f) 560 | 562 | animator.moveHeight(50).animate(1.0) 563 |
567 | - (JHChainableFloat) moveWidth; 568 | 570 | (CGFloat: f) 571 | 573 | animator.moveWidth(50).animate(1.0) 574 |
578 | - (JHChainableDegrees) rotateX; 579 | 581 | (CGFloat: angle) #not radians! 582 | 584 | animator.rotateX(360).animate(1.0); 585 |
589 | - (JHChainableDegrees) rotateY; 590 | 592 | (CGFloat: angle) #not radians! 593 | 595 | animator.rotateY(360).animate(1.0); 596 |
600 | - (JHChainableDegrees) rotateZ; 601 | 603 | (CGFloat: angle) #not radians! 604 | 606 | animator.rotateZ(360).animate(1.0); 607 |
611 | - (JHChainablePolarCoordinate) movePolar; 612 | 614 | (CGFloat: radius, CGFloat: angle) 615 | 617 | animator.movePolar(30, 90).animate(1.0); 618 |
622 | - (JHChainableBezierPath) moveOnPath; 623 | 625 | (UIBezierPath *path) 626 | 628 | animator.moveOnPath(path).animate(1.0); 629 |
633 | - (JHChainableBezierPath) moveAndRotateOnPath; 634 | 636 | (UIBezierPath *path) 637 | 639 | animator.moveAndRotateOnPath(path).animate(1.0); 640 |
644 | - (JHChainableBezierPath) moveAndReverseRotateOnPath; 645 | 647 | (UIBezierPath *path) 648 | 650 | animator.moveAndReverseRotateOnPath(path).animate(1.0); 651 |
655 | - (JHChainableFloat) transformX; 656 | 658 | (CGFloat f) 659 | 661 | animator.transformX(50).animate(1.0); 662 |
666 | - (JHChainableFloat) transformX; 667 | 669 | (CGFloat f) 670 | 672 | animator.transformX(50).animate(1.0); 673 |
677 | - (JHChainableFloat) transformY; 678 | 680 | (CGFloat f) 681 | 683 | animator.transformY(50).animate(1.0); 684 |
688 | - (JHChainableFloat) transformZ; 689 | 691 | (CGFloat f) 692 | 694 | animator.transformZ(50).animate(1.0); 695 |
699 | - (JHChainablePoint) transformXY; 700 | 702 | (CGFloat x, CGFloat y) 703 | 705 | animator.transformXY(50, 100).animate(1.0); 706 |
710 | - (JHChainableFloat) transformScale; 711 | 713 | (CGFloat f) 714 | 716 | animator.transformScale(50).animate(1.0); 717 |
721 | - (JHChainableFloat) transformScaleX; 722 | 724 | (CGFloat f) 725 | 727 | animator.transformScaleX(50).animate(1.0); 728 |
732 | - (JHChainableFloat) transformScaleY; 733 | 735 | (CGFloat f) 736 | 738 | animator.transformScaleY(50).animate(1.0); 739 |
743 | - (JHChainableAnimator *) transformIdentity; 744 | 746 | Nothing 747 | 749 | animator.transformIdentity.animate(1.0); 750 |
753 | 754 | ## Animation Effects 755 | 756 | 757 | 758 | 759 | A quick look at these funcs can be found [here](http://easings.net/) 760 | 761 | These animation functions were taken from a cool keyframe animation library that can be found [here](https://github.com/NachoSoto/NSBKeyframeAnimation) 762 | 763 | They are based off of JQuery easing functions that can be found [here](http://gsgd.co.uk/sandbox/jquery/easing/jquery.easing.1.3.js) 764 | 765 | ## Anchoring 766 | 767 | 768 | 769 | Info on anchoring can be found [here](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/CoreAnimationBasics/CoreAnimationBasics.html#//apple_ref/doc/uid/TP40004514-CH2-SW3) 770 | 771 | 772 | ## To Do 773 | I have gotten a ton of great suggestions of what to do next. If you think this is missing anything please let me know! The following is what I plan on working on in no particular order. 774 | 775 | * OSX port 776 | * Constraint animator 777 | 778 | ## Contact Info && Contributing 779 | 780 | Feel free to email me at [jhurray33@gmail.com](mailto:jhurray33@gmail.com?subject=JHChainableAnimations). I'd love to hear your thoughts on this, or see examples where this has been used. 781 | 782 | [MIT License](./LICENSE) 783 | -------------------------------------------------------------------------------- /img/JHChainableAnimationsAnchors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/JHChainableAnimations/14e2248a93514a35279b1e73c7d3b9970e42c8d3/img/JHChainableAnimationsAnchors.png -------------------------------------------------------------------------------- /img/JHChainableAnimationsEasing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/JHChainableAnimations/14e2248a93514a35279b1e73c7d3b9970e42c8d3/img/JHChainableAnimationsEasing.png -------------------------------------------------------------------------------- /img/JHChainableAnimationsEffects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/JHChainableAnimations/14e2248a93514a35279b1e73c7d3b9970e42c8d3/img/JHChainableAnimationsEffects.png -------------------------------------------------------------------------------- /img/JHChainableAnimationsExample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/JHChainableAnimations/14e2248a93514a35279b1e73c7d3b9970e42c8d3/img/JHChainableAnimationsExample1.png -------------------------------------------------------------------------------- /img/JHChainableAnimationsExample2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/JHChainableAnimations/14e2248a93514a35279b1e73c7d3b9970e42c8d3/img/JHChainableAnimationsExample2.png -------------------------------------------------------------------------------- /img/JHChainableAnimationsExample3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/JHChainableAnimations/14e2248a93514a35279b1e73c7d3b9970e42c8d3/img/JHChainableAnimationsExample3.png -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/JHChainableAnimations/14e2248a93514a35279b1e73c7d3b9970e42c8d3/img/logo.png --------------------------------------------------------------------------------