SwiftUI 手勢

摘自《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
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容