摘自《SwiftUI和Combine編程》---《手勢和導航》
Gesture
SwiftUI 中已經有一系列預先定義好的手勢,比如處理點擊的 TapGesture,處理長按的 LongPressGesture,或者拖拽的 DragGesture 等,它們都遵守 Gesture 協(xié)議。
同時,作為 View modifier,SwiftUI 也預定義了像是 onTapGesture 這樣把添加手勢和處理手勢合并在一起的簡便方法。
Text("點擊按鈕").onTapGesture {
print("按鈕被點擊")
}
不過對于更一般的情況,我們需要明確地定義手勢,并使用 gesture(_:including:) 方法來將它添加到 View 上。
@GestureState
struct OverlaySheet<Content: View>: View {
private let isPresented: Binding<Bool>
private let makeContent: ()->Content
// 被標記為 @GestureState 的變量,除了具有和普通 @State 類似的行為外,還會在 panelDraggingGesture 手勢結束后被自動置回初始值 0
@GestureState private var translation = CGPoint.zero
init(isPresented: Binding<Bool>, @ViewBuilder content: @escaping ()->Content) {
self.isPresented = isPresented
self.makeContent = content
}
var body: some View {
VStack {
Spacer()
makeContent()
}
// 計算 offset 時設定了 max(0, translation.y),也就是忽略了手勢向上的情況
.offset(y: (isPresented.wrappedValue ? 0 : UIScreen.main.bounds.height) + max(0, translation.y))
.animation(.interpolatingSpring(stiffness: 70, damping: 12))
.edgesIgnoringSafeArea(.bottom)
.gesture(panelDraggingGesture)
}
var panelDraggingGesture: some Gesture {
// 使用 DragGesture 來監(jiān)聽用戶的拖拽手勢。除了使用這個不帶任何參數(shù)的初始化方法外,DragGesture 的 init 還支持設定最小偵測距離和要使用的座標系等參數(shù)。
DragGesture()
// updating 的尾隨閉包中第二個參數(shù) state 是一個標記為 inout 的待設定值。對這個值進行設置,SwiftUI 將可以通過 $translation 對 @GestureState 狀態(tài)進行更新。
.updating($translation) { current, state, _ in
// 將 current.translation的值保存在 state 中 以便在 onEnded等 狀態(tài)讀取
// state 也即 GestureState($translation)
state.y = current.translation.height
}
.onEnded { state in
// 在手勢結束時,判斷相對于原始位置,手勢是否下劃超過了 250 point。如果下劃距離足夠,則將 isPresented 的包裝值 (wrappedValue) 設為 false,這會關閉當前的彈出界面。
if state.translation.height > 250 {
self.isPresented.wrappedValue = false
}
}
}
}
extension View {
func overlaySheet<Content: View>(isPresented: Binding<Bool>, @ViewBuilder content: @escaping ()->Content) -> some View {
overlay(OverlaySheet(isPresented: isPresented, content: content))
}
}
@State + OnChanged
除了 @GestureState 外,你也可以使用普通的 @State 來暫存劃動距離。
除了 updating(_:body:) 以外,你也可以通過 onChanged 來設定新的手勢狀態(tài)。
打個比方,假如 translation 被聲明為 @State 的話,onChanged 里同步劃動手勢的部分會被寫為:
@State private var translation = CGPoint.zero
DragGesture().onChanged { state in
self.translation = CGPoint(x: 0, y: state.translation.height)
}
普通的 @State 和 @GestureState 最大的不同在于,當手勢結束時,@GestureState 的值會被隱式地置為初始值。當這個特性正是你所需要的時候,它可以簡化你的代碼,但是如果你的狀態(tài)值需要在手勢結束后依然保持不變,則應該使用 onChanged 的版本。
手勢組合
SwiftUI 提供了三種對手勢進行組合的方式:
- 代表手勢需要順次發(fā)生的 SequenceGesture
- 需要同時發(fā)生的 SimultaneousGesture
- 只能有一個發(fā)生的 ExclusiveGesture