【SwiftUI】パスで図形を描画する方法

こんにちはコーヤです。

このページでは、パスを使用して図形を描画する方法を紹介します。

以下のバージョンで動作確認しています。

  • Xcode 13.4.1
  • Swift 5.6.1

パスの概要

パスとは画面上に自由に描画できる機能です。

画面をxy座標として、座標を指定しながら線を引いていきます。

前提知識としてプログラミングにおけるxy座標系を抑えておきましょう。数学のxy座標系とは異なり、プログラミングのxy座標系は原点が左上にあり、x軸は右、y軸は下に伸びています。

直線の描画方法

直線を描画するときは、直線の始点と終点の2点を指定します。

始点の指定方法

.move(to: CGPoint(x: 始点の座標, y: 始点の座標))

moveを使うことで始点を移動することができます。

終点の指定方法

.addLine(to: CGPoint(x: 終点の座標, y: 終点の座標))

addLineの引数で終点を指定します。

struct ContentView: View {
    var body: some View {
        ZStack {
            Path { path in
                path.move(to: CGPoint(x: 0, y: 0))
                path.addLine(to: CGPoint(x: 200, y: 200))
            }
            .stroke(Color.green, lineWidth: 5)
            .frame(width: 200, height: 200)
            Rectangle()
                .stroke(Color.black, lineWidth: 2)
                .frame(width: 200, height: 200)
        }
    }
}

連続しない直線を描画する場合

連続しない直線を描画する場合は途中でmoveを使って始点移動を行います。

struct ContentView: View {
    var body: some View {
        ZStack {
            Path { path in
                path.move(to: CGPoint(x: 0, y: 0))
                path.addLine(to: CGPoint(x: 200, y: 200))
                path.move(to: CGPoint(x: 0, y: 100))
                path.addLine(to: CGPoint(x: 100, y: 200))
            }
            .stroke(Color.green, lineWidth: 5)
            .frame(width: 200, height: 200)
            Rectangle()
                .stroke(Color.black, lineWidth: 2)
                .frame(width: 200, height: 200)
        }
    }
}

連続する直線を描画する場合

連続する直線を描画する場合はaddLineを連続して使えばOKです。前の直線の終点が次の直線の始点になります。

struct ContentView: View {
    var body: some View {
        ZStack {
            Path { path in
                path.move(to: CGPoint(x: 0, y: 0))
                path.addLine(to: CGPoint(x: 200, y: 200))
                path.addLine(to: CGPoint(x: 0, y: 100))
                path.addLine(to: CGPoint(x: 100, y: 200))
            }
            .stroke(Color.green, lineWidth: 5)
            .frame(width: 200, height: 200)
            Rectangle()
                .stroke(Color.black, lineWidth: 2)
                .frame(width: 200, height: 200)
        }
    }
}

他にもaddLinesを使用する方法があります。考え方は上記のaddLineと同じで、コードの書き方が若干異なるだけです。

.addLines([
    CGPoint(x: 座標, y: 座標),
    CGPoint(x: 座標, y: 座標),
    ...
    CGPoint(x: 座標, y: 座標)
])
struct ContentView: View {
    var body: some View {
        ZStack {
            Path { path in
                path.addLines([
                    CGPoint(x: 0, y: 0),
                    CGPoint(x: 200, y: 200),
                    CGPoint(x: 0, y: 100),
                    CGPoint(x: 100, y: 200)
                ])
            }
            .stroke(Color.green, lineWidth: 5)
            .frame(width: 200, height: 200)
            Rectangle()
                .stroke(Color.black, lineWidth: 2)
                .frame(width: 200, height: 200)
        }
    }
}

円弧の描画方法

