iOS Development

SwiftUI 5: the parts worth using first

A practical pass through the SwiftUI 5 additions that matter in real apps: animation, data flow, navigation, and where each change actually helps.

Ming Li
12/20/2024
12 min read
SwiftUIiOSAnimationNavigation

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.

Discuss an article?

Questions or comments about an article are welcome by email.