SwiftUI Shape 形狀與 Path 繪圖教學

SwiftUI 內建了許多基本形狀,如 Rectangle, Circle, Capsule 等。如果你需要更複雜的圖形或圖表,可以使用 Path 來自行繪製,甚至加上漸層與動畫。

1. 內建形狀 (Built-in Shapes)

形狀本身就是 View,可以套用 fill (填充) 或 stroke (邊框) 修飾符。

VStack {
    // 1. 圓形
    Circle()
        .fill(.blue)
        .frame(width: 50, height: 50)
        
    // 2. 膠囊形 (常用於按鈕)
    Capsule()
        .fill(.green)
        .frame(width: 100, height: 40)
        
    // 3. 圓角矩形 (帶有漸層)
    RoundedRectangle(cornerRadius: 15)
        .fill(
            LinearGradient(
                colors: [.yellow, .orange],
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
        )
        .frame(width: 100, height: 100)
}

2. 邊框樣式 (StrokeStyle)

你可以透過 StrokeStyle 設定虛線、端點樣式等。

Circle()
    .stroke(
        .red,
        style: StrokeStyle(
            lineWidth: 5,
            lineCap: .round, // 線條端點圓滑
            dash: [10, 5]    // 虛線:畫 10 點,空 5 點
        )
    )
    .frame(width: 100)

3. Path (自由繪圖)

Path 類似於 Core Graphics 的操作,你可以移動畫筆、畫線、畫曲線。

Path { path in
    // 1. 移動起點
    path.move(to: CGPoint(x: 200, y: 100))
    
    // 2. 畫直線
    path.addLine(to: CGPoint(x: 100, y: 300))
    path.addLine(to: CGPoint(x: 300, y: 300))
    
    // 3. 閉合路徑 (自動連回起點)
    path.closeSubpath()
}
.fill(.purple.opacity(0.5))

貝茲曲線與圓弧 (Curve & Arc)

Path { path in
    path.move(to: CGPoint(x: 50, y: 100))
    
    // 畫一條二次貝茲曲線 (Quad Curve)
    path.addQuadCurve(
        to: CGPoint(x: 250, y: 100),
        control: CGPoint(x: 150, y: 0) // 控制點在上方,形成拱門
    )
}
.stroke(.black, lineWidth: 3)

4. 自定義形狀 (Shape Protocol)

如果你想要製作一個可響應尺寸 (Responsive) 的形狀,應該遵循 Shape 協議。這樣你的圖形就能根據 rect 的大小自動縮放。

範例:波浪形狀 (Wave)

struct Wave: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        
        // 起點:左上角
        path.move(to: CGPoint(x: 0, y: 0))
        
        // 畫波浪到右側
        path.addQuadCurve(
            to: CGPoint(x: rect.maxX, y: 0),
            control: CGPoint(x: rect.midX, y: rect.height) // 控制點在下方
        )
        
        // 連接底部,形成封閉區塊以便填充
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.height))
        path.addLine(to: CGPoint(x: 0, y: rect.height))
        path.closeSubpath()
        
        return path
    }
}

// 使用
Wave()
    .fill(.cyan)
    .frame(height: 100) // 高度 100,寬度自動填滿
    .ignoresSafeArea() // 常用於頂部背景

5. 圖形動畫 (Trim)

trim 修飾符可以只畫出路徑的一部份,配合動畫可以製作出「畫圓」或「進度條」的效果。

struct ProgressCircle: View {
    @State private var progress: CGFloat = 0.0
    
    var body: some View {
        ZStack {
            // 背景圓環
            Circle()
                .stroke(.gray.opacity(0.2), lineWidth: 10)
            
            // 進度圓環
            Circle()
                .trim(from: 0, to: progress) // 只畫出 0 到 progress 的部分
                .stroke(
                    .blue,
                    style: StrokeStyle(lineWidth: 10, lineCap: .round)
                )
                .rotationEffect(.degrees(-90)) // 從 12 點鐘方向開始
                .animation(.easeOut(duration: 1.0), value: progress)
        }
        .frame(width: 100, height: 100)
        .onAppear {
            progress = 0.8 // 動畫演示
        }
    }
}