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 büyüleyici dünyasına cesur bir adım atıyoruz. 3D'nin 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 işletim sisteminin ekran görüntüsü oluşturmak için kullandığı gerçek bileşen olan nesnesine bir referans içerir. ekrandaki sunumunu etkileyen üç özelliği vardır: konum, sınırlar ve dönüşüm. İlk ikisi oldukça açıklayıcıdır, 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. UIView CALayer CALayer transform 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 fonksiyonunun gövdesinde küpün altı tarafını da birleştireceğiz: viewDidLoad 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: İ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: Daha iyi, değil mi? Perspektif etkisini yaratan m34'teki terimidir. Gerçek matematik için bkz. -1.0 / 400.0 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 ayarını yapıyoruz ve küpün boyutunu değerine artırıyoruz. Bu aslında "kamerayı" küpün içine yerleştirir: isDoubleSided = true cubeSize: CGFloat = 2000.0 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)) 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 doğrudan maskeleyemememizdir. Ancak bu sınırlamayı konteynerinin içine yerleştirerek aşabiliriz: CATransformLayer CALayer 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)) } 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 dalmadan aradığımız matris budur: matematiğe 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: 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 adresinde bulunabilir. https://github.com/petertechstories/uikit-mirrors Mutlu kodlama!