NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

Creating a secondary view for NavigationView

Paul Hudson    @twostraws   

Right now our NavigationLink directs the user to some sample text, which is fine for prototyping but obviously not good enough for our actual project. We’re going to replace that with a new ResortView that shows a picture from the resort, some description text, and a list of facilities.

Important: Like I said earlier, the content in my example JSON is mostly fictional, and this includes the photos – these are just generic ski photos taken from Unsplash. Unsplash photos can be used commercially or non-commercially without attribution, but I’ve included the photo credit in the JSON so you can add it later on. As for the text, this is taken from Wikipedia. If you intend to use the text in your own shipping projects, it’s important you give credit to Wikipedia and its authors and make it clear that the work is licensed under CC-BY-SA available from here: https://creativecommons.org/licenses/by-sa/3.0.

To start with, our ResortView layout is going to be pretty simple – not much more than a scroll view, a VStack, an Image, and some Text. The only interesting part is that we’re going to show the resort’s facilities as a single text view using resort.facilities.joined(separator: ", ") to get a single string.

Create a new SwiftUI view called ResortView, and give it this code to start with:

struct ResortView: View {
    let resort: Resort

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 0) {
                Image(decorative: resort.id)
                    .resizable()
                    .scaledToFit()

                Group {
                    Text(resort.description)
                        .padding(.vertical)

                    Text("Facilities")
                        .font(.headline)

                    Text(resort.facilities.joined(separator: ", "))
                        .padding(.vertical)
                }
                .padding(.horizontal)
            }
        }
        .navigationTitle("\(resort.name), \(resort.country)")
        .navigationBarTitleDisplayMode(.inline)
    }
}

You’ll also need to update ResortView_Previews to pass in an example resort for Xcode’s preview window:

struct ResortView_Previews: PreviewProvider {
    static var previews: some View {
        ResortView(resort: Resort.example)
    }
}

And now we can update the navigation link in ContentView to point to our actual view, like this:

NavigationLink {
    ResortView(resort: resort)
} label: {

There’s nothing terribly interesting in our code so far, but that’s going to change now because I want to add more details to this screen – how big the resort is, roughly how much it costs, how high it is, and how deep the snow is.

We could just put all that into a single HStack in ResortView, but that restricts what we can do in the future. So instead we’re going to group them into two views: one for resort information (price and size) and one for ski information (elevation and snow depth).

The ski view is the easier of the two to implement, so we’ll start there: create a new SwiftUI view called SkiDetailsView and give it this code:

struct SkiDetailsView: View {
    let resort: Resort

    var body: some View {
        Group {
            VStack {
                Text("Elevation")
                    .font(.caption.bold())
                Text("\(resort.elevation)m")
                    .font(.title3)
            }

            VStack {
                Text("Snow")
                    .font(.caption.bold())
                Text("\(resort.snowDepth)cm")
                    .font(.title3)
            }
        }
        .frame(maxWidth: .infinity)
    }
}

struct SkiDetailsView_Previews: PreviewProvider {
    static var previews: some View {
        SkiDetailsView(resort: Resort.example)
    }
}

Giving the Group view a maximum frame width of .infinity doesn’t actually affect the group itself, because it has no impact on layout. However, it does get passed down to its child views, which means they will automatically spread out horizontally.

As for the resort details, this is a little trickier because of two things:

  1. The size of a resort is stored as a value from 1 to 3, but really we want to use “Small”, “Average”, and “Large” instead.
  2. The price is stored as a value from 1 to 3, but we’re going to replace that with $, $$, or $$$.

As always, it’s a good idea to get calculations out of your SwiftUI layouts so it’s nice and clear, so we’re going to create two computed properties: size and price.

Start by creating a new SwiftUI view called ResortDetailsView, and give it this property:

let resort: Resort

As with ResortView, you’ll need to update the preview struct to use some example data:

struct ResortDetailsView_Previews: PreviewProvider {
    static var previews: some View {
        ResortDetailsView(resort: Resort.example)
    }
}

When it comes to getting the size of the resort we could just add this property to ResortDetailsView:

var size: String {
    ["Small", "Average", "Large"][resort.size - 1]
}

That works, but it would cause a crash if an invalid value was used, and it’s also a bit too cryptic for my liking. Instead, it’s safer and clearer to use a switch block like this:

var size: String {
    switch resort.size {
    case 1:
        return "Small"
    case 2:
        return "Average"
    default:
        return "Large"
    }
}

As for the price property, we can leverage the same repeating/count initializer we used to create example cards in project 17: String(repeating:count:) creates a new string by repeating a substring a certain number of times.

So, please add this second computed property to ResortDetailsView:

var price: String {
    String(repeating: "$", count: resort.price)
}

Now what remains in the body property is simple, because we just use the two computed properties we wrote:

var body: some View {
    Group {
        VStack {
            Text("Size")
                .font(.caption.bold())
            Text(size)
                .font(.title3)
        }

        VStack {
            Text("Price")
                .font(.caption.bold())
            Text(price)
                .font(.title3)
        }
    }
    .frame(maxWidth: .infinity)
}

Again, giving the whole Group an infinite maximum width means these views will spread out horizontally just like those from the previous view.

That completes our two mini views, so we can now drop them into ResortView – put this just before the group in ResortView:

HStack {
    ResortDetailsView(resort: resort)
    SkiDetailsView(resort: resort)
}
.padding(.vertical)
.background(Color.primary.opacity(0.1))

We’re going to add to that some more in a moment, but first I want to make one small tweak: using joined(separator:) does an okay job of converting a string array into a single string, but we’re not here to write okay code – we’re here to write great code.

Previously we’ve used the format parameter of Text to control the way numbers are formatted, but there’s a format for string arrays too. This is similar to using joined(separator:), but rather than sending back “A, B, C” like we have right now, we get back “A, B, and C” – it’s more natural to read.

Replace the current facilities text view with this:

Text(resort.facilities, format: .list(type: .and))
    .padding(.vertical)

Notice how the .and type is there? That’s because you can also use .or to get “A, B, or C” if that’s what you want.

Anyway, it’s a tiny change but I think it’s much better!

Hacking with Swift is sponsored by MadMachine

SPONSORED Want to explore your Swift skill outside of the Apple world? Join the MadMachine community and start to program microcontrollers in Swift.

Get it now

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS 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: 4.6/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.