🎨
【SwiftUI】ToolbarItem(placement: .principal)にNavigationLinkを配置する際の注意点
NavigationViewを使用している場合、ToolbarItem(placement: .principal)にNavigationLinkを配置すると画面遷移が発火しないケースがありました。
画面遷移しないパターン
以下のように、ToolbarItem(placement: .principal)内のNavigationLinkを
条件によって表示・非表示を切り替えられるようにすると、画面遷移が全く発火しなくなりました。
import SwiftUI
struct NavigationViewSampleView: View {
@State private var showItem = false
var body: some View {
NavigationView {
VStack {
Button(showItem ? "hide" : "show") {
showItem.toggle()
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
if showItem {
NavigationLink("principal") {
Text("principal tapped")
}
}
}
ToolbarItem(placement: .primaryAction) {
if showItem {
NavigationLink("menu") {
Text("menu tapped")
}
}
}
}
}
}
}
条件分岐がなければ正しく画面遷移されますし、.primaryActionの方は条件分岐があっても遷移するため、
特定条件下でのみ発生するバグかと思われます。
画面遷移するように修正する
ToolbarItem(placement: .principal)内でNavigationLinkを使わず、
isActiveで遷移を管理するNavigationLinkを使えばこの問題は発生しなくなりました。
import SwiftUI
struct NavigationViewSampleView: View {
@State private var showItem = false
@State private var principalTapped = false
var body: some View {
NavigationView {
VStack {
Button(showItem ? "hide" : "show") {
showItem.toggle()
}
}
.background {
NavigationLink(isActive: $principalTapped) {
Text("principal tapped")
} label: {
EmptyView()
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
if showItem {
Button("principal") {
principalTapped = true
}
}
}
ToolbarItem(placement: .primaryAction) {
if showItem {
NavigationLink("menu") {
Text("menu tapped")
}
}
}
}
}
}
}
また、NavigationStackを使っていれば、ToolbarItem(placement: .principal)内でNavigationLinkの表示制御をしていても問題なく画面遷移ができました。
import SwiftUI
struct NavigationStackSampleView: View {
@State private var showToolbar = false
var body: some View {
NavigationStack {
VStack {
Text("NavigationStackSampleView")
Button(showToolbar ? "hide" : "show") {
showToolbar.toggle()
}
}
.toolbar {
ToolbarItem(placement: .principal) {
if showToolbar {
NavigationLink("principal", value: "principal")
}
}
}
.navigationDestination(for: String.self) {
Text($0 + " tapped")
}
}
}
}
isActiveを用いたNavigationLinkは現在deprecatedになっているため、
iOS 16以降をターゲットとしたアプリであれば、素直にNavigationStackを使った方が良いと思います。
Discussion