例によるSwiftUI(SwiftUI by Example)12 Drawing

SwiftUIの組み込み形状
SwiftUI’s built-in shapes

SwiftUIには、一般的に使用される5つの組み込み形状(長方形、角丸長方形、円、楕円、カプセル)が用意されています。特に最後の3つは、提供するサイズに基づいて動作が微妙に異なりますが、1つの例ですべてのオプションを示すことができます。

//-----------------------------------
struct ContentView: View {
    var body: some View {
        ZStack {
            Rectangle()
                .fill(Color.black)
                .frame(width: 200, height: 200)

            RoundedRectangle(cornerRadius: 25, style: .continuous)
            //RoundedRectangle(cornerRadius: 25, style: .circular)

                .fill(Color.red)
                .frame(width: 200, height: 200)

            Capsule()
                .fill(Color.green)
                .frame(width: 100, height: 50)

            Ellipse()
                .fill(Color.blue)
                .frame(width: 100, height: 50)

            Circle()
                .fill(Color.white)
                .frame(width: 100, height: 50)
        }
    }
}
//-----------------------------------

カスタムパスを描画する方法
How to draw a custom path

//-----------------------------------
struct SpiroSquare: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        let rotations = 5
        let amount = .pi / CGFloat(rotations)
        let transform = CGAffineTransform(rotationAngle: amount)

        for _ in 0 ..< rotations {
            path = path.applying(transform)

            path.addRect(CGRect(x: -rect.width / 2, y: -rect.height / 2, width: rect.width, height: rect.height))
        }

        return path
    }
}
//-----------------------------------
//-----------------------------------
struct SpiroSquare: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        let rotations = 4
        let amount = .pi / CGFloat(rotations)
        let transform = CGAffineTransform(rotationAngle: amount)

        for _ in 0 ..< rotations {
            path = path.applying(transform)

            path.addRect(CGRect(x: 0, y: 0, width: rect.width, height: rect.height))
        }

        return path
    }
}
//-----------------------------------
struct ContentView: View {
    var body: some View {
        VStack{
            SpiroSquare()
                    .stroke()
                    .frame(width: 100, height: 50)
//            SpiroSquare()
//                        .stroke()
//                        .frame(width: 50, height: 50)
        }
    }
}
//-----------------------------------

struct ContentView: View {
var body: some View {
VStack{
HStack{
Spacer()
SpiroSquare()
.stroke()
.frame(width: 200, height: 200)
}
}
}
}
//———————————–

上について、パスの基準点がよく分からなかったので、以下試行。中央に描画された四角形が時計方向に回転する。

//-----------------------------------
struct SpiroSquare: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        let rotations = 4
        let amount = .pi / CGFloat(rotations)
        let transform = CGAffineTransform(rotationAngle: amount)

        for _ in 0 ..< rotations {
            path = path.applying(transform)

            path.addRect(CGRect(x: 0, y: 0, width: rect.width, height: rect.height))
        }

        return path
    }
}
//-----------------------------------
struct ContentView: View {
    var body: some View {
        VStack{
            SpiroSquare()
                    .stroke()
                    .frame(width: 100, height: 50)
//            SpiroSquare()
//                        .stroke()
//                        .frame(width: 50, height: 50)
        }
    }
}
//-----------------------------------

関連リンク
SwiftUIでカスタムパスを作成する

ポリゴンと星の描き方
How to draw polygons and stars

SwiftUIの基本的なパス描画システムを理解すると、あらゆる種類の形状を簡単に追加できます。たとえばStar、ほんの少しの数学で、さまざまな星の形や他のポリゴンを表現できる形を作成できます。

//-----------------------------------
struct Star: Shape {
    // store how many corners the star has, and how smooth/pointed it is
    let corners: Int
    let smoothness: CGFloat

    func path(in rect: CGRect) -> Path {
        // ensure we have at least two corners, otherwise send back an empty path
        guard corners >= 2 else { return Path() }

        // draw from the center of our rectangle
        let center = CGPoint(x: rect.width / 2, y: rect.height / 2)

        // start from directly upwards (as opposed to down or to the right)
        var currentAngle = -CGFloat.pi / 2

        // calculate how much we need to move with each star corner
        let angleAdjustment = .pi * 2 / CGFloat(corners * 2)

        // figure out how much we need to move X/Y for the inner points of the star
        let innerX = center.x * smoothness
        let innerY = center.y * smoothness

        // we're ready to start with our path now
        var path = Path()

        // move to our initial position
        path.move(to: CGPoint(x: center.x * cos(currentAngle), y: center.y * sin(currentAngle)))

        // track the lowest point we draw to, so we can center later
        var bottomEdge: CGFloat = 0

        // loop over all our points/inner points
        for corner in 0..<corners * 2  {
            // figure out the location of this point
            let sinAngle = sin(currentAngle)
            let cosAngle = cos(currentAngle)
            let bottom: CGFloat

            // if we're a multiple of 2 we are drawing the outer edge of the star
            if corner.isMultiple(of: 2) {
                // store this Y position
                bottom = center.y * sinAngle

                // …and add a line to there
                path.addLine(to: CGPoint(x: center.x * cosAngle, y: bottom))
            } else {
                // we're not a multiple of 2, which means we're drawing an inner point

                // store this Y position
                bottom = innerY * sinAngle

                // …and add a line to there
                path.addLine(to: CGPoint(x: innerX * cosAngle, y: bottom))
            }

            // if this new bottom point is our lowest, stash it away for later
            if bottom > bottomEdge {
                bottomEdge = bottom
            }

            // move on to the next corner
            currentAngle += angleAdjustment
        }

        // figure out how much unused space we have at the bottom of our drawing rectangle
        let unusedSpace = (rect.height / 2 - bottomEdge) / 2

        // create and apply a transform that moves our path down by that amount, centering the shape vertically
        let transform = CGAffineTransform(translationX: center.x, y: center.y + unusedSpace)
        return path.applying(transform)
    }
}
//-----------------------------------
struct ContentView: View {
    var body: some View {
        Star(corners: 5, smoothness: 0.2)
            //.fill(Color.red)
            .frame(width: 400, height: 400)
            //.background(Color.green)

    }
}
//-----------------------------------

