Path で曲線を描く ― addCurve とベジェ曲線

直線と円弧だけでは、自然な曲線を表現するのは難しい。波打つリボン、滑らかに接続するカード UI の縁取り、有機的なブロブ形状。こうした表現にはベジェ曲線が不可欠だ。SwiftUI の Path には addQuadCurve と addCurve という 2 つの曲線描画メソッドが用意されている。

ベジェ曲線とは

ベジェ曲線は、始点・終点・制御点から定まる滑らかな曲線である。制御点の数によって次数が変わり、制御点 1 つの 2 次ベジェ曲線と、制御点 2 つの 3 次ベジェ曲線がある。

2 次ベジェ曲線(addQuadCurve)

制御点は 1 つ。曲線の「引っ張られる方向」を 1 箇所で指定する。シンプルな弧を描くのに適している。

3 次ベジェ曲線(addCurve)

制御点は 2 つ。始点側と終点側で別々に曲がり方を制御でき、S 字曲線のような複雑な形状も表現可能。

制御点は曲線が「通過する点」ではなく、「引き寄せられる方向」を示す点だ。磁石のように曲線を引っ張るイメージを持つと理解しやすい。

addQuadCurve:2 次ベジェ曲線

addQuadCurve は現在の位置から終点まで、1 つの制御点に引っ張られる曲線を描く。

Path { path in
    path.move(to: CGPoint(x: 10, y: 150))
    path.addQuadCurve(
        to: CGPoint(x: 250, y: 150),
        control: CGPoint(x: 130, y: 10)
    )
}
.stroke(.blue, lineWidth: 2)

始点 (10, 150) から終点 (250, 150) に向かって、制御点 (130, 10) に引っ張られた放物線のような曲線が描かれる。制御点を上に置けば上に膨らみ、下に置けば下に膨らむ。

制御点を始点と終点の中間から水平方向にずらすと、非対称な曲線になる。UI デザインでは、この非対称性を活かしてカードの下辺を緩やかに波打たせるような装飾に使われることが多い。

Path { path in
    path.move(to: CGPoint(x: 0, y: 100))
    path.addQuadCurve(
        to: CGPoint(x: 300, y: 100),
        control: CGPoint(x: 80, y: 20)
    )
}
.stroke(.orange, lineWidth: 2)

この例では制御点を左寄りに配置しているため、曲線のピークが左側に偏った形になる。

addCurve:3 次ベジェ曲線

addCurve は 2 つの制御点を持ち、より複雑な曲線を描ける。始点側の制御点が曲線の「出発方向」、終点側の制御点が「到着方向」を決定する。

Path { path in
    path.move(to: CGPoint(x: 10, y: 150))
    path.addCurve(
        to: CGPoint(x: 290, y: 150),
        control1: CGPoint(x: 80, y: 10),
        control2: CGPoint(x: 220, y: 290)
    )
}
.stroke(.purple, lineWidth: 2)

control1 が上方向、control2 が下方向を向いているため、S 字型の曲線が生成される。2 次ベジェでは不可能だったこの S 字形状が、3 次ベジェの最大の強みだ。

3 次ベジェ曲線は 2 つの制御点の位置関係で曲線の性質が大きく変わる。両方の制御点を同じ側に置けば U 字型に、反対側に置けば S 字型になるのが基本的な法則である。

制御点間の距離が大きいほど曲線の振れ幅が大きくなる。

制御点の直感的な理解

ベジェ曲線の制御点を理解するには、「曲線は始点から control1 の方向に向かって出発し、control2 の方向から終点に到着する」と考えるのがわかりやすい。

// 始点から右上に出発し、右下から終点に到着する曲線
Path { path in
    path.move(to: CGPoint(x: 20, y: 200))
    path.addCurve(
        to: CGPoint(x: 280, y: 200),
        control1: CGPoint(x: 20, y: 50),
        control2: CGPoint(x: 280, y: 350)
    )
}
.stroke(.teal, lineWidth: 2)

