79,483 Lesungen
79,483 Lesungen

Rauch und Spiegel in Pure UIKit

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

Zu lang; Lesen

UIKit ist ein leistungsstarkes Toolkit, das bei richtiger Verwendung erstaunliche visuelle Effekte erzeugen kann. In diesem Artikel werden wir uns eingehend mit UIKit befassen und eine Technik zum Erstellen einer spiegelähnlichen Reflexion vorstellen. Dieser Effekt kann Ihrer App ein optisch beeindruckendes und ansprechendes Aussehen verleihen, das normalerweise nur mit komplexen grafischen Tools erreichbar zu sein scheint.
featured image - Rauch und Spiegel in Pure UIKit
Peter J. HackerNoon profile picture
0-item
1-item

Als App-Entwickler sind wir nicht nur Programmierer – wir sind Schöpfer, Entwickler und manchmal auch Illusionisten. Die Kunst der App-Entwicklung geht über Code und Design hinaus. Manchmal geht es darum, ein Überraschungs- und Illusionselement zu schaffen, das die Aufmerksamkeit der Benutzer fesselt und ein immersives Erlebnis schafft. Dieses Mal verlassen wir unsere Komfortzone der 2D-Welt und wagen einen mutigen Sprung in die faszinierende Welt der 3D .


UIKit ist mehr als eine Reihe von Tools zum Erstellen von Benutzeroberflächen. Es handelt sich um ein leistungsstarkes Toolkit, das bei richtiger Anwendung erstaunliche visuelle Effekte erzeugen kann. In diesem Artikel werden wir uns eingehend mit UIKit befassen und eine Technik zum Erstellen einer spiegelähnlichen Reflexion vorstellen. Dieser Effekt kann Ihrer App ein visuell beeindruckendes und ansprechendes Aussehen verleihen, das normalerweise nur mit komplexen grafischen Tools erreichbar zu sein scheint, obwohl sie nur mit Code erstellt wurde.

Das Endergebnis

Schauen Sie sich diesen wunderschönen, glänzenden Würfel an. Es wird nie rosten, da kein Metall verwendet wird.


Jetzt lernen wir, wie man es mit Code erstellt.

Zunächst einige Grundlagen

Für unsere Zwecke dient UIKit als schlanke Schicht auf Quartz Core und bietet uns freien Zugriff auf seine 3D-Funktionen. Ein UIView enthält einen Verweis auf ein CALayer Objekt, das die eigentliche Komponente ist, die das Betriebssystem für das Rendern auf dem Bildschirm verwendet. Es gibt drei Eigenschaften von CALayer , die seine Bildschirmdarstellung beeinflussen: Position, Grenzen und Transformation. Die ersten beiden sind ziemlich selbsterklärend, während transform mit jeder beliebigen 4x4-Matrix initialisiert werden kann. Wenn mehrere 3D-Ebenen gleichzeitig dargestellt werden müssen, müssen wir einen speziellen CATransformLayer verwenden, der den 3D-Raum seiner untergeordneten Ebenen auf einzigartige Weise beibehält, anstatt sie auf eine 2D-Ebene abzuflachen.

Ein Würfel

Beginnen wir mit dem Zeichnen eines einfachen Würfels. Zuerst erstellen wir eine Hilfsfunktion, um die Position jeder Seite anzupassen:


 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 }


Als nächstes werden wir im Hauptteil viewDidLoad Funktion unseres ViewControllers alle sechs Seiten des Würfels zusammensetzen:


 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)


So sieht dieser Code in Aktion aus:

Ein orthogonal projizierter Würfel


Es ist unbestreitbar 3D, aber irgendetwas fühlt sich komisch an, nicht wahr? Das Konzept der 3D-Perspektive in der Kunst wurde erstmals im 15. Jahrhundert von italienischen Renaissance-Malern beherrscht. Glücklicherweise können wir einen ähnlichen Effekt erzielen, indem wir einfach eine perspektivische Projektionsmatrix verwenden:


 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)


