What is pulse animation in iOS?
In this tutorial, we are going to learn how to create circular pulse animation in swift. As its name describes, ‘pulse animation‘ is an animation that comes and go just like a wave of water. The animation start gradually and fades away as it reach its end time. For more visualized example of pulse animation in iOS please, look at the image below.
In this tutorial, we will learn how to create pulse animation using swift language in iOS (will create exactly same screen as shown the exemplary image above). So let’s start the tutorial
Steps to create pulse animation in swift :
1: Create a new Xcode project, we are using “single view application” template for the tutorial.
2: Open ViewController.swift file, create an IBOutlet for the imageView (required as pulse animation origin )
@IBOutlet weak var imgvAvatar: UIImageView! |
3: Open Main.storyboard, drag a UIImageView object to Viewcontroller’s view. Connect
IBOutlet created in step 2 to the UIImageView.
4: Give UIIMageView constraint’s, below are the constraints applied to UIImageView
- Horizontally in container
- Vertically in container
- Width = 50
- Height = 50
5: Assign “avatar” image to UIImageView. You can grab a copy of the avatar assets from here.
6: As we are all set up with our design part let’s dive into the most interesting part of this tutorial, i.e. creating pulse animation in iOS using swift language.
7: Since, we are creating a circular pulse animation so for this we also need our object which acts as a pulse origin, to be circular too, in our case its avatar imageView. So make UIImageView circular.
// | |
// ViewController.swift | |
// PulseAnimation | |
// | |
// Created by iostutorialjunction.com on 15/07/18. | |
// Copyright © 2018 iostutorialjunction.com. All rights reserved. | |
// | |
import UIKit | |
class ViewController: UIViewController { | |
@IBOutlet weak var imgvAvatar: UIImageView! | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// Do any additional setup after loading the view, typically from a nib. | |
imgvAvatar.layer.cornerRadius = imgvAvatar.frame.size.width/2.0 | |
} | |
override func didReceiveMemoryWarning() { | |
super.didReceiveMemoryWarning() | |
// Dispose of any resources that can be recreated. | |
} | |
} |
8: Its time to create pulse layer, so we will create a new function and named it “createPulse“. Below is the code that creates pulse
func createPulse() { | |
for _ in 0...2 { | |
let circularPath = UIBezierPath(arcCenter: .zero, radius: UIScreen.main.bounds.size.width/2.0, startAngle: 0, endAngle: 2 * .pi, clockwise: true) | |
let pulseLayer = CAShapeLayer() | |
pulseLayer.path = circularPath.cgPath | |
pulseLayer.lineWidth = 2.0 | |
pulseLayer.fillColor = UIColor.clear.cgColor | |
pulseLayer.lineCap = kCALineCapRound | |
pulseLayer.position = CGPoint(x: imgvAvatar.frame.size.width/2.0, y: imgvAvatar.frame.size.width/2.0) | |
imgvAvatar.layer.addSublayer(pulseLayer) | |
pulseLayers.append(pulseLayer) | |
} | |
} | |
//NOTE:- pulseLayers is an array of type CAShapeLayer declared globally e.g. | |
//var pulseLayers = [CAShapeLayer]() | |
Creating pulsating layers for UIMageView
In above code, we created a circular path using UIBezierPath, setting following parameters as
i) arc center of the circular path to zero initially though we will set center of our circular pat later on.
ii) Radius of the circular path will equals to the width of your pulse animation divides by 2 i.e how long you want the pulse to go and then fades away.
iii) Start angle of the circular path will be zero so that it starts from its initials point.
iv) End angle will be 2 *.pi
v) Direction of circular path will be clockwise, it tells whether layer draws itself clock wise or anticlockwise
As we are done with circular path , we need to create a layer using CAShapeLayer class that will act as our pulse. Assign the properties to layer object as shown in the code above and finally add the layer to our avatar UIImageView layer. This is required as our avatar UIImageView acts as origin of pulse. Lastly, add layer to the array too.
9: Call the “createPulse” function inside viewDidLoad function.
Animate Pulse around avatar UIIMageView:
func animatePulsatingLayerAt(index:Int) { | |
//Giving color to the layer | |
pulseArray[index].strokeColor = UIColor.darkGray.cgColor | |
//Creating scale animation for the layer, from and to value should be in range of 0.0 to 1.0 | |
// 0.0 = minimum | |
//1.0 = maximum | |
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale") | |
scaleAnimation.fromValue = 0.0 | |
scaleAnimation.toValue = 0.9 | |
//Creating opacity animation for the layer, from and to value should be in range of 0.0 to 1.0 | |
// 0.0 = minimum | |
//1.0 = maximum | |
let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity)) | |
opacityAnimation.fromValue = 0.9 | |
opacityAnimation.toValue = 0.0 | |
//Grouping both animations and giving animation duration, animation repat count | |
let groupAnimation = CAAnimationGroup() | |
groupAnimation.animations = [scaleAnimation, opacityAnimation] | |
groupAnimation.duration = 2.3 | |
groupAnimation.repeatCount = .greatestFiniteMagnitude | |
groupAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) | |
//adding groupanimation to the layer | |
pulseArray[index].add(groupAnimation, forKey: "groupanimation") | |
} |
The above code is self explanatory, if you have any concerns or doubts then please feel free to ask then in comments.
In order to start animation we need to call the “animatePulsatingLayerAt” function as shown below
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: { | |
self.animatePulsatingLayerAt(index: 0) | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4, execute: { | |
self.animatePulsatingLayerAt(index: 1) | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { | |
self.animatePulsatingLayerAt(index: 2) | |
}) | |
}) | |
}) | |
//NOTE:- The above code should be written inside createPulse() function after for loop finishes. |
Complete code for creating pulse animation in swift:
// | |
// ViewController.swift | |
// PulseAnimation | |
// | |
// Created by iostutorialjunction.com on 15/07/18. | |
// Copyright © 2018 iostutorialjunction.com. All rights reserved. | |
// | |
import UIKit | |
class ViewController: UIViewController { | |
@IBOutlet weak var imgvAvatar: UIImageView! | |
var pulseArray = [CAShapeLayer]() | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// Do any additional setup after loading the view, typically from a nib. | |
imgvAvatar.layer.cornerRadius = imgvAvatar.frame.size.width/2.0 | |
createPulse() | |
} | |
func createPulse() { | |
for _ in 0...2 { | |
let circularPath = UIBezierPath(arcCenter: .zero, radius: ((self.imgvAvatar.superview?.frame.size.width )! )/2, startAngle: 0, endAngle: 2 * .pi , clockwise: true) | |
let pulsatingLayer = CAShapeLayer() | |
pulsatingLayer.path = circularPath.cgPath | |
pulsatingLayer.lineWidth = 2.5 | |
pulsatingLayer.fillColor = UIColor.clear.cgColor | |
pulsatingLayer.lineCap = kCALineCapRound | |
pulsatingLayer.position = CGPoint(x: imgvAvatar.frame.size.width / 2.0, y: imgvAvatar.frame.size.width / 2.0) | |
imgvAvatar.layer.addSublayer(pulsatingLayer) | |
pulseArray.append(pulsatingLayer) | |
} | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: { | |
self.animatePulsatingLayerAt(index: 0) | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4, execute: { | |
self.animatePulsatingLayerAt(index: 1) | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { | |
self.animatePulsatingLayerAt(index: 2) | |
}) | |
}) | |
}) | |
} | |
func animatePulsatingLayerAt(index:Int) { | |
//Giving color to the layer | |
pulseArray[index].strokeColor = UIColor.darkGray.cgColor | |
//Creating scale animation for the layer, from and to value should be in range of 0.0 to 1.0 | |
// 0.0 = minimum | |
//1.0 = maximum | |
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale") | |
scaleAnimation.fromValue = 0.0 | |
scaleAnimation.toValue = 0.9 | |
//Creating opacity animation for the layer, from and to value should be in range of 0.0 to 1.0 | |
// 0.0 = minimum | |
//1.0 = maximum | |
let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity)) | |
opacityAnimation.fromValue = 0.9 | |
opacityAnimation.toValue = 0.0 | |
// Grouping both animations and giving animation duration, animation repat count | |
let groupAnimation = CAAnimationGroup() | |
groupAnimation.animations = [scaleAnimation, opacityAnimation] | |
groupAnimation.duration = 2.3 | |
groupAnimation.repeatCount = .greatestFiniteMagnitude | |
groupAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) | |
//adding groupanimation to the layer | |
pulseArray[index].add(groupAnimation, forKey: "groupanimation") | |
} | |
override func didReceiveMemoryWarning() { | |
super.didReceiveMemoryWarning() | |
// Dispose of any resources that can be recreated. | |
} | |
} | |