Tüm Yazılar
KategoriSwiftUI
Okuma Süresi
9 dk
Yayın Tarihi
2026-10-15
Kelime Sayısı
1.316kelime

Kahveni hazırla - bu içerikli bir makale!

SwiftUI-Animation-Masterclass 2.0: çağrılabilen, test edilebilen ve kapatılabilen hareket

Özet

Bir SwiftUI animasyon paketi spring, decay ve particle'ları neden düz value type olarak açıyor; 2.0.0 yeniden kurulumu testler hakkında ne öğretti.

  • Bu yazı paketin çözmeye çalıştığı sorunu, arkasındaki tasarım kararlarını, onu şekillendiren bir hatayı ve hâlâ var olan sınırları anlatıyor.
  • DampedSpring, Decay, GravityBounce, RubberBand, SnapPoints, ParticleSystem ve ChangeEffect.state(at:) hareketi hiçbir view kodu olmadan hesaplıyor.
  • Paketi Swift Package Manager ile ekleyin: swift dependencies: [ ], targets: [ ] Sonra herhangi bir view'a bir modifier ekleyin.
  • 2.0.0'ın test süitinde 13 suite içinde 73 Swift Testing testi var; normal bir swift test çalıştırmasında 70'i koşuyor, README görsellerini üreten üçü ise bir çıktı klasörü verilmedikçe atlanıyor.
SwiftUI-Animation-Masterclass 2.0: çağrılabilen, test edilebilen ve kapatılabilen hareket

SwiftUI-Animation-Masterclass, iOS 17+ ve macOS 14+ üzerinde SwiftUI için hareket sunan bir Swift

paketi: fiziğe dayalı spring, decay ve sekme animasyonları, parmağın hızını koruyan sürükleme

hareketleri, particle efektleri, değer değişince oynayan efektler ve geçişler. 2.0.0 sürümü

15 Ekim 2026'da yayınlandı. Bu yazı paketin çözmeye çalıştığı sorunu, arkasındaki tasarım

kararlarını, onu şekillendiren bir hatayı ve hâlâ var olan sınırları anlatıyor.

İçindekiler


Sorun

SwiftUI'ın hazır animasyonları bir soruyu iyi yanıtlıyor: "Bu değişikliği şu eğriyle canlandır."

Ama hareketin kendisini dışarı açmıyor. Sık karşılaşılan birkaç etkileşim tam olarak buna

ihtiyaç duyuyor:

  • Bir flick nereye düşecek? Aşağıdan açılan bir panel, animasyon başlamadan önce hangi

durağa gideceğini bırakma konumuna _ve_ bırakma hızına bakarak seçmek zorunda.

  • Parmağın hızı spring'e nasıl aktarılır? Hızlı bir sürüklemeden sonra spring durağan

başlarsa view gözle görülür biçimde durup yeniden hareket ediyor.

  • Bir efekt nasıl test edilir? Bir sallanmayı ya da sekmeyi görmenin tek yolu uygulamayı

çalıştırıp izlemekse, onun hakkında bir test yazmak zor.

Paket, tek bir ekranda iyi bir efekt isteyen ama bunun için bir framework benimsemek istemeyen

uygulama geliştiricileri için. Sunduğu her şey bir view modifier, bir Animation, bir

Transition ya da düz bir value type; hiçbir bağımlılığı yok.

Tasarım kararları ve alternatifler

Matematik value type'larda, modifier'lar ince

DampedSpring, Decay, GravityBounce, RubberBand, SnapPoints, ParticleSystem ve

ChangeEffect.state(at:) hareketi hiçbir view kodu olmadan hesaplıyor. Modifier'lar yalnızca

environment'ı okuyor, gesture ya da animasyon durumunu tutuyor ve sonucu uyguluyor. Alternatif,

mantığı her ViewModifier'ın içine koymak, yazması daha kısa ama ekran olmadan birim testi

yapılacak bir şey bırakmıyor.

Kapalı formda çözülen spring

DampedSpring, sönümlü yay denklemini kare kare adımlamak yerine analitik çözümünü kullanıyor.

Konum zamanın bir fonksiyonu oluyor; böylece her kare, ister bir test ister bir GIF render'ı için

olsun, tek başına hesaplanabiliyor. Bedeli, az sönümlü, kritik sönümlü ve aşırı sönümlü durumları

ayrı ayrı ele almak. Bu işi doğrulamak için testler implementasyondan kopyalanmış sayılarla

karşılaştırma yapmıyor: PhysicsTests aynı yayı sayısal olarak integre edip iki sonucu

karşılaştırıyor.

Akla ilk gelen alternatif SwiftUI'ın kendi .spring(...)'i ve paket yine oraya varıyor:

DampedSpring.animation(initialVelocity:) sıradan bir Animation döndürüyor,

DampedSpring.relativeVelocity(_:from:to:) da saniyede nokta cinsinden gesture hızını

SwiftUI'ın beklediği göreli hıza çeviriyor. Ancak tek başına .spring(...) kullanmak, yerleşme

