この世でいちばんわかりやすいSwiftUIのパス描画


この投稿は何?

SwiftUIフレームワーク上でパスを描画する方法を解説します。
下図のような「パイチャートなどを描画するために利用できる扇形」のパスを各コードを書いていきます。

実行環境

macOS 11.4
Xcode 12.5
Swift 5.4

ハンズオン

ここで実践するシェイプは「パスの始まりと終わり」が閉じるまで、見ることはできません。
円弧を描けると、プレビュー上で確認することができます。

Shapeプロトコル

パスを描画するにはShapeプロトコルに適合した構造体を定義します。
一般的なSwiftUIビューのようなViewプロトコルでないので注意してください。

Pie構造体にShapeプロトコルを採用する
import SwiftUI

struct Pie: Shape {
    func path(in rect: CGRect) -> Path {
        // draw and return Path
    }
}

型にShapeプロトコルを採用すると、path(in:)メソッドの実装が要求されます。
このメソッドのボディで、描画したいパスを実装します。

パスのインスタンスを作成する

描画される線はPath型のインスタンスです。
とりあえず、パスのインスタンスを作成して、そのまま返しておきます。

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

以降、作成したパスのインスタンスに対して、「どのような図形を描画するか」を指定していきます。

パスの始まりを決める

画面の中央となる点を取得します。
path(in:)メソッドが受け取るrectパラメータが、そのパスが描画される画面の情報を持っています。

path(in
struct Pie: Shape {
    func path(in rect: CGRect) -> Path {        
        let center = CGPoint(x: rect.midX, y: rect.midY)

        var path = Path()
        return path
    }
}

画面の中央点がわかったら、そこを「パスの開始位置」とします。

ペンを中心に移動する
struct Pie: Shape {
    func path(in rect: CGRect) -> Path {
        let center = CGPoint(x: rect.midX, y: rect.midY)

        var path = Path()
        path.move(to: center)
        return path
    }
}

move(to:)メソッドで、パスのインスタンスを移動します。

直線を引く

パイの円弧が「開始される点」まで、直線を引きましょう。
パイの半径は「画面幅の半分」とします。
また、円弧の開始点は「画面の中心からX軸方向に半径分だけ移動した地点」とします。

直線を引く
struct Pie: Shape {    
    func path(in rect: CGRect) -> Path {
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = rect.width / 2
        let startPoint = CGPoint(
            x: center.x + radius,
            y: center.y
        )

        var path = Path()
        path.move(to: center)
        path.addLine(to: startPoint)
        return path
    }
}

円弧を引く

addArc(center:radius:startAngle:endAngle:clockwise)メソッドを使って、円弧を引くことができます。
5つのパラメータにはそれぞれ、以下の値を指定します。

  • 円弧の中心点
  • 円弧の半径
  • 円弧の開始角
  • 円弧の終了角
  • 円弧の進む方向
Pie
import SwiftUI

struct Pie: Shape {
    let startAngle = Angle(degrees: 0)
    let endAngle   = Angle(degrees: 90)

    func path(in rect: CGRect) -> Path {
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = rect.width / 2
        let startPoint = CGPoint(
            x: center.x + radius,
            y: center.y
        )

        var path = Path()
        path.move(to: center)
        path.addLine(to: startPoint)
        path.addArc(center: center,
                    radius: radius,
                    startAngle: startAngle,
                    endAngle:   endAngle,
                    clockwise: false
        )
        return path
    }
}

円弧の向きを調整するには、パス全体の角度を変更します。

struct Pie: Shape {
    let startAngle = Angle(degrees: 0-90)
    let endAngle   = Angle(degrees: 90-90)

    func path(in rect: CGRect) -> Path {
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = rect.width / 2
        let startPoint = CGPoint(
            x: center.x + radius * CGFloat(cos(startAngle.radians)),
            y: center.y + radius * CGFloat(sin(startAngle.radians))
        )

        var path = Path()
        path.move(to: center)
        path.addLine(to: startPoint)
        path.addArc(center: center,
                    radius: radius,
                    startAngle: startAngle,
                    endAngle:   endAngle,
                    clockwise: false
        )
        return path
    }
}

三角関数については、こちらのnoteで説明しています。