LoginSignup
2
3
お題は不問!Qiita Engineer Festa 2023で記事投稿!

SwiftUIにおけるジェスチャ制御:highPriorityGestureを使ってPickerの動作を保護する

Posted at

はじめに

SwiftUIを用いてUIを設計する際、特定の要素(ここではPicker)の上でジェスチャ(TapGesture)を適切に制御することは重要な課題となります。本記事では、特にhighPriorityGesture(_:)メソッドを使ってPickerの選択を妨げないようにする方法を解説します。

基本的なジェスチャ制御

まず、基本的なジェスチャ制御について簡単に紹介します。以下のコードを見てみましょう。

Rectangle()
    .fill(Color.red)
    .frame(width: 200, height: 200)
    .gesture(
        TapGesture()
            .onEnded { _ in print("Tap detected") }
    )

このコードでは、赤い四角形のRectangleビューが表示され、このビューをタップすると "Tap detected" がコンソールに出力されます。これはTapGestureがこのビューにアタッチされ、タップが終了したときにクロージャが実行されるからです。

ジェスチャの競合

しかし、1つのビューに複数のジェスチャを適用した場合、どのジェスチャが優先されるかが問題となります。これを解決するのがhighPriorityGesture(_:)メソッドです。

Rectangle()
    .fill(Color.red)
    .frame(width: 200, height: 200)
    .highPriorityGesture(
        DragGesture()
            .onChanged { _ in print("Drag detected") }
    )
    .gesture(
        TapGesture()
            .onEnded { _ in print("Tap detected") }
    )

この例では、DragGestureがhighPriorityGesture(:)で設定されていて、TapGestureが通常のgesture(:)で設定されています。これにより、ドラッグとタップが競合する場合にはドラッグが優先されます。

Pickerとジェスチャの制御

では、具体的な問題例として、Pickerを含むビュー全体にタップジェスチャが適用されていて、Pickerの選択を妨げてしまうケースを考えてみましょう。以下のようなコードがあったとします。

struct ContentView: View {
    @State private var viewState: Int = 0
    @State private var selectionValue:Int  = 0
    
    var body: some View {
        ZStack {
            if viewState == 0 {
                Color.red
            } else if viewState == 1 {
                Color.blue
            } else {
                Color.green
            }
            
            if (viewState == 0) {
                Picker("", selection: $selectionValue) {
                    Text("test1").tag(0)
                    Text("test2").tag(1)
                }
                .border(Color.white, width: 2)
            }
        }
        .edgesIgnoringSafeArea(.all)
        .onTapGesture {
            withAnimation {
                viewState = (viewState + 1) % 3
            }
        }
    }
}

全体のビューにタップジェスチャが適用されており、タップすると赤→青→緑という順番で背景色が変わっていきます。また、Pickerは背景色が赤の時のみ、表示されます。しかし、この実装ではPickerの選択がうまくいきません。なぜなら、Pickerを選択しようとするとビュー全体のタップジェスチャが発火し、結果として背景色が青になりPickerが閉じてしまうからです。

高優先度ジェスチャを利用する

これを解決するために、highPriorityGesture(_:)を用いてPicker上のタップジェスチャを無視させます。次のようにコードを変更します。

struct ContentView: View {
    @State private var viewState: Int = 0
    @State private var selectionValue:Int  = 0
    
    var body: some View {
        ZStack {
            if viewState == 0 {
                Color.red
            } else if viewState == 1 {
                Color.blue
            } else {
                Color.green
            }
            
            if (viewState == 0) {
                Picker("", selection: $selectionValue) {
                    Text("test1").tag(0)
                    Text("test2").tag(1)
                }
                .border(Color.white, width: 2)
                // highPriorityGestureを追加
                .highPriorityGesture(
                    TapGesture().onEnded { _ in
                        // do nothing
                    }
                )
            }
        }
        .edgesIgnoringSafeArea(.all)
        .onTapGesture {
            withAnimation {
                viewState = (viewState + 1) % 3
            }
        }
    }
}

このようにすると、Picker上でのタップは高優先度ジェスチャとして認識され、onEndedで何も行わないためPickerの選択は妨げられません。一方、Picker以外の部分でのタップは全体のビューに適用されているonTapGestureによって、次の背景色になります。

まとめ

この記事では、SwiftUIのhighPriorityGesture(_:)を使って、Pickerの選択を妨げないようにする方法について紹介しました。これは、特定のビューで特定のジェスチャを保護する際に非常に便利な手法です。ジェスチャの制御は、ユーザーインターフェースを設計する上で重要な考慮事項であり、SwiftUIはこのような繊細な制御を可能にします。

参考
https://developer.apple.com/documentation/swiftui/view/highprioritygesture(_:including:)

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3