
- English
- Türkçe
Swift ile Pürüzsüz Path Morphing Animasyonları
Etiketler: Path Morphing · UIBezierPath · Swift · Animation · Core Animation · Core Graphics
Yumuşak bir animasyon elde etmek için ihtiyacımız olan ana şey tüm elementler arasındaki bütün bir senkron. Normal şartlarda bu zamanlama ve konumlama değerleriyle ilgilidir. Ancak 2 farklı yapıdaki vector objesi arasında geçiş yapmak için senkronu onların arasında kurmamız gerekiyor, ki bu biraz daha uğraştırıcı.

Yukardaki farkı yaratan şey bu faktörlerin senkronu
- Node sayıları
- Node konumları
- Başlangıç noktası
- Çizim yönü
- Line - curve patternleri
Tüm aşamaları GitHub üzerinden incleyebilirsiniz. Animasyonun ilk hali de son hali de aynı animate fonksiyonu kullanılıyor. Yani asıl odağımız fonksiyon değil svgnin durumu ve onu nasıl bir UIBezierPath olarak kullandığımız.
Tüm svg -> UIBezierPath dönüşümleri için tool olarak kendi yaptığım Swift Paths’i kullanacağız.
static func animate(from shapeLayer: CAShapeLayer, to newPath: UIBezierPath, duration:CFTimeInterval = 0.8) {
let morph = CAKeyframeAnimation(keyPath: "path")
morph.values = [shapeLayer.path as Any, newPath.cgPath]
morph.keyTimes = [0, 1]
morph.duration = duration
morph.timingFunctions = [CAMediaTimingFunction(name: .easeInEaseOut)]
morph.fillMode = .both
morph.isRemovedOnCompletion = false
shapeLayer.path = newPath.cgPath
shapeLayer.add(morph, forKey: "smoothMorph")
}
Herhangi bir modifiye yapmadan önce varsayılan vektör objeleriyle animasyon böyle
Farklı Node Sayıları
İlk olarak nodeları eşitlememiz gerek. Tüm kapalı pathlerdeki node sayılarını aynı ve konumlara da benzer tutmalıyız. Bu sayede her bir nodeun dönüşeceği bir eşi olur.
Mesela örnekti tek bir gözü bu şekilde modifiye etmeliyiz.

| örnek node sayıları | ilk | son |
|---|---|---|
| kırmızı - iç | 4 | 7 |
| mavi - iç | 6 | 7 |
| kırmızı - dış | 7 | 8 |
| mavi - dış | 4 | 8 |
Tüm süreç boyunca manuel tasarım müdahelesi gerektiren tek kısım burası. Bu işlemi Sketch veya Figmadan sadece pathlere vektör aracı ile tıklayıp node ekleyerek yapabilirsiniz herhangi bir grafik bilgisi gerektirmez.
Burada toplam node sayısı artabilir, yanlış eşleşmeleri önlemek için ekstra node ile konumlanmaları yakın tutmak daha iyi olacaktır (Mesela mavinin tepesinde node olduğu için kırmızıya ekstra olarak eklendi)
Node sayıları eşitlenmiş svgleri UIBezierPath yapıp tekrar aynı animate fonksiyonunu uyguladığımızda bile çıktı oldukça toparlıyor.
Yine de bazı sapıtmalar var: şapka saat yönünde dönüyor, sağ gözün iç ve dış pathleri farklı yönlere doğru dönüyor, sol gözün içi kendi içinden geçiyor. Tabii bunları düzeltmenin de oldukça kolay bir yolu var.
Çizim yöntemleri
civilHat.move(to: CGPoint(x: 123, y: 456))
civilHat.addCurve(...
civilHat.addCurve(...
civilHat.addCurve(...
civilHat.close()
Core Graphics pathleri eşleştirirken mesafe ile ilgilenmez, tek ilgilendiği .move() un hangi konumdan başladığıdır. Çizim yönü için de yakınlıkları da sorun değildir, sonraki addCurve() yönü belirler.
Bu yüzden grafik editörlerden yerleştirdiğimiz nodelar ne kadar yakın olursa olsun koddaki pattern uyuşmadığı sürece şu an meydana gelen kayma ve iç içe geçmele kaçınılmaz. Ve elimizdeki örnekte başlangıç ve yön için bir sürü olasılık var

Çizim sırasında iki vektöründe aynı noktalardan başlayıp aynı yöne çizilmesi gerek. Üstelik sadece genel path değil tüm sub-pathler de bu şekilde uyumlu olmalı.

Bunları ayarlamak için Swift Pathse svg dosyalarımızı yükledikten sonra alt kısımdaki [animation helper] kısmına gitmeliyiz.

İki vektörü de sırsıyla yükleyip, aynı bölgelerden başlangıç noktaları seçtikten sonra istediğimiz sonuca ulaşıyoruz. Her vektör objesi için ─ output.swift─ bize tüm pathleri UIBezierPath() şeklinde tek seferde verebiliyor.

Fakat sonuca bu kadar hızlı ulaşamaya da bilirdik. Şu an çizim yönleri aynı ve çoğunlukla yapı curvelerden oluşuyor. Eğer bu iki etkenle ilgili problem yaşanırsa yine [animation helper] üzerinden Normalize L -> C ve Reverse pathi de işin içine katarak morphu düzeltebiliriz.
Animasyon işi her daim bir miktar dene-yanıl yükü taşıyor ama zevki de biraz burada. Bu karakter animasyonunu parti oyunu uygulamam olan Spy’da kullanmak istedim ancak o zaman bu dönüşümleri yaparken oldukça zorlandım. İstediğim kişiselleştirmeler hiç bir araçta yoktu. Ardından da Swift Paths doğdu zaten 😌
Çoklu dönüşüm
Animasyonu bir bütün yapmak için her bir path için ayrı bir animate() tanımlayıp bunları senkron bir şekilde çağrılabiliriz.
func civilToSpyMorph() {
animate(from: civilRightEye, to: spyRightEye)
animate(from: civilLeftEye, to: spyLeftEye)
animate(from: civilHat, to: spyHat)
}
func spyToCivilMorph() {
animate(from: spyRightEye, to: civilRightEye)
animate(from: spyLeftEye, to: civilLeftEye)
animate(from: spyHat, to: civilHat)
}
Daha detaylı konumlandırma ve fonksiyon kodları ve kullanılan assetler için githuba bakabilirisniz.
Swift Paths geliştirmeme yardımcı olmak için bir yazılımcı gözünden önerilerinizi GitHub üzerinden iletebilirsiniz.
github: zeynepmuslim/swift-path-morph