Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 54

Animating a Line-Drawing Effect with `strokeEnd`

Recommended resources

Hello! Welcome to the next lesson in our module on "Interactive Animations and Transitions."

In our last session, we mastered UIViewPropertyAnimator, learning how to create interactive and interruptible animations driven by pan gestures. We focused on animating standard UIView properties like frame and alpha.

Today, we're going to dive one level deeper into the rendering pipeline, moving from UIView to CALayer. Our goal is to animate the strokeEnd property of a CAShapeLayer to create a line-drawing effect. This is a classic and versatile technique used for everything from animated icons to progress indicators. It's a fundamental skill for creating polished, custom animations.

Here's an example of the kind of effect we'll be able to create by the end of this lesson:

Line-Drawing Animation on a Search Button
This GIF shows a line-drawing animation around a button's border, created by animating the `strokeEnd` property of a CAShapeLayer.

1. Drawing Shapes with CAShapeLayer

Before we can animate a line, we first need to draw one. While you can draw custom shapes in a UIView by overriding the draw(_:) method, this approach has performance drawbacks. Drawing in draw(_:) is CPU-intensive and happens on the main thread.

A more performant and animation-friendly approach is to use CAShapeLayer. It's a special CALayer subclass optimized for drawing vector shapes using UIBezierPath. Because CAShapeLayer offloads rendering to the GPU and its properties are directly animatable, it's the professional's choice for dynamic shape-based animations.

The process involves four main steps:

  1. Create a CAShapeLayer instance.
  2. Define the shape's geometry using a UIBezierPath.
  3. Customize the layer's appearance (e.g., stroke color, line width).
  4. Add the shape layer as a sublayer to your view's layer.

The following article provides a practical walkthrough of this process by creating a checkmark icon.

A Practical Guide to CAShapeLayer - Dimas Ashidiqi

Let's read "A Practical Guide to CAShapeLayer" by Dimas Ashidiqi. This guide clearly explains the benefits of CAShapeLayer and provides a step-by-step example of how to set one up.

Please read the sections "Introduction to CAShapeLayer" and "Getting Started". Focus on understanding the four steps outlined in the code to create and configure the checkmarkLayer. Notice how a UIBezierPath is used to define the points of the checkmark.

A key takeaway from the article is the relationship between UIBezierPath and CAShapeLayer. The path defines the "what" (the geometry), and the layer handles the "how" (the rendering and appearance).

A useful professional tip, also mentioned in the article, is that you don't have to guess the coordinates for your path. You can extract them directly from design files in tools like Figma or Sketch, ensuring pixel-perfect translation from design to code.


2. Animating the Stroke with CABasicAnimation

CAShapeLayer has two particularly powerful properties for animation: strokeStart and strokeEnd.

  • strokeStart: A value between 0.0 and 1.0 that determines where along the path the stroke begins. The default is 0.0.
  • strokeEnd: A value between 0.0 and 1.0 that determines where along the path the stroke ends. The default is 1.0.

By animating strokeEnd from 0.0 to 1.0, you can make the path appear to draw itself. To do this, we'll use CABasicAnimation, which we've used before to animate layer properties.

Let's see how this is done in the checkmark example.

A Practical Guide to CAShapeLayer - Dimas Ashidiqi

Continuing with the same article, let's look at the animation code.

Read the section "Animating the Checkmark Icon". The code is short but powerful. Pay close attention to the keyPath of the CABasicAnimation—it's set to "strokeEnd"—and how the fromValue and toValue are configured.

The code is straightforward: we create a CABasicAnimation that targets the strokeEnd property and tells it to animate from 0 to 1 over a set duration.

CABasicAnimation for strokeEnd Property
This code snippet shows the essential structure for creating a `CABasicAnimation` targeting the `strokeEnd` key path. Although the comments are in a different language, the Swift code is clear.

A Deeper Dive: The Model vs. Presentation Layer

The basic animation works, but for robust and predictable animations, it's crucial to understand a key concept in Core Animation: the model layer versus the presentation layer.

  • The model layer is where your layer's actual property values are stored. It's the "source of truth."
  • When you add an animation, Core Animation creates a presentation layer, which is a temporary copy of your layer. The animation's interpolated values are applied to this presentation layer frame by frame.
  • When the animation finishes, the presentation layer is discarded, and the screen reverts to showing the model layer, which still has its original property values.

