paint-brush
Pure UIKit'te Duman ve Aynalarile@petertech
79,450 okumalar
79,450 okumalar

Pure UIKit'te Duman ve Aynalar

ile Peter J.10m2023/05/19
Read on Terminal Reader
Read this story w/o Javascript

Çok uzun; Okumak

UIKit, doğru kullanıldığında muhteşem görsel efektler yaratabilen güçlü bir araç setidir. Bu makalede UIKit'in derinliklerine ineceğiz ve aynaya benzer bir yansıma yaratma tekniğini sergileyeceğiz. Bu efekt, uygulamanıza genellikle yalnızca karmaşık grafik araçlarıyla elde edilebilecek gibi görünen, görsel olarak etkileyici ve ilgi çekici bir görünüm kazandırabilir.
featured image - Pure UIKit'te Duman ve Aynalar
Peter J. HackerNoon profile picture
0-item
1-item

Uygulama geliştiricileri olarak biz sadece kodlayıcı değiliz; yaratıcıyız, geliştiriciyiz ve bazen de illüzyonistiz. Uygulama geliştirme sanatı yalnızca kod ve tasarımın ötesine geçer. Bazen bu, kullanıcıların dikkatini çeken ve sürükleyici bir deneyim yaratan bir sürpriz ve yanılsama unsuru yaratmakla ilgilidir. Bu sefer 2D dünyasının konfor alanımızın dışına çıkıyoruz ve 3D'nin büyüleyici dünyasına cesur bir adım atıyoruz.


UIKit, kullanıcı arayüzleri oluşturmaya yönelik bir dizi araçtan daha fazlasıdır. Doğru kullanıldığında muhteşem görsel efektler yaratabilen güçlü bir araç setidir. Bu makalede UIKit'in derinliklerine ineceğiz ve aynaya benzer bir yansıma yaratma tekniğini sergileyeceğiz. Bu efekt, uygulamanıza genellikle yalnızca karmaşık grafik araçlarıyla elde edilebilecek gibi görünen görsel olarak etkileyici ve ilgi çekici bir görünüm verebilir, ancak koddan başka hiçbir şey kullanılmadan hazırlanmıştır.

Nihai sonuç

Bu güzel, parlak küpü inceleyin. Metal kullanılmadığı için asla paslanmaz.


Şimdi kod kullanarak nasıl oluşturulacağını öğrenelim.

Önce bazı temel bilgiler

Amacımız açısından UIKit, Quartz Core'un üzerinde ince bir katman görevi görerek bize 3D özelliklerine ücretsiz erişim sağlıyor. Bir UIView işletim sisteminin ekran görüntüsü oluşturmak için kullandığı gerçek bileşen olan CALayer nesnesine bir referans içerir. CALayer ekrandaki sunumunu etkileyen üç özelliği vardır: konum, sınırlar ve dönüşüm. İlk ikisi oldukça açıklayıcıdır, transform ise herhangi bir 4x4 matris ile başlatılabilir. Birden fazla 3 boyutlu katmanın aynı anda sunulması gerektiğinde, alt katmanlarını 2 boyutlu bir düzlem üzerine düzleştirmek yerine 3 boyutlu alanını benzersiz şekilde koruyan özel bir CATransformLayer kullanmamız gerekir.

Bir küp

Basit bir küp çizerek başlayalım. Öncelikle her iki tarafın konumunu ayarlamak için bir yardımcı fonksiyon oluşturacağız:


 func setupFace( layer: CALayer, size: CGFloat, baseTransform: CATransform3D, translation: (x: CGFloat, y: CGFloat, z: CGFloat), rotation: (angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat) ) { layer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size, height: size)) var transform = baseTransform transform = CATransform3DTranslate(transform, translation.x, translation.y, translation.z) transform = CATransform3DRotate(transform, rotation.angle, rotation.x, rotation.y, rotation.z) layer.transform = transform }


