SwiftUI – Mastering Table Views (Lists) #1

Share on facebook
Share on twitter
Share on pinterest

This is the first part of the Mastering Table Views (Lists) series, if you are looking for the second one, click here!

Welcome to the first part of our Mastering Table Views (Lists) in SwiftUI series! In this tutorial we will rebuild our Food Delivery App, which we explained in our Table Views Guide, but this time with Apple’s newest UI-Framework SwiftUI! We will get to know the basic concepts of SwiftUI and learn how to apply them. If you don’t know what SwiftUI is and what you need in order to run it, have a look at this article.

Here is a video of the finished app we are going to build in this series:



Project creation and image import 🆕

Let’s start with creating a new project and importing the images we will need later on. Open Xcode 11 and create a new Xcode project. Then select Single View App and click on Next. Give the project a suitable name and make sure “Use SwiftUI” is selected. Then click on Create.

Make Sure “Use SwiftUI” is selected

Xcode starts with the ContentView.swift file opened and which shows you a kind of split screen. Before we get to know the SwiftUI interface of Xcode 11, let’s import the images we will need for creating our category cells. To do this, drag and drop the files into the Assets.xcassets folder. Make sure the images are named appropriately.

We’ve done everything necessary for setting up our project. So let’s dive into learning SwiftUI!

SwiftUI Basics 👁

Let’s get started with learning the very basics of SwiftUI that we need to know to build our Table View! Make sure you have opened the DefaultView.swift file. The code you see in the left section of your screen consists of two blocks: The ContentView struct and the ContentView_Previews struct. The ContentView struct contains the body of your UI. Here we create all the components we need for our UI, arrange and modify them. The ContentView_Previews struct then renders this body and displays it in the Preview Simulator on the right section of your screen.

Note: If the Preview Simulator is not running yet click on the “Resume” button in the upper right corner. Usually, changes are adopted automatically by the simulator. But sometimes, especially when larger changes are made, clicking the Resume button is necessary.

The right section contains the preview simulator, but sometimes clicking the “Resume” button is necessary

The right section contains the preview simulator, but sometimes clicking the “Resume” button is necessary

By default, the body contains a Text object with the content “Hello World”. A Text object in SwiftUI is similar to a Label in UIKit. Let’s change the content of the Text to “Food Delivery”. You can see that it directly affects the UI in the Preview Simulator! To modify a SwiftUI object we can make adjustments within the code or CMD-click the object directly in the Preview Simulator.

CMD-click on the Text object in the simulator and click on “Inspect”. Here we can adjust the object, for instance change the text type. For example, select “Large Title” as the font. As you see, the adjustment has a direct effect on the code. Another example, but this time within the written code: To change the font color, we can add a according modifier to the code. Below the .font modifier we can add a .color modifier and select for example green as the font color.

struct ContentView : View {
var body: some View {
       Text("Hello World")
            .font(.largeTitle)
            .foregroundColor(Color.green)
    }
}

Creating a “cell” with SwiftUI 🖌

But instead of texts, we want to have pictures, which will contain the appropriate texts for the categories. Thus, we delete the Text object and insert an Image object instead which we initialize by using the name of the image.

struct ContentView : View {
    var body: some View {
        Image("burger")
    }
}

The image currently fills out the entire screen of the device, so we need to give the image a frame which serves as a fixed-sized container for it. We do this by applying a .frame modifier to the Image object and giving the image frame a suitable width and height. In order for the image to not exceed the frame we have to add a .clipped Modifier. The .clipped modifier cuts out the area of the object that exceeds the specified frame.

struct ContentView : View {
    var body: some View {
        Image("burger")
            .frame(width: 300, height: 150)
            .clipped()
    }
}

Next, we need to resize the image so that it fits into the frame without distorting the dimensions of the image. To change the scaling of an image, we must use the .resizable modifier first. The .resizable modifier must be applied to enable changing the scaling of an image. It is important that the .resizable modifier is the first modifier of the image object.

struct ContentView : View {
    var body: some View {
        Image("burger")
            .resizable()
            .frame(width: 300, height: 150)
            .clipped()
    }
}

Next, we apply the .aspectRatio modifier. This modifier scales the image so that it fits into the frame by using the specified dimensions. For keeping the original dimensions of the Image we can choose between the .fill or .fit contentMode. “Fill” scales the image so that the entire frame gets filled out with the image. “Fit” scales the image so that the entire image is visible within the frame. In our example we choose .fill as the content mode.

struct ContentView : View {
    var body: some View {
        Image("burger")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: 300, height: 150)
            .clipped()
    }
}

Last but not least, let’s round the corners of the image. We do this by applying the .cornerRadius modifier (as the last modifier!).

struct ContentView : View {
    var body: some View {
        Image("burger")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: 300, height: 150)
            .clipped()
            .cornerRadius(20.0)
    }
}

Your preview should now look as follows:

For our category cell we want a text with the name of the category that gets stacked on top of the Image object. To stack objects in SwiftUI we use so-called ZStacks. We put the image object into a ZStack like this:

struct ContentView : View {
    var body: some View {
        ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
        }
    }
}

All objects within a ZStack are placed on top of each other. Let’s stack a Text object onto the Image object.

struct ContentView : View {
    var body: some View {
        ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Text("BURGER")
        }
    }
}

To make the Text object to be of a custom font and size we use the custom function within the .font modifier. Let’s also change the text color to white.

struct ContentView : View {
    var body: some View {
        ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(UIImage(named: "burger")!.size, contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Text("BURGER")
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
        }
    }
}

Here’s the current look of our cell:

Creating the list 💡

