SwiftUI 5: the parts worth using first
SwiftUI 5 adds a few changes that genuinely affect day-to-day development, especially around animation, state, and navigation. This is a practical pass through the parts worth knowing first, with an eye on how they help in production code rather than in demo projects.
New Animation System
SwiftUI 5.0 introduces a brand new animation system that provides more flexible and powerful animation control capabilities.
Keyframe Animations
The new keyframe animation API allows us to create complex multi-stage animations:
struct KeyframeAnimationView: View {
@State private var isAnimating = false
var body: some View {
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.keyframeAnimator(
initialValue: AnimationValues(),
repeating: isAnimating
) { content, value in
content
.scaleEffect(value.scale)
.rotationEffect(value.rotation)
.offset(value.offset)
} keyframes: { _ in
KeyframeTrack(.scale) {
SpringKeyframe(1.2, duration: 0.5)
SpringKeyframe(1.0, duration: 0.5)
}
KeyframeTrack(.rotation) {
LinearKeyframe(.degrees(0), duration: 0.0)
LinearKeyframe(.degrees(360), duration: 1.0)
}
KeyframeTrack(.offset) {
SpringKeyframe(CGSize(width: 100, height: 0), duration: 0.5)
SpringKeyframe(CGSize(width: 0, height: 0), duration: 0.5)
}
}
}
}
struct AnimationValues {
var scale = 1.0
var rotation = Angle.zero
var offset = CGSize.zero
}
Phase Animations
Phase animations allow us to create different animation effects based on different states:
enum LoadingPhase: CaseIterable {
case initial, loading, success, error
}
struct PhaseAnimationView: View {
@State private var phase: LoadingPhase = .initial
var body: some View {
RoundedRectangle(cornerRadius: 12)
.fill(colorForPhase(phase))
.frame(width: 200, height: 60)
.phaseAnimator(LoadingPhase.allCases) { content, phase in
content
.scaleEffect(scaleForPhase(phase))
.opacity(opacityForPhase(phase))
} animation: { phase in
switch phase {
case .initial: .easeInOut(duration: 0.3)
case .loading: .easeInOut(duration: 0.8).repeatForever()
case .success: .spring(duration: 0.5)
case .error: .easeInOut(duration: 0.3)
}
}
}
}
Improved Data Binding
SwiftUI 5.0 has made significant improvements to the data binding system, introducing the new @Observable macro and more powerful data flow management.
@Observable Macro
The new @Observable macro simplifies the creation of observable objects:
@Observable
class UserStore {
var name: String = ""
var email: String = ""
var isLoggedIn: Bool = false
func login() {
// Login logic
isLoggedIn = true
}
func logout() {
// Logout logic
isLoggedIn = false
name = ""
email = ""
}
}
struct UserProfileView: View {
@State private var userStore = UserStore()
var body: some View {
VStack {
if userStore.isLoggedIn {
Text("Welcome, \(userStore.name)")
Button("Logout") {
userStore.logout()
}
} else {
TextField("Username", text: $userStore.name)
TextField("Email", text: $userStore.email)
Button("Login") {
userStore.login()
}
}
}
}
}
Enhanced Navigation
SwiftUI 5.0 brings a completely new navigation system that provides more flexible and powerful navigation control.
NavigationStack Improvements
The new NavigationStack provides better navigation control:
struct NavigationExample: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List {
NavigationLink("User List", value: "users")
NavigationLink("Settings", value: "settings")
NavigationLink("About", value: "about")
}
.navigationDestination(for: String.self) { value in
switch value {
case "users":
UserListView()
case "settings":
SettingsView()
case "about":
AboutView()
default:
Text("Unknown Page")
}
}
}
}
}
Performance Optimizations
SwiftUI 5.0 also has significant performance improvements, especially in rendering large lists and complex views.
Lazy Loading Improvements
The new lazy loading mechanism provides better memory management:
struct OptimizedListView: View {
let items: [Item]
var body: some View {
LazyVStack(spacing: 8) {
ForEach(items) { item in
ItemRowView(item: item)
.id(item.id)
}
}
.scrollTargetLayout()
}
}
Conclusion
What matters in SwiftUI 5 is not novelty by itself, but that a few common jobs get cleaner: animation becomes easier to shape, state is easier to express, and navigation is easier to reason about.
If you are preparing an existing project for SwiftUI 5, it is worth piloting these APIs in small places first, then coming back to state management, navigation shape, and the performance bottlenecks you still have.
The next note will focus on migration choices and the trade-offs that show up in an existing codebase.
Related Articles
A few more you might find worth reading