Daha sonra ViewController'ın viewDidLoad fonksiyonunun gövdesinde küpün altı tarafını da birleştireceğiz:


 let cubeLayer = CATransformLayer() cubeLayer.position = CGPoint(x: view.bounds.midX, y: view.bounds.midY) view.layer.addSublayer(cubeLayer) let cubeSize: CGFloat = 200.0 var baseTransform = CATransform3DIdentity baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) baseTransform = CATransform3DRotate(baseTransform, -0.5, 1.0, 0.0, 0.0) let frontFace = CALayer() frontFace.isDoubleSided = false frontFace.backgroundColor = UIColor.blue.cgColor setupFace(layer: frontFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, 0.0, cubeSize * 0.5), rotation: (0.0, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(frontFace) let backFace = CALayer() backFace.isDoubleSided = false backFace.backgroundColor = UIColor.red.cgColor setupFace(layer: backFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, 0.0, -cubeSize * 0.5), rotation: (-.pi, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(backFace) let leftFace = CALayer() leftFace.isDoubleSided = false leftFace.backgroundColor = UIColor.green.cgColor setupFace(layer: leftFace, size: cubeSize, baseTransform: baseTransform, translation: (-cubeSize * 0.5, 0.0, 0.0), rotation: (-.pi * 0.5, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(leftFace) let rightFace = CALayer() rightFace.isDoubleSided = false rightFace.backgroundColor = UIColor.yellow.cgColor setupFace(layer: rightFace, size: cubeSize, baseTransform: baseTransform, translation: (cubeSize * 0.5, 0.0, 0.0), rotation: (.pi * 0.5, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(rightFace) let topFace = CALayer() topFace.isDoubleSided = false topFace.backgroundColor = UIColor.cyan.cgColor setupFace(layer: topFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, -cubeSize * 0.5, 0.0), rotation: (.pi * 0.5, 1.0, 0.0, 0.0)) cubeLayer.addSublayer(topFace) let bottomFace = CALayer() bottomFace.isDoubleSided = false bottomFace.backgroundColor = UIColor.gray.cgColor setupFace(layer: bottomFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, cubeSize * 0.5, 0.0), rotation: (-.pi * 0.5, 1.0, 0.0, 0.0)) cubeLayer.addSublayer(bottomFace)


İşte bu kod çalışırken nasıl görünüyor:

Dik olarak yansıtılan bir küp


İnkar edilemez bir şekilde 3 boyutlu, ama bir şeyler kötü hissettiriyor, değil mi? Sanatta 3 boyutlu perspektif kavramına ilk kez 15. yüzyılda İtalyan Rönesans ressamları hakim oldu. Neyse ki benzer bir etkiyi yalnızca perspektif projeksiyon matrisi kullanarak elde edebiliriz:


 var baseTransform = CATransform3DIdentity baseTransform.m34 = -1.0 / 400.0 baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) baseTransform = CATransform3DRotate(baseTransform, -0.5, 1.0, 0.0, 0.0)


Şimdi sonuca bakalım:

Bu küpün perspektifi var


Daha iyi, değil mi? Perspektif etkisini yaratan m34'teki -1.0 / 400.0 terimidir. Gerçek matematik için bkz. https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/building-basic-perspective-projection-matrix.html

Çevreyi haritalamak

Amacımız bir ayna etkisi göstermek, dolayısıyla yansıtacak bir şeye ihtiyacımız olacak. 3 boyutlu grafiklerde yansıtıcı yüzeyleri simüle etmek için küp haritaları yaygın olarak kullanılır. Örneğimizde, daha önce oluşturduğumuz küpü kullanarak bir tane oluşturabiliriz. Öncelikle ilgili yüzlere görseller atarız:


 frontFace.contents = UIImage(named: "front")?.cgImage backFace.contents = UIImage(named: "back")?.cgImage leftFace.contents = UIImage(named: "left")?.cgImage rightFace.contents = UIImage(named: "right")?.cgImage topFace.contents = UIImage(named: "up")?.cgImage bottomFace.contents = UIImage(named: "down")?.cgImage


Daha sonra, her yüz için isDoubleSided = true ayarını yapıyoruz ve küpün boyutunu cubeSize: CGFloat = 2000.0 değerine artırıyoruz. Bu aslında "kamerayı" küpün içine yerleştirir:


Küp haritası


Daha sonra, aynı anda birkaç küp oluşturacağımız için kurulum işlevlerini basitleştirelim:


 enum CubeFace: CaseIterable { case front case back case left case right case top case bottom func translationAndRotation(size: CGFloat) -> (translation: (x: CGFloat, y: CGFloat, z: CGFloat), rotation: (angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat)) { switch self { case .front: return ((0.0, 0.0, size * 0.5), (0.0, 0.0, 1.0, 0.0)) case .back: return ((0.0, 0.0, -size * 0.5), (-.pi, 0.0, 1.0, 0.0)) case .left: return ((-size * 0.5, 0.0, 0.0), (-.pi * 0.5, 0.0, 1.0, 0.0)) case .right: return ((size * 0.5, 0.0, 0.0), (.pi * 0.5, 0.0, 1.0, 0.0)) case .top: return ((0.0, -size * 0.5, 0.0), (.pi * 0.5, 1.0, 0.0, 0.0)) case .bottom: return ((0.0, size * 0.5, 0.0), (-.pi * 0.5, 1.0, 0.0, 0.0)) } } func texture() -> UIImage? { ... } func color() -> UIColor { ... } } func setupFace( layer: CALayer, size: CGFloat, baseTransform: CATransform3D, face: CubeFace, textured: Bool ) { layer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size, height: size)) layer.isDoubleSided = textured let (translation, rotation) = face.translationAndRotation(size: size) var transform = baseTransform transform = CATransform3DTranslate(transform, translation.x, translation.y, translation.z) transform = CATransform3DRotate(transform, rotation.angle, rotation.x, rotation.y, rotation.z) layer.transform = transform if textured { layer.contents = face.texture()?.cgImage } else { layer.backgroundColor = face.color().cgColor } } func setupCube( view: UIView, size: CGFloat, textured: Bool, baseTransform: CATransform3D, faces: [CubeFace] ) -> CATransformLayer { let cubeLayer = CATransformLayer() cubeLayer.position = CGPoint(x: view.bounds.midX, y: view.bounds.midY) for face in faces { let faceLayer = CALayer() setupFace(layer: faceLayer, size: size, baseTransform: baseTransform, face: face, textured: textured) cubeLayer.addSublayer(faceLayer) } return cubeLayer }


Şimdi hem küp haritasını hem de küçük bir küpü aynı anda oluşturalım:


 var baseTransform = CATransform3DIdentity baseTransform.m34 = -1.0 / 400.0 baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) view.layer.addSublayer(setupCube(view: view, size: 2000.0, textured: true, baseTransform: baseTransform)) view.layer.addSublayer(setupCube(view: view, size: 100.0, textured: false, baseTransform: baseTransform)) 


