Open2
TabView + NavigationStack でタップ時にルートに戻るようにする
// MainTabView.swift
import SwiftUI
struct MainTabView: View {
@State private var selection = 0
@State var path = NavigationPath()
var body: some View {
// 一度この proxy を経由させないと onChange(of: selection, perform:) では同じタブのタップ時に検知できない
let proxySelection = Binding<Int> {
selection
} set: {
selection = $0
if selection == 0 {
path = NavigationPath() // path.removeLast(path.count) でもいいはず
}
}
TabView(selection: proxySelection) { // いずれかのタブがタップされると proxySelection の set 関数が呼ばれる
BookCollectionView(user: user)
.onAppear {
selection = 0
}
.tabItem {
Image(systemName: "character.book.closed")
Text("図鑑")
}
.tag(0)
}
.tint(.primary)
}
}
// BookView.swift
import SwiftUI
struct BookView: View {
@Binding private var path: NavigationPath
init(path: Binding<NavigationPath>) {
self._path = path // @Binding の場合、アンダースコアを先頭につけないといけないっぽい
}
var body: some View {
NavigationStack(path: $path) {
Button {
self.path.append("BookDetailView") // path は [String] でなく専用の型を作ったほうがよい
} label: {
Text("詳細ページへ")
}
.navigationDestination(for: String.self, destination: { _ in
BookDetailView()
})
}
}
}
もともとは.id(randomId)
を使ってタップのたびに UUID() で生成したIDをここに入れてViewを作り直す?方法が Stack Overflow で紹介されていたが、対象タブが NavigationStack を持っているとクラッシュしたり、子Viewへのプッシュ遷移が行われなくなるなどの挙動があったし、何よりタップの度に別 id として生成し直すのもモヤッとしたので。
タブの tag 名を enum で定義してもダメだったし、また参考文献にある onRecieve(publisher: $selection, action:)
としても駄目だった。.onTapGesture()
は独自で selection を変更する処理などを書いたりするようのカスタムメソッドっぽかったのでこれも止めた。