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

Composing views to create a list row

Paul Hudson    @twostraws   

Fully updated for Xcode 11 GM

Just having names of menu items written out isn’t particularly appealing for a restaurant, so let’s make those items look good.

First, though, an important lesson: SwiftUI is designed to be composable, which means you can make views out of any other views you like. We have a simple text view for our items right now, Text(item.name), but we’re going to add much more in there to bring it to life. While we could put that directly into ContentView.swift, it becomes long and hard to read.

A better idea is to make a new view type that we can embed inside ContentView, and SwiftUI is designed to make this both easy (it takes only 30 seconds to learn) and extremely fast (it has almost zero performance impact).

So, press Cmd+N to create a new file, choose SwiftUI View under the User Interface category, then call it “ItemRow”. You’ll see Xcode has generated a new view that looks much like ContentView did when we started:

struct ItemRow: View {
    var body: some View {
        Text("Hello World!")
    }
}

We’re going to be doing something new in just a moment, but first I want to get us to the point where we are using ItemRow in our code. This means we need to add a MenuItem property to ItemRow, then use it in its body, like this:

struct ItemRow : View {
    var item: MenuItem

    var body: some View {
        Text(item.name)
    }
}

Now we can go back to ContentView.swift and replace Text(item.name) with this:

ItemRow(item: item)

As you can see, that will create new ItemRow for each item in our menu section, which in turn will have some text inside.

Now, our code won’t build right now, because there’s a problem in ItemRow.swift. This code is invalid:

struct ItemRow_Previews: PreviewProvider {
    static var previews: some View {
        ItemRow()
    }
}

That is SwiftUI’s previewing code, which is what allows it show live previews while we work. It’s trying to create an ItemRow without a menu item attached, which isn’t possible. If you really wanted to preview individual rows you could do so by instantiating a copy of our full menu and passing in an example item, but in this instance I’ve provided an example item for us so we have something to look at.

Change the code to this to make it all work again:

struct ItemRow_Previews: PreviewProvider {
    static var previews: some View {
        ItemRow(item: MenuItem.example)
    }
}

Once that’s done our code will build again, and if you return back to ContentView.swift you should see the same preview we had before – nothing has changed. Of course, now we can start to add new things to that ItemRow struct to make it more interesting!

In ItemRow, we’re going to start by placing the item’s thumbnail image and name side by side, like you’d normally see in a UITableViewCell. Try writing this:

var body: some View {
    Image(item.thumbnailImage)
    Text(item.name)
}

You’ll find that Swift gives an error message similar to this one: “Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type.”

What it means is that some View means we must return one specific view from our method – not two different views, and certainly not no views at all.

To fix this we need to put those two views inside a container, which in our case will be a HStack. This is a container that places its child views side by side horizontally, which is perfect for our needs.

So, try this instead:

var body: some View {
    HStack {
        Image(item.thumbnailImage)
        Text(item.name)
    }
}

If you wanted to put the item’s price next to its name, you might try something like this:

HStack {
    Image(item.thumbnailImage)
    Text(item.name)
    Text("$\(item.price)")
}

However, that will put the price to the right of the name, which isn’t great. What we usually want here is the kind of subtitle style that we get from UITableViewCell, where we can have an image on the left, and on the right have a title above a detail label.

We can achieve that with another stack, called a VStack – a vertical stack. This can go inside our existing HStack to create the hierarchy we want:

HStack {
    Image(item.thumbnailImage)

    VStack {
        Text(item.name)
        Text(String("$\(item.price)"))
    }
}

You’ll notice that our item name and price are centered. That happens because it’s the default behavior of VStack, but a left alignment would look much better here. We can get that by asking for a leading alignment when creating the VStack, like this:

VStack(alignment: .leading) {
    Text(item.name)
    Text(String("$\(item.price)"))
}

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: 4.6/5