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()
}
}