はじめに結論
SwiftUIでListの上にButtonを置くと、タップイベントがListのほうで実行されてしまう。そのため、ButtonのスタイルをBorderlessButtonStyle
かPlainButtonStyle
にする必要がある。
(この記事はXcode 12.4でのSwiftUIでの話です)
本題
解決方法
ButtonにBorderlessButtonStyle
かPlainButtonStyle
のスタイルを設定する
//: A UIKit based Playground for presenting user interface
import UIKit
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
List {
HStack {
Text("Cell 1")
Spacer()
Button(action: {
print("B_1押したね")
}, label: {
Text("Border")
})
.buttonStyle(BorderlessButtonStyle())
Button(action: {
print("P_1押したね")
}, label: {
Text("Plain")
})
.buttonStyle(PlainButtonStyle())
}
}
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
これで解決。タップイベントはListではなくボタンで反応するようになった。
デザイン的なものはButtonでなく内部のTextに適用していけばいい。
蛇足: なぜButtonのスタイルで判断するのかを考える
- 予想
- AppleはListをUITableViewのように動作させたい場合をデフォルトと考えているのではないかと思う。
- 実験してなんとなくわかること
- SwiftUIはListの要素のモディファイアではonTapGestureでタップしたときにUITableViewのようにList自体を変更することはやりたくない
- SwiftUIのListのなかにButtonがあるときはそれが要素なのでUITableViewのように振る舞わうことを制御できる
- もしその挙動を変更したければButton側で変更できるようにしている
以下は実験
全てのスタイルを適用してみる
他にもDefaultButtonStyle
というのがあるのでそれも適用してみる。結果はListがタップできるようになってしまう。つまりこれがデフォルトか。
//: A UIKit based Playground for presenting user interface
import UIKit
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
List {
HStack {
Text("Cell 1")
Spacer()
Button(action: {
print("B_1押したね")
}, label: {
Text("Border")
})
.buttonStyle(BorderlessButtonStyle())
Button(action: {
print("P_1押したね")
}, label: {
Text("Plain")
})
.buttonStyle(PlainButtonStyle())
Button(action: {
print("D_1押したね")
}, label: {
Text("Default")
})
.buttonStyle(DefaultButtonStyle())
}
}
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
なぜButtonのスタイルで判断するのか
- Listには複数のSectionやView(Cell/Item)が必要なためListのスタイルで統一的に指定しづらい
- 1行目とか指定する形にしてしまえばできるとは思うがそれも複雑でどうかと思う
- 内部のViewの状態で判断しようとしている
- 1行目とか指定する形にしてしまえばできるとは思うがそれも複雑でどうかと思う
具体的にはListが2行になっている場合、1行目はCellにはタップできないが、2行目はCellにもタップできる。
//: A UIKit based Playground for presenting user interface
import UIKit
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
List {
HStack {
Text("Cell 1")
Spacer()
Button(action: {
print("B_1押したね")
}, label: {
Text("Border")
})
.buttonStyle(BorderlessButtonStyle())
Button(action: {
print("P_1押したね")
}, label: {
Text("Plain")
})
.buttonStyle(PlainButtonStyle())
}
HStack {
Text("Cell 2")
Spacer()
Button(action: {
print("D_2押したね")
}, label: {
Text("Default")
})
.buttonStyle(DefaultButtonStyle())
}
}
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
上記の予想はあくまで身勝手な予想なので、何かコメントあればコメント欄にお願いします。