Skip to main content

Welcome to a Grasp lesson

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

Lesson 61

Animating Custom Data Types

Recommended resources

Hello! Welcome to the final lesson in our SwiftUI Animation Essentials module.

In previous lessons, we've covered a lot of ground, from implicit and explicit animations to custom transitions and seamless geometry effects with matchedGeometryEffect. All these techniques focused on animating the built-in properties of views—their size, position, color, and so on.

Today, we're going to take a significant step further and explore one of the most powerful concepts in the SwiftUI animation system. You will learn how to create a data-driven animation by making a custom type conform to the Animatable protocol. This unlocks the ability to teach SwiftUI how to animate your own data structures, moving beyond simple visual properties to animating the very data that drives your UI.

This is a key skill for creating advanced, bespoke visualisations like animated chart data, custom progress indicators, or any UI that needs to smoothly transition based on complex, custom data models.

1. The Problem: When withAnimation Isn't Enough

Let's start with a scenario. Imagine you've created a custom Shape and you want to animate one of its properties. You might intuitively wrap the state change that modifies this property in a withAnimation block, expecting a smooth transition. However, as you'll see, it doesn't work out of the box.

To see this problem in action, watch the beginning of this video from Paul Hudson.

(OLD) Animating simple shapes with animatableData – Drawing SwiftUI Tutorial 8/10

This video, titled 'Animating simple shapes with animatableData', clearly demonstrates why simply changing a custom property within an animation block doesn't produce an animation.

Watch from the start until 03:17. The video first builds a custom Trapezoid shape with an insetAmount property. Pay close attention to what happens when you tap the shape to change this value inside withAnimation. The presenter then explains exactly why the shape jumps instead of animating.

As the video explains, SwiftUI knows how to animate properties like opacity or scale because their underlying data types (Double, CGFloat, CGSize) are known to be "animatable." It can generate the intermediate values between the start and end states. For a custom property on a custom shape, it has no such knowledge. It sees the start state, it sees the end state, and it just jumps.

To fix this, we need to explicitly tell SwiftUI what to animate and how to interpolate it. This is where the Animatable protocol comes in.


2. The Animatable and VectorArithmetic Protocols

At the heart of SwiftUI's animation system are two key protocols: Animatable and VectorArithmetic.

  • Animatable: A protocol that types can conform to, indicating they have data that can be animated. Its single requirement is a computed property called animatableData.
  • VectorArithmetic: A protocol for types that can function as N-dimensional vectors. This means they support addition, subtraction, and scalar multiplication. This is the mathematical foundation that allows SwiftUI to interpolate values. Any type used for animatableData must conform to VectorArithmetic.

Let's get a more formal definition from an Apple engineer.

WWDC23: Explore SwiftUI animation | Apple

This WWDC23 talk, 'Explore SwiftUI animation', provides a concise and authoritative explanation of the Animatable protocol and its relationship with VectorArithmetic.

Watch from 06:33 to 07:45. This segment explains that animatableData must be a 'vector' and clarifies that a vector is essentially a fixed-length list of numbers (CGFloat, CGPoint, etc.) that supports basic vector math.

So, the process is:

  1. A state change occurs inside a withAnimation block.
  2. SwiftUI checks if the view or shape conforms to Animatable.
  3. If it does, SwiftUI gets the start and end values of its animatableData.
  4. Because this data conforms to VectorArithmetic, SwiftUI can calculate all the intermediate values over the animation's duration.
  5. For each intermediate value, SwiftUI sets the animatableData property, triggering a redraw of the view. This sequence of redraws creates the smooth animation.

3. Conforming a Shape to Animatable

Now let's apply this theory to fix our Trapezoid example. Shape already conforms to Animatable, but its default animatableData is of type EmptyAnimatableData, which does nothing. We need to override it.

Since our insetAmount is a CGFloat, which already conforms to VectorArithmetic, the implementation is straightforward. We just need to expose insetAmount through the animatableData computed property.

struct Trapezoid: Shape {
    var insetAmount: CGFloat

    // The key to the animation!
    var animatableData: CGFloat {
        get { insetAmount }
        set { insetAmount = newValue }
    }

