
Fix Navigation Link and Navigation View in SwiftUI
SwiftUI 4 and the new NavigationStack in iOS 16 are an excellent solution for pushing views into the navigation hierarchy. However, if your app still needs to support iOS 15, you need to relay on NavigationView. To write a simple navigation hierarchy with couple of views check the following code block, make sure not to add multiple Navigation Views in the same view hierarchy, as this can cause weird glitches, even though it may compile and run.
struct FirstView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: SecondView()) {
Text("Go to Next")
}
}
.padding()
}
}
}
truct SecondView : View{
var body: some View{
ZStack{
VStack{
//use navigation link without navigation view
NavigationLink(destination: ThirdView()) {
Text("Next View")
}
}
}
}
}
struct ThirdView : View {
var body: some View{
ZStack{
VStack{
Text("Third View")
}
}
}
}

The NavigationView in SwiftUI provides default back button behavior, which makes it easy to navigate back to the previous view in the navigation hierarchy. However, if you want to use a custom back button, you can use the Environment
key path of the presentationMode
to dismiss the current view.
@Environment(\.presentationMode) var presentationMode
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text("Back to Home")
}
When you have a list of data with a large number of items and you use NavigationLink
to navigate to a second view that depends on it, there can be performance issues. Initializing the second view with a large number of items can take a significant amount of time and can lead to a slow and laggy user experience. This can be especially problematic on older devices or devices with limited resources.
List{
ForEach(0..<50){ j in
NavigationLink(destination: SecondView(selectedItem: j)) {
Text("Go to Next \(j)")
}
}
}
To mitigate this issue, you can use techniques such as lazy loading to load only the data that is needed for the current view. This can help reduce the amount of time and resources required to initialize the second view, and can result in a smoother and more responsive user experience.
Here's an example of how you could implement lazy Navigation Link with simple hack
@State var showSecondView : Bool = false
@State var nextItem : Int = 0
var body: some View {
NavigationView {
VStack {
List{
ForEach(0..<30) { i in
HStack {
ZStack{
Color.white
Text("Go to Next \(i)")
}
}.onTapGesture {
showNextView(item: i)
}
}
}
}
.padding()
.background(
//Instead of creating navigation link inside loop, use invisible Navigation Link with isActive Binding
NavigationLink(destination: SecondView(selectedItem: nextItem), isActive: $showSecondView, label: { EmptyView() })
)
}
}
private func showNextView(item : Int){
nextItem = item
showSecondView.toggle()
}
It's always a good practice to avoid deprecated APIs, as they may have reduced functionality or be removed in future releases. However, sometimes you may need to target older or legacy devices, and in these cases, you may need to find a workaround.
In the context of navigation in SwiftUI, if your app needs to support older versions of iOS, such as iOS 15, you may need to use the deprecated NavigationView
component. However, if you only need to support iOS 16 and above, it's recommended to use the new NavigationStack
, as it provides improved functionality and performance.