Connecting the views using a NavigationView

Connecting the views ⛓

To let the user navigate from the ContentView to the DetailView, we have to wrap the List of our ContentView containing the different CategoryViews into a NavigationView.

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                //...
            }
        }
    }
}

When stacking views into a NavigationView they become part of a navigation hierarchy that allows the user to navigate between them.

To let the user navigate to the DetailView by tapping on a CategoryView, we have to wrap these into so-called NavigationLinks. Let’s start by wrapping the “pizza” CategoryView into such a NavigationLink. The destination of this NavigationLink is the DetailView. Because we are currently handling the “burger” CategoryView, we initialize the DetailView with the .burger case of our Categories enum.

NavigationLink(destination: DetailView(currentCategory: .burger)) {
    CategoryView(imageName: "burger", categoryName: "BURGER")
        .listRowSeparator(.hidden)
}

Let’s repeat this for the three remaining CategoryViews:

List {
    NavigationLink(destination: DetailView(currentCategory: .burger)) {
        CategoryView(imageName: "burger", categoryName: "BURGER")
        .listRowSeparator(.hidden)
    }
    NavigationLink(destination: DetailView(currentCategory: .pizza)) {
        CategoryView(imageName: "pizza", categoryName: "PIZZA")
        .listRowSeparator(.hidden)
    }
    NavigationLink(destination: DetailView(currentCategory: .pasta)) {
        CategoryView(imageName: "pasta", categoryName: "PASTA")
        .listRowSeparator(.hidden)
    }
    NavigationLink(destination: DetailView(currentCategory: .desserts)) {
        CategoryView(imageName: "desserts", categoryName: "DESSERTS")
        .listRowSeparator(.hidden)
    }
}

Let’s try out if everything works as expected by starting a Live preview. To do this, click on the play button above the Preview simulator.

Great! We can now tap on the different categories and a detailed list with all the products of the selected category is presented to us.

Let’s finish our app by adding navigation bars to the ContentView and the DetailView.

Adding navigation bars 👁

Currently, the navigation bar of ContentView is empty. To change this, add a .navigationTitle modifier to the List (not to the Navigation View!). Next, insert a Text with the String “Food Delivery“.

NavigationView {
    List {
        //...
    }
        .listStyle(.plain)
        .navigationTitle(Text("Food Delivery"))
}

We also want a navigation bar title for our DetailView. But this time, we want to display the name of the selected category instead of a static text. To do this, we need to write a function that returns an appropriate string to us depending on the passed currentCategory. Let’s insert this function into our Helper.swift file.

func categoryString (for category: Categories) -> String {
    switch category {
    case .pizza:
        return "Pizza"
    case .burger:
        return "Burger"
    case .pasta:
        return "Pasta"
    case .desserts:
        return "Desserts"
        
    }
}

Now, we can add a .navigationTitle modifier to the List of our DetailView and insert our created method as the Text’s String.

List(filterData(foodDataSet: foodData, by: currentCategory)) { food in
    DetailRow(food: food)
}
    .navigationTitle(Text(categoryString(for: currentCategory)))

To see what our DetailView will look like in a navigation hierarchy in the Preview simulator, we wrap the DetailView inside our DetailView_Previews struct into a NavigationView.

struct DetailView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            DetailView(currentCategory: .burger)
        }
    }
}

Your DetailView preview should now look like this.

One last thing: Let’s take a look at our ContentView again. Do you spot the disclosure indicators next to each CategoryView? Disclosure indicators get automatically created when using a NavgationLink inside a List. To get rid of these, we use a little workaround. Replace every CategoryView instance embedded in the respective NavigationLink with an “EmptyView” instance and make the NavigationLink invisible by using the .opacity modifier. Then, wrap the respective NavigationLink into a ZStack to stack the CategoryView on top of the invisible NavigationLink. Also, make sure to apply the .listRowSeparator to the whole ZStack. Repeat these steps for every NavigationLink in the ContentView.

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                ZStack {
                    NavigationLink(destination: DetailView(currentCategory: .burger)) {
                        EmptyView()
                    }
                        .opacity(0.0)
                    CategoryView(imageName: "burger", categoryName: "Burger")
                }
                    .listRowSeparator(.hidden)
                ZStack {
                    NavigationLink(destination: DetailView(currentCategory: .pizza)) {
                        EmptyView()
                    }
                        .opacity(0.0)
                    CategoryView(imageName: "pizza", categoryName: "Pizza")
                }
                    .listRowSeparator(.hidden)
                ZStack {
                    NavigationLink(destination: DetailView(currentCategory: .pasta)) {
                        EmptyView()
                    }
                        .opacity(0.0)
                    CategoryView(imageName: "pasta", categoryName: "Pasta")
                }
                    .listRowSeparator(.hidden)
                ZStack {
                    NavigationLink(destination: DetailView(currentCategory: .desserts)) {
                        EmptyView()
                    }
                        .opacity(0.0)
                    CategoryView(imageName: "desserts", categoryName: "Desserts")
                }
                    .listRowSeparator(.hidden)
            }
                .listStyle(.plain)
                .navigationTitle(Text("Food Delivery"))
        }
    }
}

Perfect! Let’s run our app again by starting a live ContentView Preview. You can also run the app in the “standard” simulator. Everything works fine now. We can select any category and get a detailed list of all of the products belonging to the selected category.

Conclusion 🎊

Wow! That was really a lot of stuff we just learned. 

We learned how to create dynamic Lists using our own data model. We also learned how to create custom rows by creating an outsourced view for this purpose. 

Eventually, we connected our ContentView and DetailView of our Food Delivery App by using a NavigationView. By doing this, we also learned how to transfer information between different views in SwiftUI!

Congratulations, you really learned a lot of things about SwiftUI so far.

Make sure you get some rest and grab yourself a coffee.

In the next chapter, we will create a more simple app to learn how the data flow concept used in SwiftUI apps works.

5 replies on “Connecting the views using a NavigationView”

The food item row becomes a button its self which overrides the Order button. This allows the user to click anywhere on the row and the ‘Food ordered’ text is printed to the console. Is there a reason for this or a way to fix it?

Hi Brenton

when inserting a Button into a List’s row, the whole row gets “touchable”. If you want to limit the “touchable” area to the actual Button you need to apply the following modifier to it

.buttonStyle(BorderlessButtonStyle())

It seems to me that the functionality of the categoryString method would be better implemented using a typed enum and the .rawValue property on a value. This keeps the value to string as part of the Categories type.

Leave a Reply

Your email address will not be published. Required fields are marked *