├── Resources ├── AnimatorCover.png └── AnimatorDemoExample.gif ├── Animator Demo.playground ├── timeline.xctimeline ├── contents.xcplayground ├── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── Vishal.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── Contents.swift └── Sources │ └── Animator.swift ├── LICENSE ├── README.md └── Source └── Animator.swift /Resources/AnimatorCover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishalvshekkar/Animator/HEAD/Resources/AnimatorCover.png -------------------------------------------------------------------------------- /Resources/AnimatorDemoExample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishalvshekkar/Animator/HEAD/Resources/AnimatorDemoExample.gif -------------------------------------------------------------------------------- /Animator Demo.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Animator Demo.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Animator Demo.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Animator Demo.playground/playground.xcworkspace/xcuserdata/Vishal.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishalvshekkar/Animator/HEAD/Animator Demo.playground/playground.xcworkspace/xcuserdata/Vishal.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Vishal V. Shekkar 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 | -------------------------------------------------------------------------------- /Animator Demo.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import UIKit 4 | import PlaygroundSupport 5 | 6 | let containerView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 400, height: 400)) 7 | containerView.backgroundColor = UIColor.black 8 | PlaygroundPage.current.liveView = containerView 9 | 10 | let smallView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 20, height: 20)) 11 | smallView.backgroundColor = UIColor.cyan 12 | containerView.addSubview(smallView) 13 | 14 | let smallView3 = UIView(frame: CGRect(x: 380, y: 380, width: 20, height: 20)) 15 | smallView3.backgroundColor = UIColor.red 16 | containerView.addSubview(smallView3) 17 | 18 | Animator.addAnimations() { 19 | smallView.frame.origin = CGPoint(x: 380, y: 0) 20 | smallView3.frame.origin = CGPoint(x: 0, y: 380) 21 | }.addAnimations() { 22 | smallView.frame.origin = CGPoint(x: 380, y: 380) 23 | smallView3.frame.origin = CGPoint(x: 0, y: 0) 24 | }.addAnimations(usingSpringWithDamping: 0.7, initialSpringVelocity: 0) { 25 | smallView.frame.origin = CGPoint(x: 190, y: 190) 26 | smallView3.frame.origin = CGPoint(x: 190, y: 190) 27 | }.addAnimations(usingSpringWithDamping: 0.7, initialSpringVelocity: 0) { 28 | smallView.frame.origin = CGPoint(x: 380, y: 0) 29 | smallView3.frame.origin = CGPoint(x: 0, y: 380) 30 | }.addAnimations(usingSpringWithDamping: 0.7, initialSpringVelocity: 0) { 31 | smallView.frame.origin = CGPoint(x: 380, y: 380) 32 | smallView3.frame.origin = CGPoint(x: 0, y: 0) 33 | }.addAnimations(withDuration: 1, delay: 1, options: [UIViewAnimationOptions.curveLinear], animations: { 34 | smallView.frame.origin = CGPoint(x: 260, y: 380) 35 | smallView3.frame.size = CGSize(width: 80, height: 80) 36 | }).animate { 37 | print("Completed") 38 | } 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![alt tag](https://raw.githubusercontent.com/vishalvshekkar/Animator/master/Resources/AnimatorCover.png) 2 | 3 | # Animator 4 | Animator is a block-based `UIView` animation helper which enables complex animations to be performed with ease. 5 | 6 | When there's a complex multi-step animation that needs to be performed, we usually break these down into smaller animatable steps and animate these smaller steps one after the other. To do this, every new animation step needs to be invoked after the completion of the last step. When using the stock `UIView` `animate` methods, the code gets very ugly and unmanageable with a complex animation. If you add every step into a method and invoke these methods in the completion blocks of the last animation step, the code readability reduces slightly as there's no continuation. 7 | 8 | As a developer, when you visualize an animation, and when you need to prototype that and polish over it, having a readable codebase helps a lot. It helps you better understand the steps and fix bugs easily. That's where `Animator` comes in to help. 9 | 10 | #Using `Animator` 11 | 12 | Most simply, call `addAnimations:` with a closure that contains the animation code on `Animator`. Invoke `animate` to begin animating. This animates whatever statements are in the block with a 1s duration (default). 13 | 14 | ``` 15 | Animator.addAnimations() { 16 | //Animation code 17 | }.animate() 18 | ``` 19 | 20 | 1. Every block of animations that need to be performed during one animation cycle needs to be passed as a parameter to `addAnimations:` 21 | 2. `addAnimations:` can be chained. 22 | 3. The blocks passed with `addAnimations:` are executed in the ordered these methods are invoked. 23 | 4. Every animation block is performed only after the previous block completes. 24 | 5. Call `animate` after all animations have been added to begin animating from the first block. 25 | 6. `addAnimations:` method contains various parameters that can be passed along to tweak the animation behavior. 26 | 7. Most of these parameters have been given default values to get you started with ease. 27 | 28 | Many animation blocks can be chained together with very simple and easy-on-the-eye syntax as seen below. 29 | 30 | ``` 31 | Animator.addAnimations() { 32 | //Animation code 33 | }.addAnimations() { 34 | //Animation code 35 | }.addAnimations() { 36 | //Animation code 37 | }.addAnimations() { 38 | //Animation code 39 | }.addAnimations() { 40 | //Animation code 41 | }.animate() 42 | ``` 43 | Each block is executed after the completion of the last block. 44 | 45 | #Features of `Animator` 46 | 47 | 1. Supports setting duration, delay and animationOptions. 48 | 49 | ``` 50 | Animator.addAnimations(withDuration: 1, delay: 1, options: [UIViewAnimationOptions.curveLinear]) { 51 | //Animation code 52 | } 53 | ``` 54 | 55 | 2. Supports animations with spring damping and initial velocity constants. 56 | 57 | ``` 58 | Animator.addAnimations(usingSpringWithDamping: 0.7, initialSpringVelocity: 0) { 59 | //Animation code 60 | } 61 | ``` 62 | 63 | 3. You could pass duration, delay and animationOptions along with with spring damping and initial velocity constants to the function. You could instead choose to leave out duration or animatiOptions or delay selectively when invoking the function. 64 | 65 | 4. The `animate` method can accept a completion block to let you know when the entire batch of animations that have been chained together completes. 66 | 67 | ``` 68 | Animator.addAnimations() { 69 | //Animation code 70 | }.addAnimations() { 71 | //Animation code 72 | }.addAnimations() { 73 | //Animation code 74 | }.animate { 75 | //Completion of all animations chained above 76 | } 77 | ``` 78 | ![alt tag](https://raw.githubusercontent.com/vishalvshekkar/Animator/master/Resources/AnimatorDemoExample.gif) 79 | 80 | #Other Facts 81 | 82 | 1. Developed on Swift 3.0.1 83 | 2. The playground contains a demo which you can go through. 84 | 3. You need to open the assistant editor in the Playground to view the output animation. (View -> Assistant Editor -> Show Assistant Editor) or the keyboard shortcut option+command+return (if you haven't changed that). 85 | 4. To use `Animator` in your project, just drag the file 'Animator.swift' into your project which is available in the 'Source' directory in this repository. (Copy it to your project directory) 86 | -------------------------------------------------------------------------------- /Source/Animator.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public class Animator { 4 | 5 | public typealias AnimatorAnimationsClosure = () -> () 6 | public typealias AnimatorCompletionClosure = () -> () 7 | 8 | struct AnimationParameters { 9 | 10 | enum Animationtype { 11 | case withSpringDamping 12 | case withoutSpringDamping 13 | } 14 | 15 | let duration: TimeInterval 16 | let delay: TimeInterval 17 | let options: UIViewAnimationOptions 18 | let animationtype: Animationtype 19 | let damping: CGFloat? 20 | let initialVelocity: CGFloat? 21 | 22 | init(duration: TimeInterval, delay: TimeInterval, options: UIViewAnimationOptions, animationtype: Animationtype) { 23 | self.duration = duration 24 | self.delay = delay 25 | self.options = options 26 | self.animationtype = animationtype 27 | self.damping = nil 28 | self.initialVelocity = nil 29 | } 30 | 31 | init(duration: TimeInterval, delay: TimeInterval, options: UIViewAnimationOptions, animationtype: Animationtype, damping: CGFloat, initialVelocity: CGFloat) { 32 | self.duration = duration 33 | self.delay = delay 34 | self.options = options 35 | self.animationtype = animationtype 36 | self.damping = damping 37 | self.initialVelocity = initialVelocity 38 | } 39 | } 40 | 41 | private var animations = [(parameters: AnimationParameters, animationClosures: AnimatorAnimationsClosure)]() 42 | 43 | public static func addAnimations(withDuration: TimeInterval = 1, delay: TimeInterval? = nil, options: UIViewAnimationOptions = [], animations: @escaping AnimatorAnimationsClosure) -> Animator { 44 | let newAnimator = Animator() 45 | let parameters = AnimationParameters(duration: withDuration, delay: delay ?? 0, options: options, animationtype: .withoutSpringDamping) 46 | newAnimator.animations.append((parameters, animations)) 47 | return newAnimator 48 | } 49 | 50 | public static func addAnimations(withDuration: TimeInterval = 1, delay: TimeInterval? = nil, usingSpringWithDamping: CGFloat, initialSpringVelocity: CGFloat, options: UIViewAnimationOptions = [], animations: @escaping AnimatorAnimationsClosure) -> Animator { 51 | let newAnimator = Animator() 52 | let parameters = AnimationParameters(duration: withDuration, delay: delay ?? 0, options: options, animationtype: .withSpringDamping, damping: usingSpringWithDamping, initialVelocity: initialSpringVelocity) 53 | newAnimator.animations.append((parameters, animations)) 54 | return newAnimator 55 | } 56 | 57 | public func addAnimations(withDuration: TimeInterval = 1, delay: TimeInterval? = nil, options: UIViewAnimationOptions = [], animations: @escaping AnimatorAnimationsClosure) -> Animator { 58 | let parameters = AnimationParameters(duration: withDuration, delay: delay ?? 0, options: options,animationtype: .withoutSpringDamping) 59 | self.animations.append((parameters, animations)) 60 | return self 61 | } 62 | 63 | public func addAnimations(withDuration: TimeInterval = 1, delay: TimeInterval? = nil, usingSpringWithDamping: CGFloat, initialSpringVelocity: CGFloat, options: UIViewAnimationOptions = [], animations: @escaping AnimatorAnimationsClosure) -> Animator { 64 | let parameters = AnimationParameters(duration: withDuration, delay: delay ?? 0, options: options, animationtype: .withSpringDamping, damping: usingSpringWithDamping, initialVelocity: initialSpringVelocity) 65 | self.animations.append((parameters, animations)) 66 | return self 67 | } 68 | 69 | public func animate(completion: AnimatorCompletionClosure? = nil) { 70 | if let closureToAnimate = animations.first { 71 | let parameters = closureToAnimate.parameters 72 | if parameters.animationtype == .withoutSpringDamping { 73 | UIView.animate(withDuration: parameters.duration, delay: parameters.delay, options: parameters.options, animations: closureToAnimate.animationClosures, completion: { (completed) in 74 | self.animations.removeFirst() 75 | self.animate(completion: completion) 76 | }) 77 | } else { 78 | UIView.animate(withDuration: parameters.duration, delay: parameters.delay, usingSpringWithDamping: parameters.damping ?? 0, initialSpringVelocity: parameters.initialVelocity ?? 0, options: parameters.options, animations: closureToAnimate.animationClosures, completion: { (completed) in 79 | self.animations.removeFirst() 80 | self.animate(completion: completion) 81 | }) 82 | } 83 | } else { 84 | completion?() 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Animator Demo.playground/Sources/Animator.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public class Animator { 4 | 5 | public typealias AnimatorAnimationsClosure = () -> () 6 | public typealias AnimatorCompletionClosure = () -> () 7 | 8 | struct AnimationParameters { 9 | 10 | enum Animationtype { 11 | case withSpringDamping 12 | case withoutSpringDamping 13 | } 14 | 15 | let duration: TimeInterval 16 | let delay: TimeInterval 17 | let options: UIViewAnimationOptions 18 | let animationtype: Animationtype 19 | let damping: CGFloat? 20 | let initialVelocity: CGFloat? 21 | 22 | init(duration: TimeInterval, delay: TimeInterval, options: UIViewAnimationOptions, animationtype: Animationtype) { 23 | self.duration = duration 24 | self.delay = delay 25 | self.options = options 26 | self.animationtype = animationtype 27 | self.damping = nil 28 | self.initialVelocity = nil 29 | } 30 | 31 | init(duration: TimeInterval, delay: TimeInterval, options: UIViewAnimationOptions, animationtype: Animationtype, damping: CGFloat, initialVelocity: CGFloat) { 32 | self.duration = duration 33 | self.delay = delay 34 | self.options = options 35 | self.animationtype = animationtype 36 | self.damping = damping 37 | self.initialVelocity = initialVelocity 38 | } 39 | } 40 | 41 | private var animations = [(parameters: AnimationParameters, animationClosures: AnimatorAnimationsClosure)]() 42 | 43 | public static func addAnimations(withDuration: TimeInterval = 1, delay: TimeInterval? = nil, options: UIViewAnimationOptions = [], animations: @escaping AnimatorAnimationsClosure) -> Animator { 44 | let newAnimator = Animator() 45 | let parameters = AnimationParameters(duration: withDuration, delay: delay ?? 0, options: options, animationtype: .withoutSpringDamping) 46 | newAnimator.animations.append((parameters, animations)) 47 | return newAnimator 48 | } 49 | 50 | public static func addAnimations(withDuration: TimeInterval = 1, delay: TimeInterval? = nil, usingSpringWithDamping: CGFloat, initialSpringVelocity: CGFloat, options: UIViewAnimationOptions = [], animations: @escaping AnimatorAnimationsClosure) -> Animator { 51 | let newAnimator = Animator() 52 | let parameters = AnimationParameters(duration: withDuration, delay: delay ?? 0, options: options, animationtype: .withSpringDamping, damping: usingSpringWithDamping, initialVelocity: initialSpringVelocity) 53 | newAnimator.animations.append((parameters, animations)) 54 | return newAnimator 55 | } 56 | 57 | public func addAnimations(withDuration: TimeInterval = 1, delay: TimeInterval? = nil, options: UIViewAnimationOptions = [], animations: @escaping AnimatorAnimationsClosure) -> Animator { 58 | let parameters = AnimationParameters(duration: withDuration, delay: delay ?? 0, options: options,animationtype: .withoutSpringDamping) 59 | self.animations.append((parameters, animations)) 60 | return self 61 | } 62 | 63 | public func addAnimations(withDuration: TimeInterval = 1, delay: TimeInterval? = nil, usingSpringWithDamping: CGFloat, initialSpringVelocity: CGFloat, options: UIViewAnimationOptions = [], animations: @escaping AnimatorAnimationsClosure) -> Animator { 64 | let parameters = AnimationParameters(duration: withDuration, delay: delay ?? 0, options: options, animationtype: .withSpringDamping, damping: usingSpringWithDamping, initialVelocity: initialSpringVelocity) 65 | self.animations.append((parameters, animations)) 66 | return self 67 | } 68 | 69 | public func animate(completion: AnimatorCompletionClosure? = nil) { 70 | if let closureToAnimate = animations.first { 71 | let parameters = closureToAnimate.parameters 72 | if parameters.animationtype == .withoutSpringDamping { 73 | UIView.animate(withDuration: parameters.duration, delay: parameters.delay, options: parameters.options, animations: closureToAnimate.animationClosures, completion: { (completed) in 74 | self.animations.removeFirst() 75 | self.animate(completion: completion) 76 | }) 77 | } else { 78 | UIView.animate(withDuration: parameters.duration, delay: parameters.delay, usingSpringWithDamping: parameters.damping ?? 0, initialSpringVelocity: parameters.initialVelocity ?? 0, options: parameters.options, animations: closureToAnimate.animationClosures, completion: { (completed) in 79 | self.animations.removeFirst() 80 | self.animate(completion: completion) 81 | }) 82 | } 83 | } else { 84 | completion?() 85 | } 86 | } 87 | 88 | } 89 | --------------------------------------------------------------------------------