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
I Have Real Misunderstanding With Mfmailcomposeviewcontroller in Swift (Ios8) in Simulator
How to Locate the Cgrect For a Substring of Text in a Uilabel
Allow Only Numbers For Uitextfield Input
How to Upload Images to a Server in iOS With Swift
Command Failed Due to Signal: Segmentation Fault: 11
Swift - How to Get the File Path Inside a Folder
Issue Detecting Button Cellforrowat
Uinavigationbar Custom Back Button Without Title
How to Detect If App Is Being Built For Device or Simulator in Swift
Enabling Auto Layout in iOS 6 While Remaining Backwards Compatible With iOS 5
How Does View Controller Containment Work in iOS 5
Enterprise App Update Distribution on iOS 8
Table Header Views in Storyboards
String Replacement in Objective-C