Implementing a simple Play/Pause Animation.
Animations are indispensable part of any iOS app. I have just embarked on the journey of learning animations (from beignner to complex level) and I plan to share everything here.
NOTE: Whatever I am sharing there might be a better way to imlpement it in that case please suggest in comments. Also this might be a good point for anyone to jump into animations world if you have been holding back. I will be sharing complete tutorials, resources, references.
First some of the resources I follow (Feel free to suggest more) -
For Learning how to code animations —
- Marin Torodov — iOS Animations by Tutorials.- Another Gem by Ray wenderlich team.
- iOS Core Animation advance techniques by Nick lockwood —
- Tutorials at popular sites like Raywenderlich,Objc.io, iOS Goodies etc.
For Inspiration —
- Dribble.
- Other random posts (mostly from twitter.).
Recently Michael Villar did motion experiments which is the inspiration behind this post — http://www.michaelvillar.com/motion
We are going to implement the following —

Following is our implementation plan —
- Deconstructing the animation. Noting down what is happening and converting it into iOS terms.(Its important when you get animations/interactions from designer to break it down to simpler terms from implementation perspective.)
- A small discussion on Layers and its subclass we will use.
- Implementation steps
- Create Play View and its backing layer.
- Add play animation.
- Create Pause View and its backing layer.
- Add pause animation.
- Club the two views together into final view called “PlayPauseView”.
- Add required animations to PlayPauseView.
- A Transparent button or a tap gesture on PlayPause view to recognise the tap and animate based on the current state.
4. Where to go from here.
Deconstructing the animation
Lets slow down the animation —

Play View observations
- Triangle collapsing. — When the height of the view animated to 0.0 triangle collapses.
- Frame movement. — We can use UIView animations for frame movement.
- Visibility — Goes from hidden to visible and vice versa.We control visibility by “Alpha” in iOS.
Pause View observations
- Bars width — Bars width animating from 0 to max and vice versa.
- Frame movement — We can use UIView animations for frame movement.
- Visibility — Goes from hidden to visible and vice versa.
Looks like we have identified the pieces to implement lets move to next step.
Layers — Introduction
Assumption: You know what a UIView is.- Apple Documentation (just in case!!)
Every UIView has got a backing layer which handles rendering, layout and animations for the UIView. Its of type CALayer class. Yout might have used it in something like this -
view.layer.borderRadius= 3. or view.layer.borderColor = UIColor.blackColor()
“layer” above is the backing layer instance of that UIView.
CGPath — “A graphics path is a mathematical description of a series of shapes or lines. CGPathRef defines an opaque type that represents an immutable graphics path” (Ref. — Apple Docs).
CAShapeLayer (subclass of CALayer) — Its a layer subclass that draws itself using the path given.It has advantages over CoreGraphics as its fast and memory efficient(Please read CAShapeLayer from NickLockwood’s book mentioned above).
If you want to dive deep into Layers and its subclasses follow the above resources or this article from Ray — Link
Implementation
PlayView
We will implement PlayView as a UIView subclass with a custom backing layer which will be a CAShapeLayer subclass (Yes!! you can define a custom backing layer for your view.).
A snippet from TriangleShapeLayer-
It overrides setter for ‘bounds’ of the view.When bounds are set it calls ‘shapeForBounds’ and creates path according to new bounds.
Note: When view’s bounds/frame changes the shape adjusts itself.
override var bounds : CGRect {
didSet {
path = self.shapeForBounds(bounds).CGPath
}
}func shapeForBounds(rect: CGRect) -> UIBezierPath {
// Triangle side length
let length = rect.size.height
let point1 = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect))
let point2 = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect))
let point3 = CGPointMake(CGRectGetMaxX(rect), CGRectGetMidY(rect))
let trianglePath = UIBezierPath()
trianglePath.moveToPoint(point1)
trianglePath.addLineToPoint(point2)
trianglePath.addLineToPoint(point3)
trianglePath.closePath()
return trianglePath
}Snippet from PlayView -
var shapeLayer:CAShapeLayer! {
return self.layer as CAShapeLayer
}//Defines custom backing layer class for this view.
override class func layerClass() -> AnyClass {
return TriangleShapeLayer.self
}
// override backgroundColor.
override var backgroundColor: UIColor? {
get {
return UIColor(CGColor : shapeLayer.fillColor)
}
set {
shapeLayer.fillColor = newValue!.CGColor
}}
Result:

Animation to implement:

3 things to achieve above animation —
- Triangle height is going gradually to 0 — When I change the PlayView’s height its backing layer shape (triangle) should also animate along with it.When we add animation to PlayView’s bounds we need to tell the path to animate as well so I override method “addAnimation(anim: CAAnimation!, forKey key: String!)” and add a “CABasicAnimation” on the path to animate alongwith bounds.(You can read the documentation on CABasicAnimation otherwise its self explanatory from below code.)
Here is the code snippet —
override func addAnimation(anim: CAAnimation!, forKey key: String!) {
super.addAnimation(anim, forKey: key)
if (anim.isKindOfClass(CABasicAnimation.self)) {
let basicAnimation = anim as CABasicAnimation
if (basicAnimation.keyPath == “bounds.size”) {
var pathAnimation = basicAnimation.mutableCopy() as CABasicAnimation
pathAnimation.keyPath = “path”
pathAnimation.fromValue = self.path
pathAnimation.toValue = self.shapeForBounds(self.bounds).CGPath
self.removeAnimationForKey(“path”)
self.addAnimation(pathAnimation,forKey: “path”)
}
}}
Above snippet says that if the animation is added on the size of the view then animate the path from oldBounds to newBounds.
- Alpha is going from 1 to 0
- Frame movement.
Now implement a simple UIView animation on PlayView and change all the above said values in the animation block and it should replicate the animation.
Here is the code snippet —
UIView.animateWithDuration(duration, delay: delay, options:.CurveEaseInOut, animations: { () -> Void in
self.playView.alpha = newPlayAlpha // New alpha value 1.0 or 0.0
self.playView.frame = newPlayFrame // New Frame.}) { (Bool) -> Void inself.userInteractionEnabled = true
}
}
Thats it!!!
Lets move to PauseView and its animation.
PauseView
We will implement Pause view using the same concept. A UIView subclass with a cutsom CAShapeLayer to back our PauseView class.
This is what we have to implement :

Code snippet from PauseLayer -
override var bounds : CGRect {
didSet {
path = self.shapeForBounds(bounds, width:bounds.size.width/3.0).CGPath
}
}func shapeForBounds(rect: CGRect, width:CGFloat) -> UIBezierPath {
let shapeWidth = width
let point0 = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect))
let point1 = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect))
let point2 = CGPointMake(CGRectGetMinX(rect) + shapeWidth, CGRectGetMaxY(rect))
let point3 = CGPointMake(CGRectGetMinX(rect) + shapeWidth, CGRectGetMinY(rect))
let point4 = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect))let point5 = CGPointMake(CGRectGetMinX(rect) + shapeWidth + 15.0, CGRectGetMaxY(rect))
let point6 = CGPointMake(CGRectGetMinX(rect) + shapeWidth + 15.0, CGRectGetMinY(rect))
let point7 = CGPointMake(CGRectGetMinX(rect) + shapeWidth + 15.0 + shapeWidth, CGRectGetMinY(rect))
let point8 = CGPointMake(CGRectGetMinX(rect) + shapeWidth + 15.0 + shapeWidth, CGRectGetMaxY(rect))
let customPath = UIBezierPath()
customPath.moveToPoint(point0)
customPath.addLineToPoint(point1)
customPath.addLineToPoint(point2)
customPath.addLineToPoint(point3)
customPath.addLineToPoint(point4)
customPath.moveToPoint(point5)
customPath.addLineToPoint(point6)
customPath.addLineToPoint(point7)
customPath.addLineToPoint(point8)
return customPath
}
We have overriden the bounds setter and calls ‘shapeForBounds’ with parameters ‘bounds’ and ‘width’ (which determines what is the width of the bars.I have chosen it to be 1/3rd but it could be any number.). We identify points and draw lines using UIBezierPath.
Note: Keep in mind that since in above shapes there are no curves it makes our work easy as we can draw path using limited number of points.We will move to complex shapes in subsequent articles.
Animation to implement:

