SwiftUI Animation Techniques (2026 Update for iOS 27)
The SwiftUI animation patterns I copy from project to project, updated for iOS 27 and WWDC 2026
Note: This is an updated version of my original SwiftUI Animation Techniques post, refreshed with everything introduced at WWDC 2026 and iOS 27.
I animate almost everything in SwiftUI apps. iOS 27 and WWDC 2026 added enough new tools, the .reorderable() modifier, layer shaders, timeline-driven animation, that my old post needed a rewrite rather than a touch-up.
Here are the patterns I copy from project to project when I want motion that reads instantly and doesn't fight the user.
The pattern in iOS 27 is chaining animation building blocks together instead of interpolating between state A and state B. I've mostly stopped reaching for bare withAnimation calls.
These landed before iOS 27, but they're now my default for any multi-step animation.
PhaseAnimator allows you to define a sequence of phases and animate your view based on the current phase. It's perfect for continuous looping animations or discrete multi-step interactions.
struct PulseView: View {
var body: some View {
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.phaseAnimator([false, true]) { view, isPulsing in
view
.scaleEffect(isPulsing ? 1.5 : 1.0)
.opacity(isPulsing ? 0.0 : 1.0)
} animation: { isPulsing in
.spring(response: 0.8, dampingFraction: 0.5)
}
}
}
When different properties need to animate at different rates, reach for KeyframeAnimator.
struct BouncingHeart: View {
var body: some View {
Image(systemName: "heart.fill")
.keyframeAnimator(initialValue: AnimationValues()) { content, value in
content
.scaleEffect(value.scale)
.rotationEffect(value.rotation)
} keyframes: { _ in
KeyframeTrack(\.scale) {
SpringKeyframe(1.5, duration: 0.3)
SpringKeyframe(1.0, duration: 0.2)
}
KeyframeTrack(\.rotation) {
CubicKeyframe(.degrees(10), duration: 0.15)
CubicKeyframe(.degrees(-10), duration: 0.15)
CubicKeyframe(.zero, duration: 0.2)
}
}
}
}
struct AnimationValues {
var scale = 1.0
var rotation = Angle.zero
}
WWDC 2026 changed how complex visual effects get built. You can now draw with layer shaders, drive animations with timelines, and anchor views during transitions with alignment guides.
Chained together, these cover effects that used to require dropping into Metal or wrestling CoreAnimation layers.
// Example of driving an animation with the new iOS 27 Timeline API
TimelineView(.animation(minimumInterval: 1/60)) { context in
MyComplexShaderView(time: context.date.timeIntervalSinceReferenceDate)
.drawingGroup() // Optimize rendering
}
One of the most requested features that finally landed is the new .reorderable() modifier. Previously, building a custom drag-and-drop reorder experience required complex gesture handling and manual matchedGeometryEffect juggling.
Now, SwiftUI handles the entire visual transition automatically:
struct ReorderableListView: View {
@State private var items = ["Apple", "Banana", "Cherry", "Date"]
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text(item)
.reorderable() // New in iOS 27
}
.onMove { indices, newOffset in
items.move(fromOffsets: indices, toOffset: newOffset)
}
}
.reorderContainer() // Defines the boundary for the drag interaction
}
}
Springs are still the foundation of iOS motion. Use .spring(duration:bounce:) or the newer semantic springs, and prefer physical parameters over abstract timing curves. That's what makes an app feel native.
// Bouncy, playful
.animation(.spring(response: 0.4, dampingFraction: 0.5), value: state)
// Smooth, refined
.animation(.spring(duration: 0.3, bounce: 0.2), value: state)
SwiftUI animations are notorious for stuttering out of the box once view hierarchies get complex or effects get heavy. The new shader and timeline APIs make it easier than ever to build something expensive, so profile before you ship.
If your animations are dropping frames or making the device run hot, you need to rely on Instruments rather than guesswork:
PhaseAnimator cycle, you need to isolate your animating state.The rules I follow:
drawingGroup() Sparingly: Applying .drawingGroup() rasterizes the view to an off-screen Metal texture before rendering. It's great for complex vector shapes that don't change, but if the content mutates during the animation it will wreck your performance.withAnimation blocks are fine, but relying on value-based .animation(_:value:) ensures you only ever animate the exact property you intend to, reducing unintended side effects up and down the view hierarchy.The transitions that used to take a fight now take an afternoon. Just profile them before you ship.