iOSアプリを開発する上で、UIKitのUITableViewCellでタップしたUIViewによって遷移先を変えたいことは、よくあると思います。
同じことをSwiftUIの List
と NavigationView
で実現しようとした際に試したことを備忘録として残しておきます。
環境はXcode11.0 (11A420a)、iOS13.0です。
NavigationLink
を2つ指定した場合
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
Row(city: "Twentynine Palms", state: "California")
Row(city: "Port Alsworth", state: "Alaska")
Row(city: "Skagway", state: "Alaska")
}
}
}
}
struct Row: View {
var city: String
var state: String
var body: some View {
VStack(alignment: .leading, spacing: 4) {
NavigationLink(city, destination: Text(city))
NavigationLink(state, destination: Text(state))
}
}
}
Pushの遷移を行う場合は NavigationLink
使用するので、素直に実装すると、このようになるかと思います。
これをビルドすると以下のような挙動になりました。
1回のタップで遷移を2回おこなっていて、意図した挙動になっていません。(また表示自体も disclosureIndicator
が2つ表示されてしまっています)
Button
を2つ配置して NavigationLink.init(destination:isActive:label:)
を使用した場合
NavigationLink
のインターフェイスを調べているとNavigationLink.init(destination:isActive:label:)
というメソッドが定義されています。
ドキュメントには定義以上の情報がないですが、利用できそうです。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
Row(city: "Twentynine Palms", state: "California")
Row(city: "Port Alsworth", state: "Alaska")
Row(city: "Skagway", state: "Alaska")
}
}
}
}
struct Row: View {
var city: String
var state: String
@State var buttonDidTap = (false, "")
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Button(city) {
self.buttonDidTap = (true, self.city)
}
Button(state) {
self.buttonDidTap = (true, self.state)
}
NavigationLink(destination: Text(buttonDidTap.1), isActive: $buttonDidTap.0) {
EmptyView()
}
}
}
}
実装は上記のように各ボタンのアクションを NavigationLink
にバインドしています。
一見上手く遷移できているようですが、どのボタンをタップしても2番目にあるボタンの遷移先になってしまっています。
各ボタンのアクションにブレイクポイントを貼って、もう少し挙動を確認してみます。
ボタンを1つだけタップしているはずですが、各ボタンのアクションが実行されてしまっています。
そのため最後のボタンイベントが優先されてしまい、このような挙動になってしまっているようです。
onTapGesture(count:perform:)
を使用した場合
View.onTapGesture(count:perform:)
という、これまでに試した方法よりも抽象度の低いメソッドがあるようです。これを使用して試してみます。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
Row(city: "Twentynine Palms", state: "California")
Row(city: "Port Alsworth", state: "Alaska")
Row(city: "Skagway", state: "Alaska")
}
}
}
}
struct Row: View {
var city: String
var state: String
@State var buttonDidTap = (false, "")
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(city)
.onTapGesture {
self.buttonDidTap = (true, self.city)
}
Text(state)
.onTapGesture {
self.buttonDidTap = (true, self.state)
}
NavigationLink.init(destination: Text(buttonDidTap.1), isActive: $buttonDidTap.0) {
EmptyView()
}
}
}
}
上記が実行した際のキャプチャです。各 Text
をタップした際に別画面に遷移することができています。
最後に
上記のように、onTapGesture(count:perform:)
を使用した場合は実現できました。
他にも良い方法はありそうですね。もし知っている方がいれば教えてほしいです。
参考資料
https://qiita.com/noppefoxwolf/items/33a1695ed5daa3230f3b
https://developer.apple.com/documentation/swiftui/navigationlink/3364630-init