SwiftUI Gestures 手勢操作教學

除了基本的點擊 (Tap),使用者的手指還可以做出拖曳、捏合縮放、旋轉等動作。SwiftUI 提供了強大的 Gesture API 來識別這些操作。

點擊手勢 (TapGesture)

雖然 Button 已經有點擊功能,但 onTapGesture 可以讓任何 View 變成可點擊。

Image("photo")
    .onTapGesture(count: 2) { // 雙擊
        print("圖片被雙擊了")
    }

長按手勢 (LongPressGesture)

Text("長按我")
    .onLongPressGesture(minimumDuration: 1.0) {
        print("已長按 1 秒")
    }

拖曳手勢 (DragGesture)

用於實作拖放 (Drag & Drop) 或滑動卡片效果。

@State private var offset = CGSize.zero
@State private var isDragging = false

Image("card")
    .offset(offset)
    .gesture(
        DragGesture()
            .onChanged { value in
                offset = value.translation // 跟隨手指移動
                isDragging = true
            }
            .onEnded { value in
                // 預測慣性停止點 (Tossing)
                // value.predictedEndTranslation 可以用來判斷滑動的力道
                withAnimation {
                    offset = .zero // 放開後彈回原位
                    isDragging = false
                }
            }
    )

縮放手勢 (MagnificationGesture)

用於捏合縮放圖片或地圖。

@State private var currentScale: CGFloat = 1.0
@State private var finalScale: CGFloat = 1.0

Image("map")
    .scaleEffect(finalScale * currentScale)
    .gesture(
        MagnificationGesture()
            .onChanged { value in
                currentScale = value // 手指正在捏合時的倍率變換
            }
            .onEnded { value in
                finalScale *= value // 結束後保存倍率
                currentScale = 1.0 // 重置當前倍率
            }
    )

旋轉手勢 (RotationGesture)

@State private var angle = Angle.zero

Image("compass")
    .rotationEffect(angle)
    .gesture(
        RotationGesture()
            .onChanged { value in
                angle = value
            }
    )

組合手勢

當一個 View 有多個手勢時,我們需要決定優先順序。

同時識別 (Simultaneous)

例如同時支援縮放和旋轉(像相簿瀏覽圖片)。

let zoom = MagnificationGesture().onChanged { ... }
let rotate = RotationGesture().onChanged { ... }

let combined = zoom.simultaneously(with: rotate)

Image("photo")
    .gesture(combined)

順序識別 (Sequenced)

例如:必須先長按,才能開始拖曳(避免誤觸)。

// 1. 定義狀態:是否處於長按準備拖曳的狀態
@State private var isDraggable = false
@State private var offset = CGSize.zero

// 2. 定義手勢
let longPress = LongPressGesture(minimumDuration: 0.5)
    .onEnded { _ in isDraggable = true }

let drag = DragGesture()
    .onChanged { value in 
        if isDraggable { offset = value.translation }
    }
    .onEnded { _ in 
        isDraggable = false
        offset = .zero 
    }

// 3. 組合:LongPress 之後接 Drag
let sequence = longPress.sequenced(before: drag)

Circle()
    .fill(isDraggable ? .green : .blue)
    .offset(offset)
    .gesture(sequence)

排他識別 (Exclusive)

exclusively(before:) 表示如果第一個手勢成功,就忽略第二個。

掌握手勢操作,你可以創造出更符合直覺且有趣的交互體驗。