Swiftui Navigationlink Loads Destination View Immediately, Without Clicking

SwiftUI NavigationLink loads destination view immediately, without clicking

The best way I have found to combat this issue is by using a Lazy View.

struct NavigationLazyView: View {
let build: () -> Content
init(_ build: @autoclosure @escaping () -> Content) {
self.build = build
}
var body: Content {
build()
}
}

Then the NavigationLink would look like this. You would place the View you want to be displayed inside ()

NavigationLink(destination: NavigationLazyView(DetailView(data: DataModel))) { Text("Item") }

NavigationLink open destination without isActive enabled

I assume you just wanted to activate navigation programmatically, so you need active element like button or tappable view and hidden link.

Here is possible solution for your code

var body: some View {
NavigationView {

Text("LOGIN") // << activator !!
.background(Color.yellow)
.foregroundColor(.black)
.padding()
.onTapGesture(perform: {
// false, disabled to show the issue
loginSuccess = false
})
.background(
NavigationLink(destination: LoggedView(), isActive: $loginSuccess) {
EmptyView() // << hidden !!
})
}
}

How can I use lazy load for NavigationLink correctly?

You're working against a couple of the principals of SwiftUI just enough that things are breaking. With a couple of adjustments, you won't even need the lazy navigation link.

First, generally in SwiftUI, it's advisable to not use indices in ForEach -- it's fragile and can lead to crashes, and more importantly, the view doesn't know to update if an item changes, since it only compares the indexes (which, if the array stays the same size, never changes).

Generally, it's best to use Identifiable items in a ForEach.

This, for example, works fine:

struct Item : Identifiable {
var id = UUID()
var index: Int
var string : String?
}

struct ContentView: View {
private var indices: [Int] = [1, 2, 3, 4]

@State var items: [Item] = []

var body: some View {
NavigationView {
List(items) { item in
NavigationLink {
view(for: item)
} label: {
Text("\(item.index)")
}
}
.onAppear {
items = indices.map { Item(index: $0, string: "Index: \($0)")}
}
}
}

@ViewBuilder
func view(for item: Item) -> some View {
Text(item.string ?? "Empty")
}
}

I can't say absolutely definitively what's going on with your first example, and why the lazy navigation link doesn't fix it, but my theory is that view(for:) and strings are getting captured by the @autoclosure and therefore not reflecting their updated values by the time the link is actually built. This is a side effect of the list not actually updating when the @State variable is set, due to the aforementioned issue with List and ForEach using non-identifiable indices.


I'm assuming that your real situation is complex enough that there are good reasons to be doing mutations in the onAppear and storying the indices separately from the models, but just in case, to be clear and complete, the following would be an even simpler solution to the issue, if it really were a simple situation:

struct ContentView: View {
private var items: [Item] = [.init(index: 1, string: "Index 1"),.init(index: 2, string: "Index 2"),.init(index: 3, string: "Index 3"),.init(index: 4, string: "Index 4"),]

var body: some View {
NavigationView {
List(items) { item in
NavigationLink {
view(for: item)
} label: {
Text("\(item.index)")
}
}
}
}

@ViewBuilder
func view(for item: Item) -> some View {
Text(item.string ?? "Empty")
}
}

SwiftUI: How to make NavigationLink not go anywhere and stay on current view

Use Button instead and activate hidden NavigationLink programmatically if validated. Here is an example

struct DemoView: View {
@State private var isValid = false

var body: some View {
NavigationView {
Button("Validate") {
// some validate code here like
self.isValid = self.validate()
}
.background(
NavigationLink(destination: Text("Destination"), isActive: $isValid) { EmptyView() }
)
}
}

private func validate() -> Bool {
return true
}
}

SwiftUI Navigation. View pops immediately after being pushed. How to fix?

You might be running into the bug where views pop immediately if there's exactly two navigation links. Try inserting a NavigationLink with EmptyViews as shown as a temporary bandaid.

        NavigationLink(destination: EmptyView()) {
EmptyView()
}

You can find more info here:
https://forums.swift.org/t/14-5-beta3-navigationlink-unexpected-pop/45279

A generics problem whilst creating a lazy NavigationLink in SwiftUI

This is because destination is a closure:

let destination : () -> Content1

So you need to pass ViewB as a closure:

NavigationLazyLink(destination: { ViewB() }) {
Text("Click me")
}


Related Topics



Leave a reply



Submit