チェッカーボードの描き方
How to draw a checkerboard

SwiftUIのパスは、連続した孤立した形状である必要はなく、代わりに複数の長方形、楕円などをすべて1つにまとめることができます

これを示す簡単な方法として、次のように、設定された数の行と列の一連の長方形を作成することにより、市松模様を作成する形状を作成できます。

//-----------------------------------
struct Checkerboard: Shape {
    let rows: Int
    let columns: Int

    func path(in rect: CGRect) -> Path {
        var path = Path()

        // figure out how big each row/column needs to be
        let rowSize = rect.height / CGFloat(rows)
        let columnSize = rect.width / CGFloat(columns)

        // loop over all rows and columns, making alternating squares colored
        for row in 0 ..< rows {
            for column in 0 ..< columns {
                //Swift5の新機能 isMultiple
                // ある数が別の数の倍数であるかどうかを確認
                if (row + column).isMultiple(of: 2) {
                    // this square should be colored; add a rectangle here
                    let startX = columnSize * CGFloat(column)
                    let startY = rowSize * CGFloat(row)

                    let rect = CGRect(x: startX, y: startY, width: columnSize, height: rowSize)
                    path.addRect(rect)
                }
            }
        }

        return path
    }
}
//-----------------------------------
struct ContentView: View {
    var body: some View {
        Checkerboard(rows: 16, columns: 16)
            .fill(Color.red)
            .frame(width: 200, height: 200)
    }
}
//-----------------------------------

SwiftUIでUIBezierPathとCGPathを使用する方法
How to use UIBezierPath and CGPath in SwiftUI


//-----------------------------------
struct ScaledBezier: Shape {
    let bezierPath: UIBezierPath

    func path(in rect: CGRect) -> Path {
        let path = Path(bezierPath.cgPath)

        // Figure out how much bigger we need to make our path in order for it to fill the available space without clipping.
        let multiplier = min(rect.width, rect.height)

        // Create an affine transform that uses the multiplier for both dimensions equally.
        let transform = CGAffineTransform(scaleX: multiplier, y: multiplier)

        // Apply that scale and send back the result.
        return path.applying(transform)
    }
}
//-----------------------------------
struct ContentView: View {
    
    var body: some View {
        ScaledBezier(bezierPath: .logo)
            .stroke(lineWidth: 2)
            .frame(width: 200, height: 200)
    }
}
//-----------------------------------
extension UIBezierPath {
    /// The Unwrap logo as a Bezier path.
    static var logo: UIBezierPath {
        let path = UIBezierPath()
        // 始点
        path.move(to: CGPoint(x: 0.534, y: 0.5816))
        // 次のto:が終点 + 二つのコントロールポイント
        path.addCurve(to: CGPoint(x: 0.1877, y: 0.088), controlPoint1: CGPoint(x: 0.534, y: 0.5816), controlPoint2: CGPoint(x: 0.2529, y: 0.4205))
        path.addCurve(to: CGPoint(x: 0.9728, y: 0.8259), controlPoint1: CGPoint(x: 0.4922, y: 0.4949), controlPoint2: CGPoint(x: 1.0968, y: 0.4148))
        path.addCurve(to: CGPoint(x: 0.0397, y: 0.5431), controlPoint1: CGPoint(x: 0.7118, y: 0.5248), controlPoint2: CGPoint(x: 0.3329, y: 0.7442))
        path.addCurve(to: CGPoint(x: 0.6211, y: 0.0279), controlPoint1: CGPoint(x: 0.508, y: 1.1956), controlPoint2: CGPoint(x: 1.3042, y: 0.5345))
        path.addCurve(to: CGPoint(x: 0.6904, y: 0.3615), controlPoint1: CGPoint(x: 0.7282, y: 0.2481), controlPoint2: CGPoint(x: 0.6904, y: 0.3615))
        return path
    }
}
//-----------------------------------

addCurveは、形状の右側に表示される曲線を作成するメソッドです。曲線は次のようなものです。

3つの引数が必要です。曲線の最終点(終点)と、線の曲率を実際に定義する2つの制御点です。

始点は1つ前の終点で、今回はposAが始点になります。

【Swift3】UIBezierPathで卵型を書こう【CAShapeLayer】
SwiftUIのビューサイズにあわせてPathを表示する
ベジエパスとシェイプレイヤーのビギナーズガイド