süresi gibi değerlere erişim vermiyor.

Particle'lar için tek Canvas ve seed'li simülasyon

ParticleSystem kendi zamanlayıcısı olmayan bir struct; update(by:in:) çağrıldığında ilerliyor.

ParticleView onu bir TimelineView içinden sürüyor ve bütün particle'ları tek bir Canvas'a

çiziyor; canlı particle kalmayınca saat duruyor. Rastgelelik küçük bir SplitMix64 üreticisinden

geliyor, yani bir seed particle'ları tamamen belirliyor. Testlerin ve README animasyonlarının her

çalıştırmada aynı kareleri üretmesini sağlayan bu. Pakette cihaz üzerinde yapılmış bir performans

ölçümü yok; tek Canvas bir tasarım tercihi, bir benchmark sonucu değil.

Her efektin içinde Reduce Motion

Her efekt accessibilityReduceMotion değerini environment'tan kendisi okuyor: particle'lar

çizilmiyor, değişim efektleri oynamıyor, geçişler yumuşak bir solmaya dönüyor ve sürüklemeler kısa

bir ease-out ile yerine oturuyor. Alternatif, ayarı her çağrı noktasında kontrol etmeyi

geliştiriciye bırakmak, ama bunu unutmak kolay. "Respecting Reduce Motion" adlı DocC makalesi her

efektin ne yaptığını listeliyor ve bunun nerede bittiğini de söylüyor: withAnimation ile kendi

başlattığınız animasyonlar sizin yerinize değiştirilmiyor.

Bilerek dar tutulan platformlar

Paket Swift 6.2 araçlarını, iOS 17 ve macOS 14'ü gerektiriyor ve Swift 6 dil modunda derleniyor.

visualEffect, keyframeAnimator ve iOS 17'nin Transition protokolü bu sürümlere ihtiyaç

duyuyor. CI'da derlenip test edilmeyen platformlar manifest'te yer almıyor.

Geçmişten öğrenilen: hiçbir şey yapmayan bir modifier

Repo Ağustos 2025'te açıldı. 1.0.0 sürümü (Şubat 2026) derlenemiyordu: manifest, repoda olmayan

Sources/Components ve Sources/Utilities klasörlerini gösteriyordu. Haziran 2026'da kodun büyük

kısmı kaldırıldı (15850b4, 5.991 satır silindi); geriye bir demo view ve yeni bir public API,

scrollDriven(multiplier:), kaldı.

Yeniden kurulum sırasında bu modifier'ı okumak sorunu ortaya çıkardı. d62293e itibarıyla

gövdesi şöyleydi:

swift
1public func body(content: Content) -> some View {
2 // High-performance GeometryReader implementation
3 content
4 .overlay(
5 GeometryReader { proxy in
6 Color.clear.preference(key: ScrollOffsetKey.self, value: proxy.frame(in: .global).minY)
7 }
8 )
9}

multiplier hiç okunmuyordu, ölçülen frame de kimsenin izlemediği bir preference'a yazılıyordu.

API derleniyordu, dokümantasyonu vardı ve hiçbir etkisi yoktu. e08cc0a commit'i iki imzayı da

korudu ve modifier'ın adının söylediğini yapmasını sağladı:

swift
1public func body(content: Content) -> some View {
2 let multiplier = reduceMotion ? 0 : multiplier
3 content.visualEffect { effect, proxy in
4 effect.offset(y: ScrollParallax.offset(minY: proxy.frame(in: .scrollView).minY, multiplier: multiplier))
5 }
6}

Matematik ScrollParallax.offset(minY:multiplier:) adlı saf bir fonksiyona taşındı; fonksiyon

-minY · multiplier döndürüyor, sonlu olmayan girdide 0 veriyor ve ScrollTests bunu

doğruluyor. CHANGELOG bu değişikliği "Changed" başlığı altında listeliyor, çünkü mevcut

kullanıcılar artık view'larının hareket ettiğini görüyor.

Aynı inceleme fizik tiplerinde daha sessiz bir hata sınıfı da buldu. DampedSpring, Decay ve

GravityBounce sabitlerini initializer'da doğruluyordu, ama sabitler public var olduğu için

sonradan mass = 0 atamak kontrolü atlıyor ve her metot NaN döndürüyordu. 6a44ac5 aynı

precondition'ı her özelliğin didSet'ine ekledi. Ardından f0698d8 ikinci bir açığı kapattı:

.infinity değeri > 0 kontrolünden geçiyor ve sonsuz bir stiffness ∞/∞ üretiyordu. Kontroller

artık sonlu değer istiyor; commit mesajı, eklenen 11 exit-test beklentisinin düzeltme olmadan

hepsinin başarısız olduğunu not ediyor.

Son ayrıntı testlerin kendisiyle ilgili. Bir test drop.gravity = 2000 atayıp ardından

drop.gravity == 2000 kontrol ediyordu; bu yalnızca saklanan bir özelliğin değeri geri verdiğini

kanıtlar. 921732a bunu, 100 birimlik bir düşüşün ilk kez √(2·100/2000) saniye sonra yere

