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ı.

First Last Gif

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

Fisrt Version 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. Sketch Ball

ö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)

Second Version 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

Start Node Options

Ç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ı.

animation helper place

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

animation helper inside

İ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.

Sketch Ball

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