I am trying to use SwiftUI's new NavigationStack/NavigationLink pattern but am running into an issue if the view I am navigating to has a conditional render (just a simple if statement) in it, it does shows the yellow triangle error icon and does not render the child view. If I remove this conditional statement, the view renders fine and the navigation works correctly.
I am able to reproduce this with a minimal example:
import SwiftUI
struct Thing: Identifiable, Hashable {
let id: String = UUID().uuidString
let name: String
var description: String = ""
}
struct ContentView: View {
let things: [Thing] = [
Thing(name: "Thing 1", description: "Something new"),
Thing(name: "Thing 2"),
Thing(name: "Thing 3", description: "Something blue")
]
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack {
ForEach(things, id: \.self) { thing in
NavigationLink(value: thing) {
Text(thing.name)
}
}
}
.navigationDestination(for: Thing.self) { thing in
DetailView(path: $path, thing: thing)
}
.padding()
}
}
}
struct DetailView: View {
@Binding var path: NavigationPath
let thing: Thing
var body: some View {
NavigationStack(path: $path) {
VStack {
Text(thing.id)
Text(thing.name)
if !thing.description.isEmpty {
Text(thing.description)
}
}
}
}
}
The issue seems to be with this block:
if !thing.description.isEmpty {
Text(thing.description)
}
If I remove this, the navigation works correctly. With this simple if statement, the navigation will not render the child view.
This is reproducible using Xcode 15.2 with the simulator running iOS 17.2
Behavior with conditional:
Without conditional:
What am I doing wrong? Is there a piece of this puzzle that I am missing or misusing?
You are using multiple NavigationStack
. Ideally, you should use the NavigationStack
on the parent/root view only. Try the following:
import SwiftUI
struct Thing: Identifiable, Hashable {
let id: String = UUID().uuidString
let name: String
var description: String = ""
}
struct ContentView: View {
let things: [Thing] = [
Thing(name: "Thing 1", description: "Something new"),
Thing(name: "Thing 2"),
Thing(name: "Thing 3", description: "Something blue")
]
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack {
ForEach(things, id: \.self) { thing in
NavigationLink(value: thing) {
Text(thing.name)
}
}
}
.navigationDestination(for: Thing.self) { thing in
DetailView(path: $path, thing: thing)
}
.padding()
}
}
}
struct DetailView: View {
@Binding var path: NavigationPath
let thing: Thing
var body: some View {
VStack {
Text(thing.id)
Text(thing.name)
if !thing.description.isEmpty {
Text(thing.description)
}
}
}
}
One more suggestion, which is out of scope of your question. But I see you are using NavigationPath
as @State var in your parent view. As your child views need the path so you need to pass the path
during view initialization each time.
I would suggest you to use EnvironmentObject
so that you don't have to pass the path
as argument each time. You can follow the link to know more.