摘自《SwiftUI和Combine编程》---《手势和导航》
Gesture
SwiftUI 中已经有一系列预先定义好的手势,比如处理点击的 TapGesture,处理长按的 LongPressGesture,或者拖拽的 DragGesture 等,它们都遵守 Gesture 协议。
同时,作为 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 来监听用户的拖拽手势。除了使用这个不带任何参数的初始化方法外,DragGesture 的 init 还支持设定最小侦测距离和要使用的座标系等参数。
DragGesture()
// updating 的尾随闭包中第二个参数 state 是一个标记为 inout 的待设定值。对这个值进行设置,SwiftUI 将可以通过 $translation 对 @GestureState 状态进行更新。
.updating($translation) { current, state, _ in
// 将 current.translation的值保存在 state 中 以便在 onEnded等 状态读取
// 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 来设定新的手势状态。
打个比方,假如 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 的值会被隐式地置为初始值。当这个特性正是你所需要的时候,它可以简化你的代码,但是如果你的状态值需要在手势结束后依然保持不变,则应该使用 onChanged 的版本。
手势组合
SwiftUI 提供了三种对手势进行组合的方式:
- 代表手势需要顺次发生的 SequenceGesture
- 需要同时发生的 SimultaneousGesture
- 只能有一个发生的 ExclusiveGesture
网友评论