The Util Designer
SwiftUI 學習Gesture - 拖曳手勢(Drag)
xcode 13.4.1, swift 5.5, iOS 15.4
2022-08-23
Gesture是使用者與App交互的主要途徑,包括點按(Tap)、長按手勢(Long Press)、拖曳手勢(Drag)、縮放手勢(MagnificationGesture)、旋轉手勢(RotationGesture),拖曳手勢(Drag)也是很常用的手勢,可以用來改變元件之間的順序等。
1. 下面就來看看如何使用拖曳手勢(Drag)很簡單的就實現拖動元作的效果。
import SwiftUI

struct GestureExample : View {
    @GestureState private var dragOffset = CGSize.zero
    var body: some View {
        VStack {
            Circle()
                .frame(width: 50)
                .foregroundColor(.black.opacity(0.8))
                .overlay(
                    Image(systemName: "trash")
                        .resizable()
                        .scaledToFit()
                        .foregroundColor(.green)
                        .scaleEffect(0.7)
                )
        }
        .offset(dragOffset)
        .gesture(
            DragGesture()
                .updating($dragOffset, body: { (currentState, state, transaction) in
                    state = currentState.translation
                })
        )
    }
}
拖動手勢使用DragGesture 的 updating 方法,在拖動整個期間會不斷的被呼叫,並且接收三個參數: currentState、state 與 transaction :
currentState 參數是手勢當前的狀態。這個值會依照手勢而有所不同,不過針對DragGesture,currentState會包含有當時位置,開始位置和移動速度等。
state 參數實際上是一個 in-out 參數,可以更新你在updating 方法的binding的參數: dragOffset 屬性的值。在以下的程式中,我們設定 state 的值為 currentState的當前位置 ,然後通過SwiftUI的Binding又把state的值更新dragOffset的值,從而用來控制View的offset位移狀態。
transaction 參數是gesture的上下文環境,暫時用不到。
2. 現時的程式雖然可以拖動,但拖動完畢後,元件又會回復到初步位置,為了記住當時最後的位置,就需增加一個State參數來記住上一次拖動手勢的位置,並與dragOffset的內容一起來更改View的offset位移狀態,達到拖動完畢後,把View放到最後拖動的位置,實作如下。
import SwiftUI

struct GestureExample : View {
    @GestureState private var dragOffset = CGSize.zero
    @State private var position = CGSize.zero
    
    var body: some View {
        VStack {
            Circle()
                .frame(width: 50)
                .foregroundColor(.black.opacity(0.8))
                .overlay(
                    Image(systemName: "trash")
                        .resizable()
                        .scaledToFit()
                        .foregroundColor(.green)
                        .scaleEffect(0.7)
                )
        }
        .offset(x: position.width + dragOffset.width, y: position.height + dragOffset.height)
        .gesture(
            DragGesture()
                .updating($dragOffset, body: { (currentState, state, transaction) in
                    state = currentState.translation
                })
                .onEnded({ value in
                    position.width += value.translation.width
                    position.height += value.translation.height
                })
        )
    }
}
3. 有時會在使用者拖動元作後,會再想把元件的位置做一些調整,比如只想元件停留在4個角落到,就可以再onEnded做一些處理,以下程式是在使用者拖動元件完畢後,再把元件以動畫效果移到4個角中最近的一個角落。
import SwiftUI

struct GestureExample : View {
    @GestureState private var dragOffset = CGSize.zero
    @State private var position = CGSize.zero
    
    var body: some View {
        GeometryReader { proxy in
            VStack {
                Circle()
                    .frame(width: 50)
                    .foregroundColor(.black.opacity(0.8))
                    .overlay(
                        Image(systemName: "trash")
                            .resizable()
                            .scaledToFit()
                            .foregroundColor(.green)
                            .scaleEffect(0.7)
                    )
            }
            .offset(x: position.width + dragOffset.width, y: position.height + dragOffset.height)
            .gesture(
                DragGesture()
                    .updating($dragOffset, body: { (currentState, state, transaction) in
                        state = currentState.translation
                    })
                    .onEnded({ value in
                        position.width += value.translation.width
                        position.height += value.translation.height
                        
                        withAnimation {
                            if position.width < proxy.size.width / 2 {
                                position.width = 0.0
                            } else {
                                position.width = proxy.size.width - 50
                            }
                            if (position.height < 0) {
                                position.height = -proxy.size.height/2 + 25
                            } else {
                                position.height = proxy.size.height/2 - 25
                            }
                        }
                    })
            )
        }
    }
}