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