はじめに
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:)