.addArc(
    center: CGPoint(x: 中心の座標, y: 中心の座標),
    radius: 半径,
    startAngle: .degrees(始点の偏角),
    endAngle: .degrees(終点の偏角),
    clockwise: 時計回り反時計回り
)
struct ContentView: View {
    var body: some View {
        HStack {
            Spacer()
            ZStack {
                Path { path in
                    path.addArc(
                        center: CGPoint(x: 100, y: 100),
                        radius: 100,
                        startAngle: .degrees(0),
                        endAngle: .degrees(120),
                        clockwise: true
                    )
                }
                .stroke(Color.green, lineWidth: 5)
                .frame(width: 200, height: 200)
                Rectangle()
                    .stroke(Color.black, lineWidth: 2)
                    .frame(width: 200, height: 200)
            }
            Spacer()
            ZStack {
                Path { path in
                    path.addArc(
                        center: CGPoint(x: 100, y: 100),
                        radius: 100,
                        startAngle: .degrees(0),
                        endAngle: .degrees(120),
                        clockwise: false
                    )
                }
                .stroke(Color.green, lineWidth: 5)
                .frame(width: 200, height: 200)
                Rectangle()
                    .stroke(Color.black, lineWidth: 2)
                    .frame(width: 200, height: 200)
            }
            Spacer()
        }
    }
}

図形の描画方法

SwiftUIで用意されている図形をパスで描画することができます。

.addRect(CGRect(x: 中心の座標, y: 中心の座標, width: 横の長さ, height: 縦の長さ))
.addEllipse(in: CGRect(x: 中心の座標, y: 中心の座標, width: 横の長さ, height: 縦の長さ))
.addRoundedRect(in: CGRect(x: 中心の座標, y: 中心の座標, width: 横の長さ, height: 縦の長さ), cornerSize: CGSize(width: 角の横方向の半径, height: 角の縦方向の半径))
struct ContentView: View {
    var body: some View {
        HStack {
            Spacer()
            ZStack {
                Path { path in
                    path.addRect(CGRect(x: 50, y: 25, width: 100, height: 150))
                }
                .stroke(Color.green, lineWidth: 5)
                .frame(width: 200, height: 200)
                Rectangle()
                    .stroke(Color.black, lineWidth: 2)
                    .frame(width: 200, height: 200)
            }
            Spacer()
            ZStack {
                Path { path in
                    path.addEllipse(in: CGRect(x: 25, y: 50, width: 150, height: 100))
                }
                .stroke(Color.green, lineWidth: 5)
                .frame(width: 200, height: 200)
                Rectangle()
                    .stroke(Color.black, lineWidth: 2)
                    .frame(width: 200, height: 200)
            }
            Spacer()
            ZStack {
                Path { path in
                    path.addRoundedRect(in: CGRect(x: 50, y: 50, width: 100, height: 100), cornerSize: CGSize(width: 20, height: 40))
                }
                .stroke(Color.green, lineWidth: 5)
                .frame(width: 200, height: 200)
                Rectangle()
                    .stroke(Color.black, lineWidth: 2)
                    .frame(width: 200, height: 200)
            }
            Spacer()
        }
    }
}

閉曲線の注意点

始点と終点の座標を一致させても閉曲線にはなりません。閉曲線にするにはcloseSubpathを使います。

.closeSubpath()
struct ContentView: View {
    var body: some View {
        HStack {
            Spacer()
            ZStack {
                Path { path in
                    path.addLines([
                        CGPoint(x: 100, y: 0),
                        CGPoint(x: 0, y: 200),
                        CGPoint(x: 200, y: 200),
                        CGPoint(x: 100, y: 00)
                    ])
                }
                .stroke(Color.green, lineWidth: 30)
                .frame(width: 200, height: 200)
                Rectangle()
                    .stroke(Color.black, lineWidth: 2)
                    .frame(width: 200, height: 200)
            }
            Spacer()
            ZStack {
                Path { path in
                    path.addLines([
                        CGPoint(x: 100, y: 0),
                        CGPoint(x: 0, y: 200),
                        CGPoint(x: 200, y: 200),
                        CGPoint(x: 100, y: 00)
                    ])
                    path.closeSubpath()
                }
                .stroke(Color.green, lineWidth: 30)
                .frame(width: 200, height: 200)
                Rectangle()
                    .stroke(Color.black, lineWidth: 2)
                    .frame(width: 200, height: 200)
            }
            Spacer()
        }
    }
}

以上です。ご参考になれば幸いです。

コメント欄