Aynı anda iki küp

Yansımalar

UIKit sağlam bir çerçevedir ancak karmaşık görsel efektler için yerleşik özelliklerden yoksundur. Ancak nesnelere isteğe bağlı maskeler uygulama yeteneği sunuyor ve ayna efektini yaratmak için tam olarak bundan yararlanacağız. Temel olarak, ortamı her biri karşılık gelen küp yüzüyle maskelenen altı kez oluşturacağız.


İşin zor yanı, bir CATransformLayer doğrudan maskeleyemememizdir. Ancak bu sınırlamayı CALayer konteynerinin içine yerleştirerek aşabiliriz:


 func setupReflectiveFace( view: UIView, size: CGFloat, baseTransform: CATransform3D, face: CubeFace ) -> CALayer { let maskLayer = CALayer() maskLayer.frame = view.bounds maskLayer.addSublayer(setupCube(view: view, size: size, textured: false, baseTransform: baseTransform, faces: [face])) let colorLayer = CALayer() colorLayer.frame = view.bounds colorLayer.mask = maskLayer colorLayer.addSublayer(setupCube(view: view, size: 2000.0, textured: true, baseTransform: baseTransform, faces: [.front, .back, .left, .right, .top, .bottom])) return colorLayer }


Ve şimdi viewDidLoad'umuz şöyle görünmeli:


 var baseTransform = CATransform3DIdentity baseTransform.m34 = -1.0 / 400.0 baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) for face in CubeFace.allCases { view.layer.addSublayer(setupReflectiveFace(view: view, size: 100.0, baseTransform: baseTransform, face: face)) } 


