Jacob’s Tech Tavern

Jacob’s Tech Tavern

Apple Animation Through the Ages: 2019

Try out real code from NeXTSTEP to Mac OS X to SwiftUI

Jacob Bartlett's avatar
Jacob Bartlett
Sep 26, 2023

Timeline

1989 — NeXTSTEP Generation: Display PostScript

2001 — Apple’s Renaissance: Quartz & OpenGL

2007 — The Modern Era: Core Animation

2014 — The Performance Age: Metal

2019 — The Declarative Revolution: SwiftUI


2019 — The Declarative Revolution: SwiftUI

Let’s set the stage one last time as we flash toward the present.

In 2013, React slammed the heft of Facebook face-first into the constantly-changing mess that was frontend in the early 2010s. React treated UI as a function of state, introduced the virtual DOM for high performance, and worked with component-based, compositional rendering.

Core React.js features from c-sharpcorner.com

Apple, which had witnessed the gradual neutering of Microsoft Windows by the web in the 2000s, had been doing everything in its power to kill the web as a viable platform on iOS.

But there were barbarians at the gates!

Facebook’s React Native introduced cross-platform app compilation using standard React syntax in 2015. Shortly after, in 2017, Google introduced Flutter, which used the Dart language to allow React-style UI development across web, Android, and iOS. Both approaches came packed with developer-friendly ergonomics like hot-reloading UI.

Image from gravitywell.co.uk.

This competition, coupled with the uncomfortable platform split across iPhone (UIKit), Mac (AppKit), and Apple Watch (WatchKit), native app development was beginning to look like a bad long-term bet for new engineers.

SwiftUI was announced as the future of Apple platform UI development in 2019, spawning years of arguments about whether it was production-ready (it is).

SwiftUI was the declarative framework to create UI across all Apple platforms. It was built on Core Animation and Metal foundations and made animation far easier than ever before.

WWDC Platforms State of the Union, 2022

If you’ve been following the article, and we aren’t yet in the 2040s rendering your Vision Pro projects with Generative AR, you don’t need me to tell you how to set up a SwiftUI app.

Let’s get right to it.

As always, my sample code for this mini-project is available on GitHub.

SwiftUI 1.0: Basic animations

Looking through the SFSymbols catalogue for inspiration, I stumbled upon this number, dumbbell.fill. A sporty theme would work great here!

dumbbell.fill from SFSymbols

In SwiftUI, animations are truly a function of state. When we animate a state change, the UI, which depends on the state, naturally animates to represent the new state. This level of declarative-ness could make Core Animation blush!

We can get started in ContentView.swift with a simple dumbbell image at the bottom of the screen.

import SwiftUI

struct ContentView: View {

    var body: some View {
        Image(systemName: "dumbbell.fill")
            .imageScale(.large)
            .foregroundColor(.accentColor)
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
    }
}

I want to create a looping animation that creates the effect of doing alternating reps with two dumbbells. This would involve two components:

  1. A vertical translation

  2. Image scaling

We can use scaleEffect modifier, along with the alignment property of the frame modifier to achieve this.

struct ContentView: View {

    @State private var isAnimated: Bool = false
    
    var body: some View {
        Image(systemName: "dumbbell.fill")
            .imageScale(.large)
            .foregroundColor(.accentColor)
            .scaleEffect(isAnimated ? 4 : 1)
            .frame(maxWidth: .infinity,
                   maxHeight: .infinity,
                   alignment: isAnimated ? .top : .bottom)
            .animation(loopingAnimation, value: isAnimated)
            .onAppear {
                isAnimated = true
            }
    }
    
    private var loopingAnimation: Animation {
        .easeInOut(duration: 0.75)
        .repeatForever(autoreverses: true)
    }
}

We create an implicit animation using the animation modifier. This tells the SwiftUI rendering engine to animate a state change when the specified value updates. Here, we’re just using a simple isAnimated property as the value we sets in onAppear; and using repeatForever on our Animation to ensure it loops. Now we get a clean lifting effect:

Going further, we can make this look even snazzier and give the code a clean-up.

If you know anything about me, you’ll know I love to use enums where I can. Here, I create a Rep enum to represent the state; and store the visual effects I want for each state.

enum Rep {
    case left
    case right
    
    mutating func lift() {
        switch self {
        case .left: self = .right
        case .right: self = .left
        }
    }
    
    func scale(rep: Rep) -> Double {
        self == rep ? 4 : 1
    }

    func alignment(rep: Rep) -> Alignment {
        self == rep ? .top : .bottom
    }
}

Since I want to make two dumbbells, the effect will depend on which side I lift in my rep. The scaleEffect and alignment will be larger and higher when the rep matches the dumbbell’s side.

Here’s the final SwiftUI view:

struct DumbbellView: View {

    @State private var rep: Rep?

    var body: some View {
        HStack {
            dumbbell(side: .left)
            dumbbell(side: .right)
        }
        .padding(40)
        .background(Color.indigo)
        .onAppear {
            if rep == nil {
                rep = .left
                rep?.lift()
            }
        }
    }
            
    private func dumbbell(side: Rep) -> some View {
        Image(systemName: "dumbbell.fill")
            .imageScale(.large)
            .foregroundColor(.white)
            .scaleEffect(side.scale(rep: rep ?? .left))
            .frame(maxWidth: .infinity,
                   maxHeight: .infinity,
                   alignment: side.alignment(rep: rep ?? .left))
            .animation(loopingAnimation, value: rep)
    }
    
    private var loopingAnimation: Animation {
        .easeInOut(duration: 0.75)
        .repeatForever(autoreverses: true)
    }
}

To make two dumbbells, we’ve refactored our SwiftUI code to create a dumbbell view via a helper method, initializing an icon for each side. Since we use repeatForever in our Animation, we only need to change the state once, using onAppear, to cause the animated state change to continue forever.

Now we’ve produced a truly gainful animation.

(I need to start going to the gym again)

SwiftUI 2.0: matchedGeometryEffect

SwiftUI 1.0 was both revolutionary and highly flawed. While introducing UI as a function of state to the native iOS world and a beautiful declarative syntax, navigation was completely busted, and many key features were missing.

2020’s release on iOS 14 fixed many of these problems and introduced some great new features — my favourite being matchedGeometryEffect. This creates an animation effect similar to Keynote’s Magic Move.

matchedGeometryEffect requires you to set unique identifiers between elements, which allows SwiftUI to maintain an understanding of the view’s identity even between different subviews in a hierarchy. The rendering engine captures the geometry — that is, position and size — and creates a smooth motion that interpolates the changes between the two views when a transition happens.

Subscribe for free to continue reading

Enter your email and get every post from Jacob’s Tech Tavern.
Already have an account? Sign in
© 2026 Jacob Bartlett · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture