SwiftUI 手势

作者: 一粒咸瓜子 | 来源:发表于2022-01-13 15:32 被阅读0次

摘自《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

相关文章

网友评论

    本文标题:SwiftUI 手势

    本文链接:https://www.haomeiwen.com/subject/xabncrtx.html