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
}
}
})
)
}
}
}