değdiğini kontrol eden bir testle değiştirdi; böylece yeni gravity değeri harekete hiç ulaşmazsa

test başarısız oluyor.

Üç durumda da ders aynı: derlenen bir public API, çalışan bir API ile aynı şey değil; bir test de

ancak davranış bozulduğunda başarısız oluyorsa işe yarıyor.

Kullanım

Paketi Swift Package Manager ile ekleyin:

swift
1dependencies: [
2 .package(url: "https://github.com/muhittincamdali/SwiftUI-Animation-Masterclass.git", from: "2.0.0")
3],
4targets: [
5 .target(name: "YourApp", dependencies: [
6 .product(name: "SwiftUIAnimationMasterclass", package: "SwiftUI-Animation-Masterclass")
7 ])
8]

Sonra herhangi bir view'a bir modifier ekleyin. Bu beğeni düğmesi her dokunuşta zıplıyor ve bir

avuç kalp fırlatıyor:

swift
1import SwiftUI
2import SwiftUIAnimationMasterclass
3 
4struct LikeButton: View {
5 @State private var likes = 0
6 
7 var body: some View {
8 Button("Like", systemImage: "heart.fill") { likes += 1 }
9 .buttonStyle(.borderedProminent)
10 .changeEffect(.jump(), value: likes)
11 .particleBurst(.hearts, trigger: likes)
12 }
13}

Sorun bölümündeki aşağıdan açılan panel tek bir modifier. Yavaş bir bırakış en yakın durağa

dönüyor, bir flick ise bir sonrakine devam ediyor:

swift
1struct PullUpPanel: View {
2 @State private var detent = 1
3 
4 var body: some View {
5 RoundedRectangle(cornerRadius: 24)
6 .fill(.blue.gradient)
7 .frame(height: 400)
8 .overlay(alignment: .top) {
9 Capsule().fill(.white.opacity(0.6)).frame(width: 40, height: 5).padding(10)
10 }
11 .snapDrag(.vertical, offsets: [0, 260], selection: $detent)
12 }
13}

İki örnek de örnek uygulamanın ve test target'ının derlediği ReadmeSamples.swift dosyasından

geliyor; bu yüzden README, bir derleme kırılmadan API'den uzaklaşamıyor. Bütün efektleri bir arada

görmek için AnimationShowcase()'i bir #Preview içine koyabilir ya da MotionGallery örnek

uygulamasını bir iPhone simülatöründe veya Mac'te çalıştırabilirsiniz.

Sınırlar

  • Cihazda performans ölçümü yok. Particle sayısı 10.000 ile, simülasyon adımı 1/60 saniye ile

sınırlı; ama kare hızı ve CPU kullanımı gerçek donanımda ölçülmedi.

  • Render kontrol ediliyor, snapshot testi yok. Testler view'ları ImageRenderer ile çizip

kapsama ve piksel kontrolü yapıyor; referans görüntü karşılaştırması yok ve scrollDriven'ın

visualEffect bağlantısını bir UI testi doğrulamıyor.

  • Çökme kontrolleri yalnızca macOS'ta. Geçersiz sabitlerin süreci durdurduğunu kanıtlayan

exit test'ler, Swift Testing'in yalnızca macOS'ta bulunan bir özelliğini kullanıyor.

  • Precondition'lar release derlemesinde de çökertiyor. Sıfır kütle gibi geçersiz bir

yapılandırma uygulamayı yazıldığı yerde durduruyor; gesture hızı gibi sistemden gelen değerler

ise sınırlanıyor.

  • Tek skaler hız. SwiftUI'ın spring'i tek bir başlangıç hızı alıyor; bu yüzden iki boyutlu bir

sürükleme hızını hareket yönüne izdüşürüyor.

  • Dar platform aralığı. iOS 17+, macOS 14+ ve Xcode 26 ya da üstü.

2.0.0'ın test süitinde 13 suite içinde 73 Swift Testing testi var; normal bir swift test

çalıştırmasında 70'i koşuyor, README görsellerini üreten üçü ise bir çıktı klasörü verilmedikçe

atlanıyor. README'deki yol haritasında bir path boyunca ilerleyen animasyon ve aynı Transition

protokolü üzerine kurulu yeni geçişler yer alıyor.

Kaynak kod: github.com/muhittincamdali/SwiftUI-Animation-Masterclass

Etiketler

#swiftui#animation#physics#particles#swift-package#accessibility
Muhittin Çamdalı

Muhittin Çamdalı

Senior Mobile Engineer

12+ yıllık deneyime sahip Senior Mobile Engineer. iOS, Flutter ve React Native ile mobil ürünler geliştiriyorum; full-stack ve AI-native çalışıyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Onay e-postasındaki bağlantıyı açıp “Aboneliğimi onayla” düğmesine bastığında aboneliğin başlar. Bültende açılma/tıklama istatistikleri tutulur; dilediğin an tek tıkla ayrılabilirsin. Gizlilik

Paylaş

İlgili İçerik