3 things to achieve this animation —
- Change in the width of the bars from 0 to max (1/3 rd of view’s width). We animate width of the PauseView from 0 to max and path should animate itself from 0 to max/3 or vice versa.So we override “addAnimation” method in PauseLayer.Here is the code snippet -
override func addAnimation(anim: CAAnimation!, forKey key: String!) {
super.addAnimation(anim, forKey: key)
if (anim.isKindOfClass(CABasicAnimation.self)) {
let basicAnimation = anim as CABasicAnimation
if (basicAnimation.keyPath == “bounds.size”) {
var pathAnimation = basicAnimation.mutableCopy() as CABasicAnimation
pathAnimation.keyPath = “path”
pathAnimation.fromValue = self.path
pathAnimation.toValue = self.shapeForBounds(self.bounds, width: self.bounds.size.width/3.0).CGPath
self.removeAnimationForKey(“path”)
self.addAnimation(pathAnimation,forKey: “path”)
}
}}If the animation is added on “bounds.size” of the PauseView then animate the path from current width to the new width of the view (1/3rd of the view ).
2. Frame changes.
3. Alpha changes.
Now Implement a simple UIView animation on PauseView and change all the above values in its animation block and it should replicate the above animation.
Here is the code snippet —
UIView.animateWithDuration(duration, delay: delay, options:.CurveEaseInOut, animations: { () -> Void in
self.pauseView.alpha = newPauseAlpha // New alpha value 1.0 or 0.0
self.pauseView.frame = newPauseFrame // New Frame.}) { (Bool) -> Void inself.userInteractionEnabled = true
}
}
Short snippet from the PauseView.
// Our custom CAShapeLayer class.
override class func layerClass() -> AnyClass {
return PauseLayer.self
}
var shapeLayer:CAShapeLayer! {
return self.layer as CAShapeLayer
}// Override backgroundColor.
override var backgroundColor: UIColor? {
get {
return UIColor(CGColor : shapeLayer.fillColor)
}
set {
shapeLayer.fillColor = newValue!.CGColor
}
}Thats it!!!
Yay!!! we have implemented both views, there backing layers and respective animations.
Now we create our final view PlayPauseView which will handle the state of the view whether it is in Play or Pause state and animate from one state to another.
Here is the code snippet for the animation from one state to another-
// Here the new Alpha, frames values are calculated according to //whether PlayView is visible or Pause.
switch(animationType) {
case .Play:
newPlayAlpha = 0.0
newPlayFrame = CGRectMake(oldPlayFrame.origin.x + displacement, oldPlayFrame.origin.y + defaultWidth/2.0, oldPlayBounds.size.width, 0.0)
newPauseFrame = CGRectMake(oldPauseFrame.origin.x + displacement, oldPauseFrame.origin.y, defaultWidth, oldPauseFrame.size.height)
newPauseAlpha = 1.0case .Pause:
newPlayAlpha = 1.0
newPlayFrame = CGRectMake(oldPlayFrame.origin.x — displacement, oldPlayFrame.origin.y — defaultWidth/2.0, defaultWidth, defaultWidth)
newPauseFrame = CGRectMake(oldPauseFrame.origin.x — displacement, oldPauseFrame.origin.y, 0.0, oldPauseFrame.size.height)
newPauseAlpha = 0.0
}
// Animate from one state to another -
UIView.animateWithDuration(duration, delay: delay, options:.CurveEaseInOut, animations: { () -> Void inself.playView.alpha = newPlayAlpha
self.playView.frame = newPlayFrame
self.pauseView.alpha = newPauseAlpha
self.pauseView.frame = newPauseFrame
}) { (Bool) -> Void inself.userInteractionEnabled = true
if (self.currentState == .Play) {
self.currentState = .Pause
}
else {
self.currentState = .Play
}
}
Note : I have also added a transparent button on this view to get tap behaviour so this view can be used as a button over any video or gifs.
Here is what it looks like finally —

Where to go from here
This might be a relatively simple animation but above procedure will help us to build complex animations later on. Complex animations are mostly about various simple pieces working together(Mostly!!). Also now will be a good time to dig deeper into “Layers” as we will use it a lot.
You can checkout the Github link for the final project (pull requests are welcome!!)— https://github.com/kNeerajPro/PlayPauseAnimation
I will keep sharing whatever I learn through above resources or others.
If you think there could be a better way to do above animation please comment , I would love to hear your views.
You can reach me at @cocoagarage or @kneeraj1762 on twitter.
Cheers!!