はじめに
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が閉じてしまうからです。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1923880%2Fd689d34b-6c1a-b909-f377-013fcdf79c91.gif?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=fc78d56fa4901a7b411dc742d7876d66)
高優先度ジェスチャを利用する
これを解決するために、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によって、次の背景色になります。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1923880%2Fca641efc-0a45-43f5-b15c-7db25a17009e.gif?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=75552bd52286b97ed5439dc3716fdae7)
まとめ
この記事では、SwiftUIのhighPriorityGesture(_:)を使って、Pickerの選択を妨げないようにする方法について紹介しました。これは、特定のビューで特定のジェスチャを保護する際に非常に便利な手法です。ジェスチャの制御は、ユーザーインターフェースを設計する上で重要な考慮事項であり、SwiftUIはこのような繊細な制御を可能にします。
参考
https://developer.apple.com/documentation/swiftui/view/highprioritygesture(_:including:)