単純な形状やパスを超えて移動すると、SwiftUIの2つの便利な機能が組み合わさって、驚くほど少ない作業で美しいエフェクトを作成できます。1つ目はCGAffineTransformであり、パスまたはビューを回転、スケーリング、またはシアーする方法を記述します。もう1つは偶奇の塗りつぶしで、重なり合う形状のレンダリング方法を制御できます。
CGAffineTransformと偶奇の塗りつぶしを使用した形状の変換
Transforming shapes using CGAffineTransform and even-odd fills
import SwiftUI //------------------- struct Flower1: Shape { //How much to move this petal away from the center var petalOffset: Double = -20 // How wide to make each petal var petalWidth: Double = 100 func path(in rect: CGRect) -> Path { // オリジナルの楕円描画 let originalPetal = Path(ellipseIn: CGRect(x: CGFloat(petalOffset), y: 0, width: CGFloat(petalWidth), height: rect.width / 2)) // 位置 let rotation = CGAffineTransform(rotationAngle: 4) // 位置を元に回転 画面の幅と高さの半分 let position = rotation.concatenating(CGAffineTransform(translationX: rect.width / 2, y: rect.height / 2)) return originalPetal.applying(position) } } //------------------- struct Flower2: Shape { // How much to move this petal away from the center var petalOffset: Double = -20 // How wide to make each petal var petalWidth: Double = 100 func path(in rect: CGRect) -> Path { // The path that will hold all petals var path = Path() // Count from 0 up to pi * 2, moving up pi / 8 each time for number in stride(from: 0, to: CGFloat.pi * 2, by: CGFloat.pi / 8) { // rotate the petal by the current value of our loop let rotation = CGAffineTransform(rotationAngle: number) // move the petal to be at the center of our view let position = rotation.concatenating(CGAffineTransform(translationX: rect.width / 2, y: rect.height / 2)) // create a path for this petal using our properties plus a fixed Y and height let originalPetal = Path(ellipseIn: CGRect(x: CGFloat(petalOffset), y: 0, width: CGFloat(petalWidth), height: rect.width / 2)) // apply our rotation/position transformation to the petal let rotatedPetal = originalPetal.applying(position) // add it to our main path path.addPath(rotatedPetal) } // now send the main path back return path } } struct ContentView: View { //@State private var petalOffset = -20.0 @State private var petalOffset = -20.0 @State private var petalWidth = 100.0 var body: some View { VStack { //Flower1(petalOffset: petalOffset, petalWidth: petalWidth) // .stroke(Color.red, lineWidth: 1) //Flower2(petalOffset: petalOffset, petalWidth: petalWidth) // .stroke(Color.red, lineWidth: 1) // Flower2(petalOffset: petalOffset, petalWidth: petalWidth) //.fill(Color.red) Flower2(petalOffset: petalOffset, petalWidth: petalWidth) .fill(Color.red, style: FillStyle(eoFill: true)) Text("Offset") Slider(value: $petalOffset, in: -40...40) .padding([.horizontal, .bottom]) Text("Width") Slider(value: $petalWidth, in: 0...100) .padding(.horizontal) } } }
ImagePaintを使用したクリエイティブな境界線と塗りつぶし
Creative borders and fills using ImagePaint
struct ContentView: View { var body: some View { VStack{ Text("Hello World") .frame(width: 300, height: 200) .background(Color.red) Spacer() /* Text("Hello World") .frame(width: 300, height: 200) .background(Color.red) Spacer() Text("Hello World") .frame(width: 300, height: 200) .border(Color.red, width: 30) Spacer() */ Text("Hello World") .frame(width: 300, height: 200) //.background(Image("IMG_1301") .resizable()) //.border(ImagePaint(image: Image("IMG_1301"), scale: 0.1), width: 30) .border(ImagePaint(image: Image("IMG_1301"), sourceRect: CGRect(x: 0, y: 0.25, width: 1, height: 0.5), scale: 0.1), width: 30) Capsule() .strokeBorder(ImagePaint(image: Image("IMG_1301"), scale: 0.1), lineWidth: 20) .frame(width: 300, height: 200) } } }
drawingGroup()を使用して高性能なメタルレンダリングを有効にする
Enabling high-performance Metal rendering with drawingGroup()
SwiftUIは、デフォルトでレンダリングにCore Animationを使用する
import SwiftUI //----------------------------- struct ColorCyclingCircle: View { var amount = 0.0 var steps = 100 var body: some View { ZStack { ForEach(0..<steps) { value in Circle() .inset(by: CGFloat(value)) .strokeBorder(self.color(for: value, brightness: 1), lineWidth: 2) //.strokeBorder(Color.red) } } } //-------------- func color(for value: Int, brightness: Double) -> Color { var targetHue = Double(value) / Double(self.steps) + self.amount if targetHue > 1 { targetHue -= 1 } return Color(hue: targetHue, saturation: 1, brightness: brightness) } } //----------------------------- struct ContentView: View { @State private var colorCycle = 0.0 var body: some View { VStack { ColorCyclingCircle(amount: self.colorCycle) .frame(width: 300, height: 300) Slider(value: $colorCycle) } } } //-----------------------------
drawingGroup
これは、SwiftUIに、ビューのコンテンツを単一のレンダリングされた出力として画面に戻す前にオフスクリーンイメージにレンダリングする必要があることを通知します。これは大幅に高速です。舞台裏では、これは、非常に高速なグラフィックスのためにGPUを直接操作するためのAppleのフレームワークであるMetalを利用しています。
重要:drawingGroup()修飾子が知っていることが、あなたがそれらを打ったときにパフォーマンスの問題を解決する方法として、あなたの武器を保つために役立ちますが、あなたがすべきではないことが多いということ、それを使用しています。オフスクリーンレンダーパスを追加すると、単純な描画ではSwiftUIの速度が低下する可能性があるため、実際のパフォーマンスの問題が発生するまで待ってから、を呼び出そうとする必要がありますdrawingGroup()。
import SwiftUI //----------------------------- struct ColorCyclingCircle: View { var amount = 0.0 var steps = 100 var body: some View { ZStack { ForEach(0..<steps) { value in Circle() .inset(by: CGFloat(value)) //.strokeBorder(self.color(for: value, brightness: 1), lineWidth: 2) //.strokeBorder(Color.red) .strokeBorder(LinearGradient(gradient: Gradient(colors: [ self.color(for: value, brightness: 1), self.color(for: value, brightness: 0.5) ]), startPoint: .top, endPoint: .bottom), lineWidth: 2) } } .drawingGroup() } //-------------- func color(for value: Int, brightness: Double) -> Color { var targetHue = Double(value) / Double(self.steps) + self.amount if targetHue > 1 { targetHue -= 1 } return Color(hue: targetHue, saturation: 1, brightness: brightness) } } //----------------------------- struct ContentView: View { @State private var colorCycle = 0.0 var body: some View { VStack { ColorCyclingCircle(amount: self.colorCycle) .frame(width: 300, height: 300) Slider(value: $colorCycle) } } } //-----------------------------