[email protected]
Your Profile
Dimas Ashidiqi
Sunday, 14 January 2024
#Animation

A Practical Guide to CAShapeLayer

Get started with CAShapeLayer by creating an animated checkmark icon

A Practical Guide to CAShapeLayer

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:

  1. Create the CAShapeLayer instance
  2. Crete the path using UIBezierPath, then assign it to our CAShapeLayer instance
  3. Customize the CAShapeLayer’s properties
  4. 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.