SwiftUI Path Animations
In the fifth of our Thinking in SwiftUI challenges, we asked you to animate a path .
SwiftUI Challenge #5: Path Animations βοΈ
— objc.io (@objcio) March 4, 2020
Animate a path between two starting points, in a motion that doesn't jump between them.
Reply with your solution, and we'll post ours next Tuesday. π
Starter code: https://t.co/aSLm1bS49G pic.twitter.com/E8v0onpbdD
We started the challenge with code that draws a line. The starting point changes based on a boolean property, but despite the withAnimation
, the path itself doesn't animate:
let p1 = CGPoint(x: 50, y: 50)
let p2 = CGPoint(x: 100, y: 25)
let p3 = CGPoint(x: 100, y: 100)
struct ContentView: View {
@State var toggle = true
var body: some View {
VStack {
Button("Toggle") {
withAnimation { self.toggle.toggle() }
}
Path { p in
p.move(to: toggle ? p1 : p2)
p.addLine(to: p3)
}.stroke(lineWidth: 2)
}
}
}
To animate a path, we need to tell SwiftUI which properties are animatable. One way to do this is by wrapping the path in a custom Shape
. We create a Line
shape with properties for both the line's start and end points:
struct Line: Shape {
var start, end: CGPoint
func path(in rect: CGRect) -> Path {
Path { p in
p.move(to: start)
p.addLine(to: end)
}
}
}
To animate the start
and end
properties, we need to expose them via animatableData
, and the type of animatableData
needs to conform to the VectorArithmetic
protocol. Unfortunately, CGPoint
does not conform to VectorArithmetic
, and it's bad practice to add this conformance ourselves: you're not supposed to conform types you don't own to protocols you don't own
, even though in this case, there wouldn't be much harm in it.
Luckily, CGPoint
does conform to Animatable
, so we can use its animatableData
. We can now make both points of the Line
animatable by creating an animatable pair out of the two CGPoint.Animatable
values:
extension Line {
var animatableData: AnimatablePair<CGPoint.AnimatableData, CGPoint.AnimatableData> {
get { AnimatablePair(start.animatableData, end.animatableData) }
set { (start.animatableData, end.animatableData) = (newValue.first, newValue.second) }
}
}
Our new book, Thinking in SwiftUI , discusses the animation system in more detail in chapter six. You can join the early access here .