This can cause your animation to "snap back" to its initial state. To prevent this, you must manually update the model layer's property to its final value after adding the animation.

The following resource provides an excellent explanation of this concept and other important CABasicAnimation properties.

CABasicAnimation for animating strokes

The article "CABasicAnimation for animating strokes" offers a more detailed look at the mechanics of the animation, including the important fillMode property and the model/presentation layer concept.

Please read the section that begins with "Now lets dive into animating the line!". Focus on the numbered explanations, especially point #5, which discusses the presentation layer and the importance of setting the model layer's final value.

Let's summarize the key points from that deep dive:

  • keyPath: The string name of the property to animate (e.g., "strokeEnd", "strokeStart").
  • fromValue / toValue: The start and end values for the animation.
  • duration: The animation's length in seconds.
  • fillMode: Controls the layer's state before the animation starts and after it ends.
    • kCAFillModeForwards: The layer will remain in its final state when the animation completes.
    • kCAFillModeBackwards: The layer will immediately jump to its initial state (fromValue) as soon as the animation is added.
  • Model Layer Update: To make the animation's end state permanent, you must set the property on the model layer itself. For our line-drawing effect, this means setting myShapeLayer.strokeEnd = 1.0 right after you add the animation.

The resource also introduces animating strokeStart slightly after strokeEnd to create a "worm" effect where the line segment appears to travel along the path. This is achieved by grouping two CABasicAnimation objects in a CAAnimationGroup.


Example: A Circular Progress Indicator

Let's combine these concepts to create a circular progress indicator that animates from 0% to 100%.

// 1. Create a view to host the layer
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))

// 2. Create the circular path
let circularPath = UIBezierPath(
    arcCenter: CGPoint(x: 50, y: 50),
    radius: 40,
    startAngle: -CGFloat.pi / 2, // Start at the top
    endAngle: 2 * CGFloat.pi - (CGFloat.pi / 2),
    clockwise: true
)

// 3. Create the shape layer
let progressLayer = CAShapeLayer()
progressLayer.path = circularPath.cgPath
progressLayer.strokeColor = UIColor.systemBlue.cgColor
progressLayer.lineWidth = 10
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.lineCap = .round

// Set the initial state of the model layer
progressLayer.strokeEnd = 0.0

// 4. Add the layer to the view
containerView.layer.addSublayer(progressLayer)

// 5. Create the animation
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.toValue = 1.0
animation.duration = 2.0
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false // Keep the presentation layer state

// Add the animation to the layer
progressLayer.add(animation, forKey: "progressAnimation")

// An alternative, and often better, way to persist the final state
// is to update the model layer directly after adding the animation.
// progressLayer.strokeEnd = 1.0

In this example, setting animation.isRemovedOnCompletion = false and animation.fillMode = .forwards tells the layer to maintain its visual state after the animation. A more robust practice, as discussed, is to update the model layer's property (progressLayer.strokeEnd = 1.0) which makes the change permanent and independent of the animation object's lifecycle.


Conclusion

You've now added a powerful and visually appealing technique to your animation toolkit. By moving down to the CALayer level, you've gained more direct control over rendering and performance.

Key Takeaways:

  • CAShapeLayer combined with UIBezierPath is the high-performance way to draw and animate custom vector shapes.
  • The strokeStart and strokeEnd properties are key to creating line-drawing animations.
  • CABasicAnimation is used to animate CALayer properties like strokeEnd.
  • To avoid your animation "snapping back," you must ensure the model layer's final property value is set correctly, either through fillMode or by directly updating the property after adding the animation.

Preview of the Next Lesson:

In our next lesson, we will implement a custom view controller transition using the UIViewControllerAnimatedTransitioning protocol. This is a fantastic opportunity to apply what we've learned about CALayer animations. Custom transitions often involve animating views and layers in complex ways, and the line-drawing and shape animation skills you've just acquired will be directly applicable.