SwiftUI animation API’si güçlü ama kafa karıştırıcı. .animation() modifier’ı, withAnimation { } block’u, matchedGeometryEffect. Ne zaman hangisini kullanmalı?
12 iOS uygulamamda farklı animation pattern’leri kullandım. Her birinin kendi use case’i var. Bu yazıda pratik örneklerle anlatacağım.
Implicit animation
En basit ve en yaygın. State değişikliği otomatik animate ediyor.
struct ContentView: View {
@State private var isExpanded = false
var body: some View {
VStack {
Rectangle()
.frame(width: isExpanded ? 300 : 100, height: 100)
.animation(.easeInOut, value: isExpanded)
Button("Toggle") { isExpanded.toggle() }
}
}
}.animation(_:value:) modifier’ı. isExpanded değiştiğinde Rectangle’ın frame’ini smooth animate ediyor.
Ne zaman kullanılır:
– Basit property değişiklikleri (frame, opacity, color)
– UI component’in kendi davranışı (toggle, expand)
– Declarative, component-level animation
Dikkat: value parameter’ı kritik. Olmazsa view’ın her change’inde animate oluyor.
Explicit animation
withAnimation block’u state change’i animate ediyor.
@State private var offset: CGFloat = 0
Button("Move") {
withAnimation(.spring()) {
offset = 200
}
}
Circle()
.offset(x: offset)Not: .animation() modifier yok. withAnimation block’u içindeki state change’ler animate oluyor.
Avantaj: Specific action’ın animation’ını kontrol ediyorsun. Başka state change’ler animate olmuyor.
Ne zaman kullanılır:
– User interaction’ın reaction’ı (button tap, swipe)
– Complex state machine transitions
– Network response’dan sonra UI update
matchedGeometryEffect
En güçlü ve en karmaşık. İki farklı view arasında shared element transition.
struct ContentView: View {
@State private var selected: Bool = false
@Namespace private var namespace
var body: some View {
if selected {
FullscreenView(namespace: namespace)
} else {
ThumbnailView(namespace: namespace)
}
}
}
struct ThumbnailView: View {
let namespace: Namespace.ID
var body: some View {
Image("photo")
.matchedGeometryEffect(id: "photo", in: namespace)
.frame(width: 100, height: 100)
}
}
struct FullscreenView: View {
let namespace: Namespace.ID
var body: some View {
Image("photo")
.matchedGeometryEffect(id: "photo", in: namespace)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}Kullanıcı thumbnail’e tıklıyor, image smoothly fullscreen’e büyüyor. iOS Photos app style.
Ne zaman kullanılır:
– Photo gallery → detail transition
– List item → detail view
– Tab bar → full screen
– Any “hero transition” pattern
Dentii’de brushing session thumbnail’den detail view’a transition bu pattern’le. User experience dramatically iyi.
Animation curves
SwiftUI built-in curves:
.linear: constant speed.easeIn: slow start, fast end.easeOut: fast start, slow end.easeInOut: slow-fast-slow.spring(): physical spring simulation.interactiveSpring(): user-controllable spring
Custom timing:
.animation(.easeInOut(duration: 0.5), value: state)
.animation(.spring(response: 0.4, dampingFraction: 0.6), value: state)Spring animations natural hissediyor. Çoğu interaction için default tercih.
Transitions
View’ın girişi/çıkışı için transition:
if show {
Text("Hello")
.transition(.slide)
}Built-in: .slide, .opacity, .move(edge: .top), .scale.
AsymmetricTransition:
.transition(.asymmetric(
insertion: .move(edge: .top),
removal: .move(edge: .bottom)
))Entry/exit farklı animation. Pattern’i toast notification için kullanıyorum.
Performance considerations
Animation pahalı. Her frame GPU work.
Golden rules:
- Transform only.
offset(),scaleEffect(),rotationEffect()GPU-accelerated. - Avoid layout changes.
frame()değiştirmek layout recomputation tetikliyor. - Opacity OK. Alpha change GPU’da ucuz.
- Complex path animation expensive. Basit shape’ler tercih.
Common mistakes
1. Animation her yere uygulamak.
.animation(.easeInOut) // Deprecated, value olmadanBu tüm state change’leri animate ediyor. Unexpected behavior. value: parameter’ı zorunlu iOS 15+.
2. Nested animation chaos.
.animation(.easeIn)
.animation(.spring()) // Hangisi geçerli?Order confusion. En son wins ama net değil. Tek animation modifier kullan.
3. matchedGeometryEffect namespace scope hatası.
İki view farklı parent’larda ama aynı namespace kullanıyor. Transition çalışmıyor.
Fix: Common parent’ta @Namespace tanımla, child’lara propagate et.
Sonuç
Implicit animation basit durumlar için (%70 use case). Explicit animation user-triggered transitions için. matchedGeometryEffect hero transitions için.
Doğru pattern doğru senaryoda kullanıldığında SwiftUI animation’ları Apple’ın native app’leriyle aynı kalitede. Pratik pattern bilgisi ile iyi UX mümkün.