NEW: Join my free 100 Days of SwiftUI challenge today! >>

Displaying a detail screen with NavigationLink

Paul Hudson    @twostraws   

Fully updated for Xcode 11 GM

When a menu item is tapped, we want to bring in a detail view that shows more information. We already placed ContentView inside a navigation view, so now we can use a new view type called NavigationLink. We need to give this a destination – what kind of thing it should show – then provide everything inside the link as a closure.

In practice, this looks like all the other containers we’ve used so far, so let’s try it out with a neat shortcut: although we’re going to be showing a detail view in just a minute, we can use a regular text view as a placeholder.

So, put this around all the existing contents of the body property in ItemRow:

NavigationLink(destination: Text(item.name)) {
    // existing contents…
}

That means the whole row is a navigation link with a destination of the item’s name.

If you run the app now you’ll see two important differences:

  1. All our rows now have a gray disclosure indicator on their right edge, because SwiftUI gives us the correct behavior by default.
  2. When you tap on any item a new screen will slide in saying the name of whatever item you chose.

Being able to present raw views like this is a great timesaver while building up user interfaces!

Of course, we want more – we want a nice big picture, some details about the food, and more. So, press Cmd+N to make another new SwiftUI view, this time called ItemDetail.swift.

As with ItemRow, this needs to have a menu item passed in and stored as a property, so add this to ItemDetail now:

var item: MenuItem

We also need to update its preview code to use our example item, so we can see what we’re doing:

static var previews: some View {
    ItemDetail(item: MenuItem.example)
}

As with our list rows, we’re going to start off simple and iterate until we get something that works well.

First, a simple version of our ItemDetail view that has an item’s image and description, plus a title at the top:

struct ItemDetail : View {
    var item: MenuItem

    var body: some View {
        VStack {
            Image(item.mainImage)
            Text(item.description)
        }
        .navigationBarTitle(item.name)
    }
}

So that you can start seeing things in action immediately, let’s update our NavigationLink in ItemRow.swift so that it shows an ItemDetail view with the selected item:

NavigationLink(destination: ItemDetail(item: item)) {

Now you can run the code as we progress, seeing the detail screen in action.

You won’t see the title at the top because the preview doesn’t know it’s in a navigation view. To fix that, we can just change the preview like so:

struct ItemDetail_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            ItemDetail(item: MenuItem.example)
        }
    }
}

That doesn’t actually change what our code does at runtime – it’s just the preview that has changed.

You can see that our detail view has some layout issues, so let’s correct them.

First, that navigation bar title shouldn’t be big, because Apple recommends using that style only for top-level screens in a user interface. We can fix that by adding another parameter to the navigationBarTitle() modifier, like this:

.navigationBarTitle(Text(item.name), displayMode: .inline)

Tip: Although SwiftUI was letting us use simple strings for navigation bar titles previously, now that we’re specifically requesting an inline style we need to wrap the title string in a Text view.

Second, while having the image go edge to edge looks good, having the description text go edge to edge is less good. We can fix that by adding a padding() modifier like this:

Text(item.description)
    .padding()

The padding() modifier lets us specify the sides where we want padding and also how much to use, but without any parameters it will apply padding to all edges. How much it will apply depends on the context – what device is being used, etc – but it generally looks good.

Third, it looks strange having our content vertically centered, because our eyes are used to information being aligned to the top. To fix that we can use another Spacer(), directly after the item description:

Image(item.mainImage)
Text(item.description)
    .padding()
Spacer()

This is starting to look good, but we also need to find a way to show the name of the person who took the photo of our food. We could put that below the picture or inside an alert, but a better idea is to put over the image, in the bottom-right corner.

You’ve already met horizontal and vertical stacks (HStack and VStack), but SwiftUI gives us a third option called ZStack to handle overlapping views. To use one here, replace our existing image with this:

ZStack {
    Image(item.mainImage)
    Text("Photo: \(item.photoCredit)")
}

That creates the image then layers some text on top. Chances are you’ll struggle to see that text, so let’s apply some modifiers to make it clearer:

Text("Photo: \(item.photoCredit)")
    .padding(4)
    .background(Color.black)
    .font(.caption)
    .foregroundColor(.white)

Tip: If you swap the order of the padding() and background() modifiers the result is different. The order matters!

It’s more visible now, but that just means we can see it doesn’t look great – it shouldn’t really be right over our food!

To fix that we can add some alignment to our ZStack so that the label is in the bottom-right corner:

ZStack(alignment: .bottomTrailing) {

We can even apply some custom offsets to the image, pulling it up and left just a little from the edge:

Text("Photo: \(item.photoCredit)")
    .padding(4)
    .background(Color.black)
    .font(.caption)
    .foregroundColor(.white)
    .offset(x: -5, y: -5)

Nice!

Further reading

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

Similar solutions…

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5