Ty Typhon
Ty Typhon

Reputation: 73

Nested NavigationLinks with `isActive=true` are not displaying correctly

I've begun learning SwiftUI, but have had some issues when building up my NavigationView - I'm not sure if this is a bug, or if I'm misunderstanding how nested navigation links should work!

This is my current code:

import SwiftUI

struct ContentView: View {
  var body: some View {
    NavigationView {
      ViewOne()
    }
  }
}

struct ViewOne: View {
  @State var isActiveOne: Bool = true

  var body: some View {
    VStack {
      Text("View One")
      
      NavigationLink(
        destination: ViewTwo(),
        isActive: $isActiveOne,
        label: { EmptyView() }
      )
      
      Button(
        action: { self.isActiveOne = true },
        label: { Text("Set isActiveOne=true") }
      )
    }
  }
}

struct ViewTwo: View {
  @State var isActiveTwo: Bool = true

  var body: some View {
    VStack {
      Text("View Two")
      
      NavigationLink(
        destination: Text("Success!"),
        isActive: $isActiveTwo,
        label: { EmptyView() }
      )

      Button(
        action: { self.isActiveTwo = true },
        label: { Text("Set isActiveTwo=true") }
      )
    }
  }
}

struct ContentView_Preview: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

I expect that:

However, when I run this code on the iOS 14.5 simulator, the second NavigationLink is not automatically displayed (and I just see the ViewTwo Button text).

What I also find strange is that if I default isActiveTrue to false, and then press the button, it navigates correctly - something about the combination of the default "true" state + the nested navigation link seems to be causing the issue.

Any idea if this is a bug in SwiftUI? Or have I done something wrong with my setup here?

Any thoughts would be a huge help - I've been banging my head against the wall for a while now...

Preview Screenshot

Upvotes: 7

Views: 700

Answers (1)

Damiaan Dufaux
Damiaan Dufaux

Reputation: 4795

This looks like a bug, you can file it at https://feedbackassistant.apple.com

A temporary workaround would be to put your NavigationLinks inside a List. That seems to work on iOS 14.5 and 15 (beta 2)

Invisible List

If you don't want the List behaviour/look then you can put the list on the background and make it transparent (with the opacity modifier)

Here is an example of the invisible List workaround:

struct WorkaroundLink<Destination: View>: View {
    let destination: Destination
    let isActive: Binding<Bool>

    var body: some View {
        List {
            NavigationLink(
                destination: destination,
                isActive: isActive,
                label: { EmptyView() }
            )
        }.opacity(0.01)
    }
}

extension View {
    func workaroundLink<D: View>(to destination: D, isActive: Binding<Bool>) -> some View {
        background(WorkaroundLink(destination: destination, isActive: isActive))
    }
}

Rewriting your example with this construct becomes:

struct ContentView: View {
    var body: some View {
        NavigationView { ViewOne() }
    }
}

struct ViewOne: View {
    @State var isActiveOne = true

    var body: some View {
        VStack {
            Text("View One")
            Button("Set isActiveOne=true") { isActiveOne = true }
        }.workaroundLink(to: ViewTwo(), isActive: $isActiveOne)
    }
}

struct ViewTwo: View {
    @State var isActiveTwo = true
    var body: some View {
        VStack {
            Text("View Two")

            Button(
                action: { self.isActiveTwo = true },
                label: { Text("Set isActiveTwo=true") }
            )
        }.workaroundLink(to: Text("Success!"), isActive: $isActiveTwo)
    }
}

Upvotes: 5

Related Questions