Şu ana kadar sadece bir maske


Bu görüntü zaten elde etmek istediğimiz şeye çok benziyor, ancak bu noktada küp yalnızca küp haritası üzerinde 3 boyutlu bir maskedir. Peki onu gerçek bir aynaya nasıl dönüştürebiliriz?

Ayna Boyutu

Dünyayı 3 boyutlu uzayda rastgele bir düzleme göre yansıtmanın basit bir yöntemi olduğu ortaya çıktı. Karmaşık matematiğe dalmadan aradığımız matris budur:


 func mirrorMatrix(planePoint: Vector4D, planeTransform: CATransform3D, planeNormal: Vector4D) -> CATransform3D { let pt = applyTransform(transform: planeTransform, point: planePoint) let normalTransform = CATransform3DInvert(planeTransform).transposed let normal = applyTransform(transform: normalTransform, point: planeNormal).normalized() let a = normal.x let b = normal.y let c = normal.z let d = -(a * pt.x + b * pt.y + c * pt.z) return CATransform3D([ 1 - 2 * a * a, -2 * a * b, -2 * a * c, -2 * a * d, -2 * a * b, 1 - 2 * b * b, -2 * b * c, -2 * b * d, -2 * a * c, -2 * b * c, 1 - 2 * c * c, -2 * c * d, 0.0, 0.0, 0.0, 1.0 ]).transposed }


Daha sonra aşağıdaki kodu küp kurulum fonksiyonuna dahil ediyoruz:


 func setupCube( view: UIView, size: CGFloat, textured: Bool, baseTransform: CATransform3D, faces: [CubeFace], mirrorFace: CubeFace? = nil ) -> CATransformLayer { ... if let mirrorFace { let mirrorPlane = mirrorFace.transform(size: size, baseTransform: baseTransform) let mirror = mirrorMatrix(planePoint: Vector4D(x: 0.0, y: 0.0, z: 0.0, w: 1.0), planeTransform: mirrorPlane, planeNormal: Vector4D(x: 0.0, y: 0.0, z: 1.0, w: 1.0)) cubeLayer.sublayerTransform = mirror } }


Ve sonunda, uğruna çabaladığımız parlak küpü görebiliyoruz:

Çok güzel değil mi?

Neden UIKit?

Elbette aynı etkiyi Metal veya SceneKit gibi Metal tabanlı bir çerçeveyle elde etmek daha kolay görünebilir. Ancak bunların kendi sınırları vardır. Büyük olan? Canlı UIKit görünümlerini Metal tarafından çizilen 3D içeriğe taşıyamazsınız.


Bu makalede incelediğimiz yöntem, her türlü içeriği 3D ortamda görüntülememize olanak tanıyor. Buna haritalar, videolar ve etkileşimli görünümler dahildir. Ayrıca, kullanmak isteyebileceğiniz tüm UIKit animasyonlarıyla sorunsuz bir şekilde harmanlanabilir.


Bu makalenin kaynak kodu ve bazı yardımcı işlevler https://github.com/petertechstories/uikit-mirrors adresinde bulunabilir.

Mutlu kodlama!