6

After installing Xcode 11 beta 5 this morning, I noticed that NavigationDestinationLink was deprecated in favor of NavigationLink.

Also, that's what Apple says about it in the release notes:

NavigationDestinationLink and DynamicNavigationDestinationLink are deprecated; their functionality is now included in NavigationLink. (50630794)

The way I use NavigationDestinationLink is to programmatically push a new view into the stack via self.link.presented?.value = true. That functionality doesn't seem to be present in NavigationLink.

Any idea anyone? I would rather not use NavigationDestinationLink anymore as it's deprecated...

Thank you!

UPDATE: Actually, the NavigationDestinationLink way does not work anymore so I guess we have no way of pushing programmatically anymore?

UPDATE 2:

NavigationLink(destination: CustomView(), isActive: $isActive) {
    return Text("")
}

This works but when you pass isActive to true, any state update will trigger this code and push over and over... Also, if you pass it back to false, it will pop the view. Not only updates, if you set isActive to true, it will push the view (good) and if we press the back button, it will go back then immediately push again since it's still true. Playing with onAppear was my hope but it's not called when going back to it... I'm not sure how we're supposed to use this.

Benjamin Clanet
  • 1,076
  • 3
  • 10
  • 17

5 Answers5

21

After spending some time with NavigationLink(destination:isActive), I am liking it a lot more than the old NavigationDestinationLink. The old view was a little confusing, while the new approach seems much more elegant. And once I figure out how to push without animations, it would make state restoration at application launch very easy.

There is one problem though, a big and ugly bug. :-(

enter image description here

Pushing a view programatically works fine, and popping it programatically does too. The problem starts when we use the BACK button in the pushed view which behaves oddly every other time. The first time the view is popped, the view pops and pushes again immediately. The second time around it works fine. Then the third time it starts all over again.

I have created a bug report (number here). I recommend you do the same and reference my number too, to help Apple group them together and get more attention to the problem.

I designed a workaround, that basically consists of replacing the default Back button, with our own:

class Model: ObservableObject {
    @Published var pushed = false
}

struct ContentView: View {
    @EnvironmentObject var model: Model

    var body: some View {
        NavigationView {
            VStack {
                Button("Push") {
                    // view pushed programmatically
                    self.model.pushed = true
                }

                NavigationLink(destination: DetailView(), isActive: $model.pushed) { EmptyView() }
            }
        }
    }
}

struct DetailView: View {
    @EnvironmentObject var model: Model

    var body: some View {
        Button("Bring me Back (programatically)") {
            // view popped programmatically
            self.model.pushed = false
        }
        // workaround
        .navigationBarBackButtonHidden(true) // not needed, but just in case
        .navigationBarItems(leading: MyBackButton(label: "Back!") {
            self.model.pushed = false
        })
    }
}

struct MyBackButton: View {
    let label: String
    let closure: () -> ()

    var body: some View {
        Button(action: { self.closure() }) {
            HStack {
                Image(systemName: "chevron.left")
                Text(label)
            }
        }
    }
}
kontiki
  • 37,663
  • 13
  • 111
  • 125
  • Yup, look I just wrote about that in my "Update 2" :( – Benjamin Clanet Jul 30 '19 at 14:57
  • This is really interesting, thanks for sharing. However, it still has a different behaviour. Previously the `DetailView` would only be created once. Now it gets created every time the `ContentView` updates. This causes some issues for me that the state of the detail view resets. I created a new question about my issue. I would really appreciate your help. https://stackoverflow.com/q/57289150/4583267 – Thomas Vos Jul 31 '19 at 11:02
  • *Note:* The `EmptyView()` still uses space. This is visible by the button being above the center line. I find this rather annoying. – philipp Oct 02 '19 at 21:40
  • 1
    @philipp The space is being occupied by NavigationLink, not EmptyView. In any case, you can always add `NavigationLink(...) {..}.frame(width: 0, height: 0)`. – kontiki Oct 03 '19 at 05:28
  • @kontiki great idea. I'm struggling with loading a new Navigation view from inside a UIKit collection view, That trick and using the NavigationLink(tag) should do the trick. – philipp Oct 05 '19 at 05:52
3

To improve the workaround without replacing the back button with a custom one, you can use the code above :

NavigationLink(destination: Test().onAppear(perform: {
    self.editPushed = nil
}), tag: 1, selection: self.$editPushed) {
    Button(action: {
        self.editPushed = 1
    }) {
        Image(systemName: "plus.app.fill")
            .font(.title)
    }
}

The onAppear block will erase the selection value preventing to display the detail view twice

CeDerache
  • 43
  • 8
1

You can also use NavigationLink(destination:tag:selection)

NavigationLink(destination: MyModal(), tag: 1, selection: $tag) {
    EmptyView()
}

So programmatically you can set tag to 1 in order to push MyModal. This approach has the same behaviour as the one with the Bool binding variable, so when you pop the first time it pushes the view immediately, hopefully they'll fix it in next beta.

The only downside I see with this approach, compared to DynamicNavigationDestinationLink is that you need to provide a View to NavigationLink, even if you don't need one. Hopefully they'll find a cleaner way to allow us to push programmatically.

0

The solution is to create custom back button for your detail view and pop detail view manually.

.navigationBarItems(leading:
                Button(action: {
                    self.showDetail = false
                }) {
                    Image(systemName: "chevron.left").foregroundColor(.red)
                        .font(.system(size: 24, weight: .semibold))
                    Text("Back").foregroundColor(.red)
                    .font(.system(size: 19))
                }
            )
Karen Karapetyan
  • 704
  • 1
  • 9
  • 18
0

The method used in the selected answer has been deprecated again. Here's the solution copied from this answer in this post.

    @State private var readyToNavigate : Bool = false

    var body: some View {
        NavigationStack {
           VStack {
              Button {
                  //Code here before changing the bool value
                  readyToNavigate = true
              } label: {
                  Text("Navigate Button")
              }
          }
           .navigationTitle("Navigation")
           .navigationDestination(isPresented: $readyToNavigate) {
              MyTargetView()
          }
       }
   }

bobbyg603
  • 3,536
  • 2
  • 19
  • 30
  • this should be working for me tho, no compile errors, NavStack is embedded in tabBar, no transition tho – Marlhex Jan 31 '23 at 19:41