control1 を始点の真上に、control2 を終点の真下に配置すると、曲線は始点から上に向かって出発し、下からカーブして終点に到着する。この「出発方向」と「到着方向」の考え方を掴めば、制御点の位置決めが格段に楽になる。

曲線の連結

複数の曲線を滑らかに接続するには、前の曲線の終点における接線方向と、次の曲線の始点における接線方向を一致させる必要がある。具体的には、前のセグメントの control2 と次のセグメントの control1 を、接続点を挟んで一直線上に置く。

Path { path in
    path.move(to: CGPoint(x: 10, y: 100))

    // 第1セグメント
    path.addCurve(
        to: CGPoint(x: 150, y: 100),
        control1: CGPoint(x: 50, y: 10),
        control2: CGPoint(x: 110, y: 190)
    )

    // 第2セグメント(滑らかに接続)
    path.addCurve(
        to: CGPoint(x: 290, y: 100),
        control1: CGPoint(x: 190, y: 10),
        control2: CGPoint(x: 250, y: 100)
    )
}
.stroke(.red, lineWidth: 2)

第 1 セグメントの control2 は (110, 190) で接続点 (150, 100) の下にある。滑らかに接続するためには、第 2 セグメントの control1 を接続点の上に配置すればよい。(190, 10) がその役割を果たしており、接続点を挟んで下→上の方向が連続している。

この原理は、なめらかな波形やブロブ形状を作る際に欠かせない基礎知識となる。

addQuadCurve と addCurve の使い分け

どちらのメソッドを使うかは、描きたい曲線の複雑さで判断する。

addQuadCurve が適するケース

単純な弧やアーチ、ボタンの丸い縁取り、カードの緩やかな波打ちなど、1 方向にふくらむ曲線。制御点が 1 つなのでパラメータ調整が少なく、試行錯誤のコストが低い。

addCurve が適するケース

S 字カーブ、複雑な波形、ブロブ形状、滑らかに接続する複数セグメントの曲線。2 つの制御点で出発方向と到着方向を独立して制御できるため、表現力が格段に高い。

実践:波形の背景を作る

ベジェ曲線の応用例として、画面上部に配置する波形の背景を作ってみよう。

struct WaveBackground: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let w = rect.width
        let h = rect.height

        path.move(to: CGPoint(x: 0, y: h * 0.6))

        path.addCurve(
            to: CGPoint(x: w * 0.5, y: h * 0.5),
            control1: CGPoint(x: w * 0.15, y: h * 0.75),
            control2: CGPoint(x: w * 0.35, y: h * 0.4)
        )

        path.addCurve(
            to: CGPoint(x: w, y: h * 0.65),
            control1: CGPoint(x: w * 0.65, y: h * 0.6),
            control2: CGPoint(x: w * 0.85, y: h * 0.8)
        )

        path.addLine(to: CGPoint(x: w, y: 0))
        path.addLine(to: CGPoint(x: 0, y: 0))
        path.closeSubpath()

        return path
    }
}

このコードでは、2 つの addCurve を連結して波形の下辺を描き、右上→左上→始点と直線で閉じて塗りつぶし可能な領域を作っている。rect.width と rect.height を基準にした相対座標を使っているため、どんなサイズのビューに配置してもレスポンシブに動作する。

Shape プロトコルに準拠させることで、fill や stroke はもちろん、trim によるアニメーションも適用可能になる。ハードコードされた座標ではなく rect からの比率で計算するのが、再利用可能なカスタムシェイプを作るうえでの重要なポイントだ。

ベジェ曲線をマスターすれば、Path による描画の自由度は飛躍的に広がる。最初は制御点の位置決めに戸惑うかもしれないが、「出発方向と到着方向」の原則を意識しながら試行錯誤を重ねることで、直感的に扱えるようになるだろう。