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.

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.

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.
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!

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:
A vertical translation
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.
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.





