A Practical Guide to CAShapeLayer
Get started with CAShapeLayer by creating an animated checkmark icon
What You’ll Learn
In this tutorial, you’ll build an animated checkmark icon. In the process, you’ll learn how you can use CAShapeLayer to animate shapes.
Introduction to CAShapeLayer
CAShapeLayer is a subclass of CALayer that allows you to draw shapes and
animate them. Notice that I put emphasize in “animate”, because if you
just want to draw shapes, you can use UIView’s draw(_:)
method instead. But the draw(_:)
method approach is more expensive because
it uses CPU on the main thread, whereas CAShapeLayer
use GPU. Where
CAShapeLayer
really shines is when you want to animate the shape’s
properties like the strokes, color, and patterns.
Getting Started
Create a new XCode project with the iOS app template. In this tutorial, we’ll use UIKit.
Let’s get started by create a new file called CheckmarkView.swift
. In this
project, I will create the UIs 100% programmatically. So, type the following
code:
import UIKit
class CheckmarIconView: UIView {
init() {
super.init(frame: .init(x: 0, y: 0, width: 40, height: 40))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You should be familiar with the code above. We create CheckmarIconView
which is a subclass of UIView
. We call the super.init(frame:)
method
with 40 as the width and the height of the view.
Next, we’ll draw the checkmark icon. This will require 4 steps:
- Create the
CAShapeLayer
instance - Crete the path using
UIBezierPath
, then assign it to ourCAShapeLayer
instance - Customize the CAShapeLayer’s properties
- Add the
CAShapeLayer
to the view’s layer as its sublayer
Here’s how you can do those steps in code:
class CheckmarIconView: UIView {
// 1. Create the `CAShapeLayer` instance
let checkmarkLayer = CAShapeLayer()
init() {
super.init(frame: .init(x: 0, y: 0, width: 40, height: 40))
setupCheckmarkLayer() // don't forget to call this method
}
// ...
private func setupCheckmarkLayer() {
// 2. Crete the path using `UIBezierPath`, then assign it to our `CAShapeLayer` instance
let path = UIBezierPath()
path.move(to: CGPoint(x: 6.5, y: 20.5))
path.addLine(to: CGPoint(x: 16.5, y: 30.5))
path.addLine(to: CGPoint(x: 33.5, y: 8.5))
checkmarkLayer.path = path.cgPath
// 3. Customize the CAShapeLayer's properties
checkmarkLayer.fillColor = UIColor.clear.cgColor
checkmarkLayer.strokeColor = UIColor.systemBlue.cgColor
checkmarkLayer.lineWidth = 3
checkmarkLayer.lineCap = .round
checkmarkLayer.lineJoin = .round
// 4. Add the `CAShapeLayer` to the view's layer as its sublayer
layer.addSublayer(checkmarkLayer)
}
}
First, we create the CAShapeLayer
instance as a property of the view and
named it CheckmarkLayer
. We do this because later we will need to refer to this
layer when we want to animate the checkmark.
Then, we create the path using UIBezierPath
. It may
look like I’m making up the numbers, but in the next section I will explain
how I came up with those numbers, and how you can too.
After that, we customize the properties of our CheckmarkLayer
. One thing
to highlight here is that you have to set the fillColor
to UIColor.clear.cgColor
.
If you don’t do that, the icon will become a triangle instead of a checkmark,
and it took me off guard at first.
Last, we add the CheckmarkLayer
to the view’s layer as its sublayer.
Understanding how to work with UIBezierPath
At first, it may seem like you have to guess the numbers when you create the path, but you are not.
Here’s the icon that I draw in Figma:
If you think about it, the checkmark icon is made up of two lines. And those lines are made up of three points in the following coordinates:
If you look closer to our path code, you’ll see that the numbers are exactly
the same as the coordinates in Figma. The method also perfectly resembles the
icon in Figma. Because the first point is located in (6.5, 20.5), we ask the
path to move to CGPoint(x: 6.5, y: 20.5)
. Then, we ask the path to add a
line to CGPoint(x: 16.5, y: 30.5)
and then to CGPoint(x: 33.5, y: 8.5)
.
One thing to note is that the coordinate system will only match if you’re working with UIKit. If you’re working with AppKit, the coordinate system is flipped, so you’ll have to flip the y-axis.
Animating the Checkmark Icon
To animate the icon, add this method to our CheckmarIconView
class:
func startAnimation() {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = 0.5
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
checkmarkLayer.add(animation, forKey: "loadingAnimation")
}
In this method, we create a CABasicAnimation
instance and that will
animate the strokeEnd
property of our CheckmarkLayer
from 0 to 1 in 0.5
second. Then, we add the animation to our checkmarkLayer
.
Now, you can call the startAnimation()
method in UIViewController
’s
ViewDidAppear
or UIView
’s didMoveToWindow
method.
With that, you have animated the checkmark icon.
Conclusion
In this tutorial, you’ve learned how to use CAShapeLayer
to draw shapes
and animate them. You’ve also learned how to create UIBezierPath
from a
Figma file.
There are still many things that you can animate with CAShapeLayer
, like
the dash pattern and the line width.