# Deep nested navigation

**URL:** <https://forums.swift.org/t/deep-nested-navigation/36637>\
**Category:** Swift Composable Architecture\
**Created:** [May 19, 2020, 7:58am UTC](https://forums.swift.org/t/deep-nested-navigation/36637 "2020-05-19T07:58:09Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![grinder81](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/grinder81/32/4351_2.png) [@grinder81](https://forums.swift.org/u/grinder81)\
**Post date:** [May 19, 2020, 7:58am UTC](https://forums.swift.org/t/deep-nested-navigation/36637/1 "2020-05-19T07:58:09Z")

</div>

First of all thanks for all the details examples and explanations! I'm battling with nested navigation with a selection that uses programmatic navigation and hit a wall where programmatic navigation is a problem.

I created a gist [here](https://gist.github.com/grinder81/f4e37066b573b599c7f7dafa868733db) which shows pure SwiftUI example. If I start converting it using TCA then it will become a sort of [List-NavigateAndLoad](https://github.com/pointfreeco/swift-composable-architecture/blob/master/Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-Lists-NavigateAndLoad.swift) example with more nested navigation.

Here is the navigation tree:

- List view  
--- Item 1  
--- Item 2  
--- Item n  
--------(1. Navigation on each item) Another list or scrollview  
----------------- Item 1  
----------------- Item 2  
----------------- Item m  
-------------------------------(2. Navigation on each item)  
------------------------------------------- Some details view for 2nd level navigated item.

Problem: When you do 2nd level navigation then `set navigation` for top or number 1 navigation will call with `nil` and that's broke everything. Whenever nested navigation is triggering, upper-level navigation getting called but why? I would assume it's a navigation stack and until an item poped, the navigation shouldn't get called.

Please let me know if that's require more explanation but I'm sure I'm not the only one facing that issue.

Thanks

---

<div class="post-metadata">

**Author:** ![grinder81](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/grinder81/32/4351_2.png) [@grinder81](https://forums.swift.org/u/grinder81)\
**Post date:** [May 19, 2020, 3:29pm UTC](https://forums.swift.org/t/deep-nested-navigation/36637/2 "2020-05-19T15:29:12Z")

</div>

Here are some code snapshots:

```
struct TopListState: Equatable {
var rows: IdentifiedArrayOf<Row> = [
    .init(value: 1),
    .init(value: 2),
    .init(value: 3),
    .init(value: 4),
    .init(value: 5),
    .init(value: 6),
    .init(value: 7)
]

var selection: Identified<Row.ID, CategoriesState>? <--- For navigation

```

}

Then in my top list view:

```
    var body: some View {
    NavigationView {
        WithViewStore(self.store) { viewStore in
            List {
                ForEach(viewStore.rows) { row in
                    NavigationLink(
                        destination: IfLetStore(
                            self.store.scope(
                                state: \.selection?.value,
                                action: TopListAction.categories),
                            then: CategoriesView.init(store:)
                        ),
                        tag: row.id,
                        selection: viewStore.binding(
                            get: \.selection?.id,
                            send: TopListAction.setNavigation(selection:) <--- This will get call for nested navigation
                        )
                    ) {
                        Text("\(row.value)")
                    }
                }
            }.navigationBarTitle("Top List")
        }
    }
}

```

Here is a child list cell navigation link:

```
                NavigationLink( <---- When that navigation get pushed, the upper navigation get popped and 
                destination: CategoryListView(data: self.category.data),
                tag: self.category.id,
                selection: self.selection
            ) {
                Text("See All".uppercased())
                    .font(.headline)
            }

```

Here is a working version using [Playground](https://gist.github.com/grinder81/f4e37066b573b599c7f7dafa868733db)

---

<div class="post-metadata">

**Author:** ![stephencelis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/stephencelis/32/2675_2.png) [@stephencelis](https://forums.swift.org/u/stephencelis)\
**Post date:** [May 19, 2020, 4:07pm UTC](https://forums.swift.org/t/deep-nested-navigation/36637/3 "2020-05-19T16:07:35Z")

</div>

Hi @grinder81! You're encountering a SwiftUI bug that's been brought up a few times before. Including here: [How to manage ForEachStore with NavigationLink's binding? - #2 by stephencelis](https://forums.swift.org/t/how-to-manage-foreachstore-with-navigationlinks-binding/36459/2)

And on our issue tracker. A potential fix and basic explanation here: [Multiple levels of navigation · Issue #66 · pointfreeco/swift-composable-architecture · GitHub](https://github.com/pointfreeco/swift-composable-architecture/issues/66#issuecomment-626388775)

It's a bit difficult to see in running the vanilla SwiftUI code you posted above, but it has the exact same problem. In fact, we can debug by introducing a binding helper:

```swift
extension Binding {
  func debug(_ prefix: String) -> Binding {
    Binding(
      get: {
        print("\(prefix): getting \(self.wrappedValue)")
        return self.wrappedValue
    },
      set: {
        print("\(prefix): setting \(self.wrappedValue) to \($0)")
        self.wrappedValue = $0
    })
  }
}

```

And enhancing each binding:

```swift
// In `TopListView`:
.debug("TopListView")
...
// In `CategoryView`:
.debug("CategoryView")

```

Now when we run your playground the following will print when the top view first displays:

```nohighlight
TopListView: getting nil
TopListView: getting nil
TopListView: getting nil
TopListView: getting nil
TopListView: getting nil
TopListView: getting nil
TopListView: getting nil
TopListView: getting nil

```

If you drill down by tapping the second row, the following will print:

```nohighlight
TopListView: setting nil to Optional(399EC22A-F9A5-451D-8A4E-40F0B6F101BB)
TopListView: getting Optional(399EC22A-F9A5-451D-8A4E-40F0B6F101BB)
TopListView: getting Optional(399EC22A-F9A5-451D-8A4E-40F0B6F101BB)
TopListView: getting Optional(399EC22A-F9A5-451D-8A4E-40F0B6F101BB)
TopListView: getting Optional(399EC22A-F9A5-451D-8A4E-40F0B6F101BB)
TopListView: getting Optional(399EC22A-F9A5-451D-8A4E-40F0B6F101BB)
TopListView: getting Optional(399EC22A-F9A5-451D-8A4E-40F0B6F101BB)
TopListView: getting Optional(399EC22A-F9A5-451D-8A4E-40F0B6F101BB)
1
CategoryView: getting nil
CategoryView: getting nil
CategoryView: getting nil
CategoryView: getting nil
CategoryView: getting nil
CategoryView: getting nil
CategoryView: getting nil

```

And finally, if you drill down into a category:

```nohighlight
CategoryView: setting nil to Optional(6F4A197D-5D2D-4DF0-A489-92C0A4F3F879)
CategoryView: getting Optional(6F4A197D-5D2D-4DF0-A489-92C0A4F3F879)
TopListView: getting Optional(399EC22A-F9A5-451D-8A4E-40F0B6F101BB)
TopListView: setting Optional(399EC22A-F9A5-451D-8A4E-40F0B6F101BB) to nil
TopListView: getting nil
TopListView: getting nil
TopListView: getting nil
TopListView: getting nil
TopListView: getting nil
TopListView: getting nil
TopListView: getting nil

```

You'll see that `TopListView` _is_ indeed `nil`ing out in the vanilla SwiftUI code. The reason the SwiftUI example above appears to be working just fine is because `selected`/`selection` don't drive state in any way. In TCA, however, the bindings would send actions to the store that update state, causing the buggy behavior you were seeing. If you attempt to programmatically drive a non-TCA observable object you will see the same behavior.

So the "fix" is to add `.navigationViewStyle(StackNavigationViewStyle())` to your navigation view, which changes the binding behavior to _not_ `nil` out the parent view's selection. But if you wanna support both iPhone and iPad you'll probably only want to apply this view modifier to the iPhone.

We're hoping that this bug will be fixed next month, but it might be worth filing more feedback with Apple to help make it happen!

---

<div class="post-metadata">

**Author:** ![stephencelis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/stephencelis/32/2675_2.png) [@stephencelis](https://forums.swift.org/u/stephencelis)\
**Post date:** [May 19, 2020, 4:14pm UTC](https://forums.swift.org/t/deep-nested-navigation/36637/4 "2020-05-19T16:14:35Z")

</div>

Also worth noting that you can rewrite the above SwiftUI example and remove all the selection bindings and everything will still work just fine, since nothing is mutating them directly for programmatic navigation. And you can do the same in TCA when you don't need programmatic navigation: merely omit the selection bindings and let SwiftUI do its thing.

If you _do_ need programmatic navigation, the selection bindings are there for you, but you _will_ be dealing with this bug (and others).

---

<div class="post-metadata">

**Author:** ![grinder81](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/grinder81/32/4351_2.png) [@grinder81](https://forums.swift.org/u/grinder81)\
**Post date:** [May 19, 2020, 4:23pm UTC](https://forums.swift.org/t/deep-nested-navigation/36637/5 "2020-05-19T16:23:30Z")

</div>

Thanks a lot @stephencelis I know that's not TCA bug and it's SwiftUI issue but didn't know that issue brought up before. I also know I'm not the only one seeing it but is there any workaround which you gave here and thanks a lot for that.

To verify the issue is actually from SwiftUI, I wrote the whole SwiftUI from TCA version and got same problem.

Again, thanks for the help, that solve my problem. I'll try to find a way to report bug to apple.

---

<div class="post-metadata">

**Author:** ![stephencelis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/stephencelis/32/2675_2.png) [@stephencelis](https://forums.swift.org/u/stephencelis)\
**Post date:** [May 19, 2020, 4:26pm UTC](https://forums.swift.org/t/deep-nested-navigation/36637/6 "2020-05-19T16:26:29Z")

</div>

> [@grinder81](#):
>
> To verify the issue is actually from SwiftUI, I wrote the whole SwiftUI from TCA version and got same problem.

Ah okay, my bad for misunderstanding! Glad this fixes things for you.

One other bug you may need to be aware of and workaround is deep-linking. It's not possible to deep link more than one layer right now all at once. You must incrementally drill down each layer, which can be tricky in both vanilla SwiftUI _and_ TCA.

---

<div class="post-metadata">

**Author:** ![grinder81](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/grinder81/32/4351_2.png) [@grinder81](https://forums.swift.org/u/grinder81)\
**Post date:** [May 19, 2020, 4:45pm UTC](https://forums.swift.org/t/deep-nested-navigation/36637/7 "2020-05-19T16:45:16Z")

</div>

> One other bug you may need to be aware of and workaround is deep-linking. It's not possible to deep link more than one layer right now all at once. You must incrementally drill down each layer, which can be tricky in both vanilla SwiftUI _and_ TCA.

Thanks a lot for the heads up! Lots of issue need to be fixed from SwiftUI.

---

<div class="post-metadata">

**Author:** ![damirstuhec](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/damirstuhec/32/11974_2.png) [@damirstuhec](https://forums.swift.org/u/damirstuhec)\
**Post date:** [May 30, 2020, 8:47pm UTC](https://forums.swift.org/t/deep-nested-navigation/36637/8 "2020-05-30T20:47:55Z")

</div>

@stephencelis In my case, even when using `.navigationViewStyle(StackNavigationViewStyle())`, the programmatic navigation doesn't work more than one layer deep. Is that what you experienced in your testing as well?

I have two `List`s, both using `NavigationLink(destination:tag:selection:)`, in this composition: `List 1 -> List 2 -> Item Detail`.

I need to use programmatic navigation because I offer item deletion from both `List 2` and `Item Detail` levels, which should automatically dismiss the presented item by setting the `"selectedItem"` on the store to `nil`. The dismissal works on the first (`List 2`) level, but not on the `Item Detail` level.

I'm guessing this is again the SwiftUI issue, but I wanted to ask you anyway. Thanks!

---

<div class="post-metadata">

**Author:** ![stephencelis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/stephencelis/32/2675_2.png) [@stephencelis](https://forums.swift.org/u/stephencelis)\
**Post date:** [May 30, 2020, 11:17pm UTC](https://forums.swift.org/t/deep-nested-navigation/36637/9 "2020-05-30T23:17:12Z")

</div>

> [@damirstuhec](#):
>
> @stephencelis In my case, even when using `.navigationViewStyle(StackNavigationViewStyle())` , the programmatic navigation doesn't work more than one layer deep. Is that what you experienced in your testing as well?

Yup, unfortunately 🙁

> [@stephencelis](#):
>
> One other bug you may need to be aware of and workaround is deep-linking. It's not possible to deep link more than one layer right now all at once. You must incrementally drill down each layer, which can be tricky in both vanilla SwiftUI _and_ TCA.

Hopefully we'll get some fixes in the coming weeks, though!

---

<div class="post-metadata">

**Author:** ![damirstuhec](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/damirstuhec/32/11974_2.png) [@damirstuhec](https://forums.swift.org/u/damirstuhec)\
**Post date:** [May 31, 2020, 8:27am UTC](https://forums.swift.org/t/deep-nested-navigation/36637/10 "2020-05-31T08:27:48Z")

</div>

Thanks Stephen. Yeah, let's cross our fingers.

---

<div class="post-metadata">

**Author:** ![grinder81](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/grinder81/32/4351_2.png) [@grinder81](https://forums.swift.org/u/grinder81)\
**Post date:** [June 15, 2020, 5:07pm UTC](https://forums.swift.org/t/deep-nested-navigation/36637/11 "2020-06-15T17:07:29Z")

</div>

@stephencelis I found another SwiftUI bug related with that. Submitted a bug

```nohighlight

extension Binding {
  func debug(_ prefix: String) -> Binding {
    Binding(
      get: {
        print("\(prefix): getting \(self.wrappedValue)")
        return self.wrappedValue
    },
      set: {
        print("\(prefix): setting \(self.wrappedValue) to \($0)")
        self.wrappedValue = $0
    })
  }
}

struct AppView: View {
    var body: some View {
        TabView {
            NavigationView {
                ContentView()
            }
            .tag(1)
            .tabItem({ Text("One") })
            .navigationViewStyle(StackNavigationViewStyle())
            
            NavigationView {
                ContentView()
            }
            .tag(2)
            .tabItem({ Text("Two") })
            .navigationViewStyle(StackNavigationViewStyle())

        }
    }
}

struct ContentView: View {
    @State var isActive: Bool = false
    
    var body: some View {
        VStack {
            Text("Hello")
        }
        .navigationBarTitle(Text("Title"), displayMode: .inline)
        .navigationBarItems(
            trailing: NavigationLink(
                destination: NestedView(),
                isActive: self.$isActive.debug("isActive"),
                label: {
                    Text("Add")
                }
            )
        )
    }
}

struct NestedView: View {
    @State var isPresenting: Bool = false
    var body: some View {
        ScrollView {
            HStack {
                Button(action: {
                    self.isPresenting = true
                }){
                    Text("Button 1")
                }
                
                Button(action: {
                    self.isPresenting = true
                }){
                    Text("Button 2")
                }
            }
        }.sheet(isPresented: self.$isPresenting.debug("Presenting")) {
            Text("Presnting")
        }
    }
}

print("✅")

import PlaygroundSupport

PlaygroundPage.current.liveView = UIHostingController(rootView: AppView())

```

I hope they will really fix the Navigation properly

---

<div class="post-metadata">

**Author:** ![damirstuhec](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/damirstuhec/32/11974_2.png) [@damirstuhec](https://forums.swift.org/u/damirstuhec)\
**Post date:** [October 2, 2020, 7:31pm UTC](https://forums.swift.org/t/deep-nested-navigation/36637/12 "2020-10-02T19:31:02Z")

</div>

While `StackNavigationViewStyle` does solve a lot of navigation issues with SwiftUI, I'm experiencing that it also causes rows in the second-level `List` to remain highlighted after tapping the "Back" button.

Here's a sample code that reproduces it:

```swift
struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: ItemCollectionView()) {
                    Text("Navigate to level 2")
                }
            }
        }
        // BUG: This line causes the "Navigate to level 3" row to remain highlighted
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

struct ItemCollectionView: View {
    var body: some View {
        List {
            NavigationLink(destination: Text("Level 3")) {
                Text("Navigate to level 3")
            }
        }
    }
}

```

I know this is purely a SwiftUI bug, so I filled feedback, but I would still love to know if anyone has a workaround? Thanks!

---

<div class="post-metadata">

**Author:** ![ole-kr-stumpf](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ole-kr-stumpf/32/14361_2.png) [@ole-kr-stumpf](https://forums.swift.org/u/ole-kr-stumpf)\
**Post date:** [February 16, 2021, 3:52pm UTC](https://forums.swift.org/t/deep-nested-navigation/36637/13 "2021-02-16T15:52:02Z")

</div>

I solved this by moving the innermost NavigationLink out of the list and setting the label parameter to EmptyView()
