【SwiftUI】Shapeプロトコルの使い方!カスタム図形の作り方

この記事からわかること
- SwiftUIのShapeのプロトコルとは?
- カスタム図形の作り方
- Swift UIの図形描画の仕組み
- path(in:)メソッドとは?
index
[open]
\ アプリをリリースしました /
環境
- Xcode:16.0
- iOS:18.0
- Swift:5.9
- macOS:Sonoma 14.6.1
Swift UIの図形描画の仕組み
Swift UIでは画面に図形を描画する方法がいくつか存在するように感じます。しかし実際に図形を描画している仕組みとして変わらないのはPathオブジェクト(構造体)が図形を描画するための情報を保持していることです。ここでの情報とは図形を表現するための座標などです。
Swift UIデフォルトで用意されている図形描画のためのRectangle
構造体やCircle
構造体など図形を生成する型は全てShapeプロトコルに準拠しています。
Shapeプロトコルとは?
Shape
プロトコルはビューを描画する際に必要になる形状情報を定義するためのプロトコルです。またその形状に対してサイズや色の変更、トリミングといった操作をするためのAPIも提供しています。
Shape
プロトコルの定義を見てみるとAnimatable
とView
に準拠していることがわかります。
public protocol Shape : Animatable, View {
func path(in rect: CGRect) -> Path
}
Animatable
プロトコルに準拠していることでアニメーションが適用されるようになっています。具体的には任意のプロパティの値が変化した際に実際のビューもアニメーションで変化します。
View
プロトコルに準拠していることでSwift UIにおいてビューを構成するために必要な機能を保持させています。
path(in:)メソッド
定義されているpath(in:)
メソッドはCGRect型で座標を受け取り、図形を描画するために必要となるPathオブジェクトを返すメソッドです。
Rectangle
構造体やCircle
構造体なども同様に内部的にpath(in:)
メソッドから該当の形を定義したPathオブジェクトを返しているようです。以下のような感じなのかな?(※公式ではないので違ったらごめんなさい)
Rectangle構造体
public struct Rectangle : Shape {
public func path(in rect: CGRect) -> Path {
var path = Path()
path.addRect(rect)
return path
}
}
Circle構造体
public struct Circle : Shape {
public func path(in rect: CGRect) -> Path {
var path = Path()
path.addEllipse(in: rect)
return path
}
}
その他のメソッド
extension Shape {
@inlinable public func fill<S>(_ content: S, style: FillStyle = FillStyle()) -> some View where S : ShapeStyle
@inlinable public func stroke<S>(_ content: S, style: StrokeStyle) -> some View where S : ShapeStyle
}
オリジナルのカスタム図形を作成する
Shape
プロトコルを使用してカスタムな図形を作成することも可能になります。では「三角形」を作成してみます。まずはShape
プロトコルに準拠させたTriangle
構造体を定義します。path(in:)
メソッドの中でPath
インスタンスを作成し、三角形を構築します。
public struct Triangle : Shape {
public func path(in rect: CGRect) -> Path {
var path = Path()
path.addLines([ // 三角形
CGPoint(x: 50, y: 50), // 始点1
CGPoint(x: 100, y: 100), // 終点1であり始点2
CGPoint(x: 100, y: 50), // 終点2であり始点3
CGPoint(x: 50, y: 50), // 終点3
])
return path
}
}
あとはSwift UIの中で呼び出すだけで三角形が実装できます。
struct TestNavigationView: View {
var body: some View {
VStack{
Triangle()
.fill(.cyan)
.frame(width: 100,height:100)
}
}
}

この要領でpathオブジェクトを構築すればさまざまな形を構築することができます。
扇型
struct FanShape : Shape {
public func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 200, y: 200))
path.addArc(
center: CGPoint(x: 200, y: 200),
radius: 100,
startAngle: Angle(degrees: 180),
endAngle: Angle(degrees: 90),
clockwise: true
)
return path
}
}

チェックマーク型
struct CheckMarkShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let startPoint = CGPoint(x: rect.width * 0.2, y: rect.height * 0.5)
let midPoint = CGPoint(x: rect.width * 0.4, y: rect.height * 0.7)
let endPoint = CGPoint(x: rect.width * 0.8, y: rect.height * 0.3)
path.move(to: startPoint)
path.addLine(to: midPoint)
path.addLine(to: endPoint)
return path
}
}

trim(from:,to:)
を使用することでチェックマークを描くような実装も可能です。

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。