Of course we don’t want only one category for our Food Delivery App, but instead we need a total of four categories. To avoid writing the same code four times we make it reusable by CMD-clicking on the ZStack and selecting “Extract as subview” and giving it the name “CategoryView”. If this option isn’t available you can manually create a subview like this:


import SwiftUI

//We deleted the ZStack first, extracted it as a subview below, and inserted this subview
struct ContentView : View {
    var body: some View {
        CategoryView()
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

//Here is the extracted subview
struct CategoryView : View {
    
    var body: some View {
        return ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Text("BURGER")
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
            }
    }
}

The ZStack is now outsourced as a separate view which we can easily insert into our body.

Hint: Extracting views as subviews is best practice in SwiftUI and helps you making your code more reusable and clear.

We want to have four categories for our Food Delivery App. To achieve this, we use something called Lists in SwiftUI. A list is like a Table View you know from UIKit and contains multiple rows of data in a single column.

“List: A container that presents rows of data arranged in a single column.”

Apple

Lets create such a list and embed our CategoryView inside it. To have a total of four category “cells” (it’s not 100 percent correct to call the rows of a list cells) we have to insert three more CategoryViews into the List.

struct ContentView : View {
    var body: some View {
        List {
            CategoryView()
            CategoryView()
            CategoryView()
            CategoryView()
        }
    }
}

Hint: Maybe your cells are not centred anymore. To fix this, insert a Spacer inside the ContentView’s ZStack between the Image and the Text object.

struct CategoryView : View {
    
    var body: some View {
        return ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Spacer()
            Text("BURGER")
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
            }
    }
}

The cells are a little too close together. To increase the distances we can add paddings. A padding serves as a gap next to an object. We want to add paddings above and below our CategoryViews to increase the distance between them. So we add two suitable .padding modifiers to the ZStack of the extracted CategoryView.

struct CategoryView : View {
    
    var body: some View {
        return ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Spacer()
            Text("BURGER")
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
            }
                .padding(.top, 5)
                .padding(.bottom, 5)
    }
}

Here’s what your Preview Simulator should display so far:

Making the list dynamic 🔄

Of course not every CategoryView should contain the same image with the same text. To change this we insert two variables into our CategoryView above its body, a variable imageName and a variable categoryName. We want to use these variables to initialize the image and to insert the correct text. The values for the variables will then be entered at the initialization of the CategoryViews. So we adapt our CategoryView accordingly and use the variables for our image and the text instead of static values.

struct CategoryView : View {
    
    var imageName: String
    var categoryName: String
    
    var body: some View {
        return ZStack {
            Image(imageName)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Spacer()
            Text(categoryName)
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
            }
                .padding(.top, 5)
                .padding(.bottom, 5)
    }
}

Now we have to adjust the initializations of the CategoryViews in the ContentView body and specify suitable values.

import SwiftUI

struct ContentView : View {
    var body: some View {
        List {
            CategoryView(imageName: "burger", categoryName: "BURGER")
            CategoryView(imageName: "pizza", categoryName: "PIZZA")
            CategoryView(imageName: "pasta", categoryName: "PASTA")
            CategoryView(imageName: "cake", categoryName: "DESSERTS")
        }
    }
}

Great! Here’s how our Food Delivery app looks so far:

And here’s the whole code we have written so far:

CategoryView.swift:


import SwiftUI

struct ContentView : View {
    var body: some View {
        List {
            CategoryView(imageName: "burger", categoryName: "BURGER")
            CategoryView(imageName: "pizza", categoryName: "PIZZA")
            CategoryView(imageName: "pasta", categoryName: "PASTA")
            CategoryView(imageName: "cake", categoryName: "DESSERTS")
        }
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

struct CategoryView : View {
    
    var imageName: String
    var categoryName: String
    
    var body: some View {
        return ZStack {
            Image(imageName)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Spacer()
            Text(categoryName)
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
            }
                .padding(.top, 5)
                .padding(.bottom, 5)
    }
}

Conclusion 🎊

Finished! With SwiftUI we have created a dynamic TableView as a list. In the next part of this series we will create a second screen containing the individual products of each category and learn how to connect different views with SwiftUI and how to transfer data between them.

6 replies on “SwiftUI – Mastering Table Views (Lists) #1”

Hi all,

Your demos are awesome they are very helpful to me. I’m struggling with custom pickerview in swiftui can you make any demo for that ?

Thanks and Regards
G Bharat

Hey there, thanks for your kind feedback? What exactly are you struggling with? We made a tutorial about how to create a multi component picker in SwiftUI.

struct CategoryView: View {

var imageName: String
var categoryName: String

var body: some View {

HStack {

Spacer()

ZStack {

Image(imageName)
.resizable()
//.scaledToFill()
.aspectRatio(contentMode: .fill)
.frame(width: 300, height: 150)
.clipped()
.cornerRadius(20)
.shadow(color: Color.gray, radius: 2, x: 1, y: 2)
.overlay(
Rectangle()
.stroke(Color.black, lineWidth: 0.5)
.cornerRadius(20)
)

Text(categoryName)
.font(.custom(“HelveticaNeue-Medium”, size: 50))
.foregroundColor(.white)
.shadow(color: .black, radius: 2, x: 1, y: 2)
}

Spacer()

}
.padding(.top, 5)
.padding(.bottom, 5)

}
}

Leave a Reply

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

small_c_popup.png

Covid-19 Forces you into quarantine?

Start Mastering swiftUI Today save your 33% discount

small_c_popup.png

Are you ready for a new era of iOS development?

Start learning swiftUI today - download our free e-book