Skip to main content

Fließende Übergänge zwischen Ansichten

Diese View zeigt eine Liste von Elementen an, wobei beim Wechsel zur Detailansicht ein sanfter Übergang mit Skalierungs- und Ausblendanimation verwendet wird. Die Animation sorgt für ein nahtloses Benutzererlebnis beim Navigieren zwischen Listen- und Detailansicht.

🔍 Zweck

  • Anzeige einer Artikelliste mit animiertem Übergang zur Detailseite
  • Präsentation von Profilen oder Kontakten mit eleganter Navigation
  • Produktübersichten in Shopping-Apps mit animierten Details
  • Galeriedarstellung mit sanftem Wechsel zu Bilddetails
  • Onboarding-Sequenzen mit flüssigen Seitenübergängen

📄 Codebeispiel

import SwiftUI

struct Item: Identifiable, Equatable {
    let id = UUID()
    let title: String
    let subtitle: String
    let color: Color
}

struct AnimatedListView: View {
    @State private var selectedItem: Item? = nil
    @Namespace private var animation
    
    let items: [Item] = [
        Item(title: "Apple", subtitle: "Red and delicious", color: .red),
        Item(title: "Banana", subtitle: "Sweet and yellow", color: .yellow),
        Item(title: "Grape", subtitle: "Juicy purple fruit", color: .purple)
    ]
    
    var body: some View {
        ZStack {
            if selectedItem == nil {
                // List View with fade and scale transition out
                List(items) { item in
                    Button {
                        withAnimation(.spring(response: 0.45, dampingFraction: 0.85)) {
                            selectedItem = item
                        }
                    } label: {
                        HStack(spacing: 16) {
                            Circle()
                                .fill(item.color)
                                .frame(width: 40, height: 40)
                                .matchedGeometryEffect(id: item.id, in: animation)
                            VStack(alignment: .leading) {
                                Text(item.title)
                                    .font(.headline)
                                Text(item.subtitle)
                                    .font(.subheadline)
                                    .foregroundStyle(.secondary)
                            }
                        }
                        .padding(.vertical, 8)
                    }
                }
                .listStyle(.plain)
                .transition(.asymmetric(
                    insertion: .identity,
                    removal:
                        .scale(scale: 0.9).combined(with:.opacity)
                ))
            } else if let selected = selectedItem {
                // Detail View with fade and scale transition in
                VStack(spacing: 24) {
                    Spacer()
                    Circle()
                        .fill(selected.color.gradient)
                        .frame(width: 140, height: 140)
                        .matchedGeometryEffect(id: selected.id, in: animation)
                        .shadow(radius: 10, y: 6)
                    
                    Text(selected.title)
                        .font(.largeTitle.bold())
                    
                    Text(selected.subtitle)
                        .font(.title3)
                        .foregroundStyle(.secondary)
                    
                    Spacer()
                    
                    Button("Back") {
                        withAnimation(.spring(response: 0.45, dampingFraction: 0.85)) {
                            selectedItem = nil
                        }
                    }
                    .buttonStyle(.borderedProminent)
                    .padding(.bottom, 32)
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(
                    selected.color.opacity(0.10).ignoresSafeArea()
                )
                .transition(
                    .asymmetric(
                        insertion:
                            .scale(scale: 1.1).combined(with:.opacity),
                        removal:
                            .opacity
                    )
                )
            }
        }
        // Animate root ZStack changes for smooth transitions
        .animation(.easeInOut(duration: 0.5), value: selectedItem)
        .navigationTitle("Fruits")
    }
}

#Preview {
    NavigationStack {
        AnimatedListView()
    }
}