(2023) NavigationStack and NavigationDestination, using NavigationPath Correctly

2k views Asked by At

I am trying to accomplish a relatively simple task. I want to navigate from a root view -> intermediate view that captures input -> final view that displays input -> back to root view.

The solution I came up with uses the new NavigationPath and NavigationStack constructs and is partially derived from this stack overflow post: A complex Navigation with NavigationPath in SwiftUI

I created an enum with my views (NAV_ITEMS), then created a path of type NAV_ITEMS, and pushed onto that path when I wanted to go to the next view. A downfall of this solution is that I manage the state variable, called newRoomName, from the root view and pass it down as a binding to both child views, which seems wrong.

import SwiftUI

enum NAV_VIEWS {
    case AddRoomName, AddRoomSuccess, addHomeName, addHomeNameSuccess
}

struct NavPathTestView: View {
    
    @State var presentedViews = [NAV_VIEWS]()
    @State var newRoomName: String = ""

        var body: some View {
            NavigationStack(path: $presentedViews) {
                NavigationLink(value: NAV_VIEWS.AddRoomName) {
                    HStack {
                        Text("Add Room")
                    }.padding()
                        .foregroundColor(.white)
                        .background(Color.red)
                        .cornerRadius(40)
                }

                .navigationDestination(for: NAV_VIEWS.self) { i in
                    switch i {
                    case .AddRoomName:
                            RoomView(presentedViews: $presentedViews, newRoomName: $newRoomName)
                    case .AddRoomSuccess:
                            RoomSuccessView1(presentedViews: $presentedViews, newRoomName: $newRoomName)
                    case .addHomeName:
                        Text("add Home Name")
                    case .addHomeNameSuccess:
                        Text("Add Home Success")
                    }
                }
                .navigationTitle("Root Page")
            }
        }
}

struct RoomView: View {
      
    @Binding var presentedViews : [NAV_VIEWS]
    @Binding var newRoomName: String
    
    var body: some View {
        
        VStack() {
            
            Text("Add Room Name")
            Spacer()
            TextField("My new Room", text: $newRoomName)
            .padding()
            .textInputAutocapitalization(.words)
            .cornerRadius(20)
            .onSubmit {
                print("NewRoomName: \(newRoomName)")
            }
            Spacer()
            Button (action: {
                presentedViews.append(NAV_VIEWS.AddRoomSuccess)
            })
            {
                HStack {
                    Text("Go to Success")
                }.padding()
                    .foregroundColor(.white)
                    .background(Color.red)
                    .cornerRadius(40)
            }
        }
        
    }
}

struct RoomSuccessView1: View {
    
    @Binding var presentedViews : [NAV_VIEWS]
    @Binding var newRoomName: String
    
    var body: some View {
        
        
        VStack {
            Text("Room Success Page")
            Spacer()
            Text("The passed newRoomName: \(newRoomName)")
            Spacer()
            Button (action: {
                presentedViews.removeLast(presentedViews.count)
            })
            {
                HStack {
                    Text("Back To Root")
                }.padding()
                    .foregroundColor(.white)
                    .background(Color.red)
                    .cornerRadius(40)
            }
        }
    }
}


struct NavPathTestView_Previews: PreviewProvider {
    static var previews: some View {
        NavPathTestView()
    }
}

Is there a better way to do this? Routing using navigationDestination on each child view caused path issues and animation issues with how screens came into the presentation.

For example, if I used a navigationDestination on each child view (child1 -> child2) then called RootView() from the child2 view to navigate back to root, it would return, but the next time that child1 was opened it would slide in from the left hand side of the screen, different than the normal slide in from the right.

1

There are 1 answers

1
Md. Yamin Mollah On

SwiftUI's NavigationStack and NavigationPath made the whole navigation much easier than before. You can use a NavigationPath like the following to handle any navigation within your whole app using only Data Type. Use navigationDestination(for: ) function to define different destination for different data types.

I faced a lot of problems first when I tried NavigationStack and NavigationPath on my own. The whole internet did not provide me a solid deep understanding about it. That's why I wrote an article on Medium with a sample project on GitHub about Multi-level nested navigation.

Read the article on Medium and run the project from GitHub

struct ContentView: View {
    @State var path: NavigationPath = .init()
    var body: some View {
        NavigationStack(path: $path.animation(.easeOut)) {
            VStack {
                Button(action: {
                    path.append(FoodItemListView.tag)
                }) {
                    MenuButton(label: "Yummy Foods ")
                }
            
                Button(action: {
                    path.append(ClothItemListView.tag)
                }) {
                    MenuButton(label: "Fashionable Cloths ️")
                }
            }.navigationTitle("SwiftUI Navigations")
            .padding(.horizontal, 20)
            .padding(.vertical, 15)
            .navigationDestination(for: String.self) { tag in
                if tag == FoodItemListView.tag {
                    FoodItemListView(path: $path)
                } else if tag == ClothItemListView.tag {
                    ClothItemListView(path: $path)
                }
            }.navigationDestination(for: FastFood.self) { foodItem in
                FoodItemDetailsView(foodItem: foodItem, path: $path)
            }.navigationDestination(for: Cloth.self) { clothItem in
                ClothItemDetailsView(clothItem: clothItem, path: $path)
            }
        }
    }
}