    func path(in rect: CGRect) -> Path {
        var path = Path()
        // ... drawing code that uses insetAmount ...
        path.move(to: CGPoint(x: 0, y: rect.maxY))
        path.addLine(to: CGPoint(x: insetAmount, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.maxX - insetAmount, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        path.addLine(to: CGPoint(x: 0, y: rect.maxY))
        return path
    }
}

With this change, when insetAmount is animated from 10 to 90, SwiftUI will:

  1. Read the start value (10) and end value (90) from animatableData.
  2. Generate intermediate values (e.g., 11, 12, ... 90).
  3. Repeatedly call the set on animatableData with these values, which updates our insetAmount.
  4. Call path(in:) for each update, redrawing the shape and creating the animation.

Let's see this in practice.

(OLD) Animating simple shapes with animatableData – Drawing SwiftUI Tutorial 8/10

Continuing with the same video from Paul Hudson, watch how adding the animatableData property makes the Trapezoid animate smoothly.

Watch from 03:17 to 05:03. This section shows the exact code and explains how SwiftUI now uses this property to drive the animation frame by frame.

The SwiftUI-Lab article "Advanced SwiftUI Animations" provides another great example, animating the number of sides of a polygon.

Polygon Side Count Animation in SwiftUI
A GIF showing a polygon smoothly animating between having 3, 4, 7, and 30 sides. The transition between shapes is fluid.

4. Animating Custom Data Types with VectorArithmetic

So far, we've only animated a single CGFloat. What if we want to animate a more complex, custom data type?

  • Multiple Properties: To animate two properties, you can use AnimatablePair<First, Second>, where First and Second are types conforming to VectorArithmetic. You can even nest AnimatablePair to handle more than two properties.
  • Fully Custom Types: For the ultimate flexibility, you can make your own struct conform to VectorArithmetic. This is how you create truly data-driven animations.

To conform a type to VectorArithmetic, you need to implement several requirements that define how your type behaves in vector operations:

  • static var zero: Self: A static property representing the "zero" or "identity" state of your type.
  • static func + (Self, Self) -> Self: Defines addition.
  • static func - (Self, Self) -> Self: Defines subtraction.
  • mutating func scale(by: Double): Defines how to multiply your type by a scalar (a Double).
  • var magnitudeSquared: Double: The squared length of the vector. Used by the animation system for calculations.

The following reading provides an excellent, in-depth walkthrough of creating a custom ClockTime struct and making it conform to VectorArithmetic to drive a clock animation.

Advanced SwiftUI Animations - Part 1: Paths

This article, 'Advanced SwiftUI Animations - Part 1: Paths', is the most comprehensive resource for this topic. We'll focus on the section where it makes a custom ClockTime type animatable.

Read the section titled 'Making Your Own Type Animatable (with VectorArithmetic)'. Study how the ClockTime struct is defined and then how it's made to conform to VectorArithmetic. The key insight is that it performs all the math operations by converting the time to a single Double (total seconds), and then converts it back.

This ClockTime example is a perfect illustration of the power of this technique. By conforming ClockTime to VectorArithmetic, you teach SwiftUI the physics of how time "moves." When you animate from ClockTime(9, 51, 15) to ClockTime(9, 55, 00), SwiftUI can now generate all the intermediate ClockTime values, allowing the clock hands to move smoothly and correctly to their new positions.

This isn't limited to Shapes. Any View can conform to Animatable. Doing so gives you fine-grained control over layout or drawing, but it comes with a performance consideration: the view's body will be re-evaluated on every single frame of the animation, which can be expensive. It's a powerful tool, but use it when built-in modifiers can't achieve your desired effect.


Conclusion

You've now reached the deepest level of the SwiftUI animation framework. By understanding and using the Animatable and VectorArithmetic protocols, you can move beyond animating simple view properties and start creating animations driven directly by your app's custom data models.

Key Takeaways:

  • SwiftUI can only animate properties whose data types it knows how to interpolate.
  • To make a custom property animatable, the view or shape must conform to the Animatable protocol.
  • The animatableData computed property bridges your custom property to the animation system.
  • The type of animatableData must conform to VectorArithmetic, which provides the mathematical rules for interpolation (addition, scaling).
  • For simple types, you can use CGFloat or Double. For multiple properties, use AnimatablePair.
  • For complex custom types (e.g., a data point in a chart), you can conform the type itself to VectorArithmetic, giving SwiftUI the "recipe" to animate it.

Preview of the Next Module:

This lesson concludes our deep dive into animations. We've covered the essentials for building professional, fluid UIs in SwiftUI. Now, we are going to pivot to another pillar of professional iOS development: testing. In the next module, we will start from the ground up, learning how to write effective, isolated unit tests for your business logic using the Arrange-Act-Assert pattern.