Schauen wir uns nun das Ergebnis an:

Dieser Würfel hat Perspektive


Besser, nicht wahr? Der Term -1.0 / 400.0 bei m34 erzeugt den Perspektiveffekt. Die eigentliche Mathematik finden Sie unter https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/building-basic-perspective-projection-matrix.html

Kartierung der Umgebung

Unser Ziel ist es, einen Spiegeleffekt zu demonstrieren, also brauchen wir etwas zum Reflektieren. In 3D-Grafiken werden Würfelkarten häufig zur Simulation reflektierender Oberflächen verwendet. In unserem Beispiel können wir einen erstellen, indem wir den tatsächlichen Würfel verwenden, den wir zuvor erstellt haben. Zuerst ordnen wir Bilder den entsprechenden Gesichtern zu:


 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


Als nächstes setzen wir für jede Fläche isDoubleSided = true und erhöhen die Größe des Würfels auf cubeSize: CGFloat = 2000.0 . Dadurch wird im Wesentlichen die „Kamera“ im Würfel platziert:


Die Würfelkarte


Da wir mehrere Cubes gleichzeitig erstellen werden, vereinfachen wir als Nächstes die Einrichtungsfunktionen:


 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 }


Lassen Sie uns nun sowohl die Würfelkarte als auch einen kleinen Würfel gleichzeitig rendern:


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


Zwei Würfel auf einmal

Reflexionen

UIKit ist ein robustes Framework, es fehlen jedoch integrierte Funktionen für komplexe visuelle Effekte. Allerdings bietet es die Möglichkeit, beliebige Masken auf Objekte anzuwenden, und genau das werden wir ausnutzen, um den Spiegeleffekt zu erzeugen. Im Wesentlichen rendern wir die Umgebung sechsmal, jeweils maskiert durch die entsprechende Würfelfläche.


Der schwierige Aspekt besteht darin, dass wir einen CATransformLayer nicht direkt maskieren können. Wir können diese Einschränkung jedoch umgehen, indem wir es in einem CALayer Container verschachteln:


 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 }


Und jetzt sollte unser viewDidLoad so aussehen:


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


Bisher nur eine Maske


Dieses Bild ähnelt bereits stark dem, was wir erreichen wollten, aber zu diesem Zeitpunkt ist der Würfel lediglich eine 3D-ähnliche Maske über der Würfelkarte. Wie verwandeln wir es also in einen echten Spiegel?

Die Spiegeldimension

Es stellt sich heraus, dass es eine einfache Methode gibt, die Welt relativ zu einer beliebigen Ebene im 3D-Raum zu spiegeln. Ohne in komplexe Mathematik einzutauchen, ist dies die Matrix, die wir suchen:


 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 }


Als nächstes integrieren wir den folgenden Code in die Cube-Setup-Funktion:


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


Und endlich können wir den glänzenden Würfel erblicken, nach dem wir gestrebt haben:

Ist das nicht schön?

Warum UIKit?

Sicherlich scheint es mit Metal oder einem Metal-basierten Framework wie SceneKit einfacher zu sein, den gleichen Effekt zu erzielen. Aber diese haben ihre eigenen Grenzen. Der Grosse? Sie können keine Live-UIKit-Ansichten in den von Metal gezeichneten 3D-Inhalt integrieren.


Mit der Methode, die wir in diesem Artikel betrachtet haben, können wir alle Arten von Inhalten in einer 3D-Umgebung anzeigen. Dazu gehören Karten, Videos und interaktive Ansichten. Außerdem lässt es sich problemlos mit allen UIKit-Animationen kombinieren, die Sie verwenden möchten.


Der Quellcode für diesen Artikel sowie einige Hilfsfunktionen finden Sie unter https://github.com/petertechstories/uikit-mirrors

Viel Spaß beim Codieren!



Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks