Categories
Uncategorized

SwiftUI – Mastering Table Views (Lists) #2

This is the second part of the Mastering Table Views (Lists) series, here is the first part!

Welcome to the second part of our Mastering Table Views (Lists) in SwiftUI series! In this series we will rebuild a Food Delivery App with Apple’s newest UI-Framework SwiftUI! We will get to know 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.

In this part we will create a detailed view that contain the products of the category that is selected by the user. We will learn how to connect the detailed view with the main view and how to pass data through them.

Building our data model 🛠

Because we want to provide our detailed view with the several products, we have to create an according data model for handling that products.

Let’s create a new Swift file by clicking File – New – File and then selecting Swift File. Let’s call this file Food. Make sure the Foundation and SwiftUI Kit is imported. For handling our food products we create a class called Food inside this file.

import Foundation
import SwiftUI

class Food {

}

Each product should contain the product’s title and the price of the product. Therefore we create appropriate attributes.

class Food {

   let title: String
   let price: Double

}

Every Food instance should belong to one of the four categories: burger, pasta, pizza and dessert. To be able to select and distinguish between these four different options we have to create an enum.

“An enumeration is a group of values that are related […] The easiest way to think about enums is as structured lists of related items. A few examples: Colors: red, green, blue, purple, yellow, etc. Ice cream flavors: vanilla, chocolate, stracciatella, butter pecan, etc.”

learnappmaking.com

Let’s create a new file named Helper to place that enum in. We call the enum Categories and insert four different cases for our four categories.

enum Categories {
    case burger
    case pasta
    case pizza
    case dessert
}

Now we can switch back the our Food.swift file and insert an attribute category of the type Categories. Each instance created out of this class will now be assigned to a certain category.

class Food {
    
    let title: String
    let price: Double
    let category: Categories
    
}

Because we will use our Food data model to wrap multiple instances of it into a List later on, we need it to conform to the Identifiable protocol. This is required to pass custom class instances into Lists in SwiftUI (it’s also the reason why we imported the SwiftUI kit into our Food.swift file). The Identifiable protocol has only one mandatory requirement: It needs the class to contain an attribute (for the list) to identify every instance by a unique id. Therefore we simply declare an id attribute of the type Int.

class Food: Identifiable {
    
    let title: String
    let price: Double
    let category: Categories
    let id: Int
    
}

Our Food model now contains everything we need, so let’s add the according init function:

class Food: Identifiable {
    
    let title: String
    let price: Double
    let category: Categories
    let id: Int
    
    init(title: String, price: Double, category: Categories, id: Int) {
        self.title = title
        self.price = price
        self.category = category
        self.id = id
    }
    
}

Providing the product data ➡️

Now that we created our Food data model we are able to import our products which we will use to feed our detailed list.

We will use a separate file for this data, so create a new Swift file called FoodData.

Let’s use an array containing all the products we want for our app. Each product is a instance of the Food data model and must therefore be initialised with the according product title, price, category and id.

Feel free to copy and paste the array below into your FoodData.swift file. Of course you can edit this array to your wishes!


let foodData: [Food] = [
    Food(title: "Margherita", price: 5.99, category: .pizza, id: 1),
    Food(title: "Prosciutto", price: 6.89, category: .pizza, id: 2),
    Food(title: "Funghi", price: 6.99, category: .pizza, id: 3),
    Food(title: "Calzone", price: 6.99, category: .pizza, id: 4),
    Food(title: "BBQ Burger", price: 9.90, category: .burger, id: 5),
    Food(title: "Cheeseburger", price: 7.90, category: .burger, id: 6),
    Food(title: "Vegan Burger", price: 8.90, category: .burger, id: 7),
    Food(title: "Pulled Pork Burger", price: 11.90, category: .burger, id: 8),
    Food(title: "Spagetthi Bolognese", price: 8.90, category: .pasta, id: 9),
    Food(title: "Penne all'arrabbiata", price: 7.90, category: .pasta, id: 10),
    Food(title: "Aglio e olio", price: 7.90, category: .pasta, id: 11),
    Food(title: "Cheesecake", price: 3.99, category: .dessert, id: 12),
    Food(title: "Cupcake", price: 2.99, category: .dessert, id: 13),
    Food(title: "Icecream", price: 2.99, category: .dessert, id: 14)
]

Creating our food detail rows 🖌

Let’s create our UI for the detailed products view. We want to have a list again for this. As you learned in the last part of this tutorial, lists contain of rows with the data to display.

We first want to create the UI of these rows before we make a list out of them. Therefore we create a new file but now of the type SwiftUI View. Let’s call it DetailRow.swift.

Each DetailRow should contain three objects: A Text object containing the title of the product, another Text object containing the according price and a Button which notifies us when the user orders the product.

Let’s start with our first Text object. We can use the default Text object which currently contains “Hello World” for this, but change it to, for example, “BBQ Burger”. Don’t worry, we will make this dynamic by using our data model in a moment. But for now, let’s use static values for building up the UI.

Next, we want another Text object for the price of the product. The title Text and the price Text should be stacked vertically, so CMD-click on the Text object and select “Embed in VStack”. Now we can insert a second Text object containing the price of the food, for example, “10.00 $”. Both text object should be aligned on the “left side”, so we insert the .leading option for the alignment of the VStack.

struct DetailRow : View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("BBQ Burger")
            Text("10.00 $")
        }
    }
}

We want our title text object to be of a more emphasised look. Therefore we apply the .font modifier with the .headline option to it. We do the same with the price Text object but select the .caption option. Additionally, we want a little padding for the title Text, so we add a .padding modifier for the text to be 10 points away from the upper bound.

struct DetailRow : View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("BBQ Burger")
                .font(.headline)
                .padding(.top, 10)
            Text("10.00 $")
                .font(.caption)
        }
    }
}

Next, let’s create the “Order” Button. To do this, we have to embed the VStack into an HStack. CMD-click on the VStack and select “Embed in HStack”. Now we can insert another Text object next to the both Text objects.


struct DetailRow : View {
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("BBQ Burger")
                    .font(.headline)
                    .padding(.top, 10)
                    Text("10.00 $")
                        .font(.caption)
                }
            Text("ORDER")
            }
    }
}

To make this new Text to be part of a Button, select it and click on “Embed in a Button”.

But let’s delete the onTrigger argument and instead insert the action parameter followed by curly braces manually. The action argument of a Button accepts a closure that contains what happens when the user taps on the Button. We want to be notified, therefore we write a appropriate print statement into the curly braces.

struct DetailRow: View {
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("BBQ Burger")
                    .font(.headline)
                    .padding(.top, 10)
                    Text("10.00 $")
                        .font(.caption)
                }
            Button(action: {print("Order received")}) {
                    Text("ORDER")
                }
            }
    }
}

We want to change the Button’s size, so we apply a .frame modifier to it and insert the width and height we want for the Button. We also want an orange background for it that should be rounded. Therefore we apply the .background and .cornerRadius modifier to it.

struct DetailRow: View {
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("BBQ Burger")
                    .font(.headline)
                    .padding(.top, 10)
                    Text("10.00 $")
                        .font(.caption)
                }
            Button(action: {print("Order received")}) {
                    Text("ORDER")
                }
                .frame(width: 80, height: 50)
                .background(Color.orange)
                .cornerRadius(10.0)
            }
    }
}

Last but not least, we want the Text inside the Button to be of a white font, so we insert a .foregroundColor modifier below the text object.

struct DetailRow: View {
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("BBQ Burger")
                    .font(.headline)
                    .padding(.top, 10)
                    Text("10.00 $")
                        .font(.caption)
                }
            Button(action: {print("Order received")}) {
                    Text("ORDER")
                        .foregroundColor(.white)
                }
                .frame(width: 80, height: 50)
                .background(Color.orange)
                .cornerRadius(10.0)
            }
    }
}

To push both objects inside the HStack, the VStack containing the two Text objects and the Button, to the left and right edges of the screen we insert a Spacer between them. We also want all objects to be a little bit away from all edge, therefore we apply an overall .padding modifier

struct DetailRow: View {
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("BBQ Burger")
                    .font(.headline)
                    .padding(.top, 10)
                    Text("10.00 $")
                        .font(.caption)
                }
            Spacer()
            Button(action: {print("Order received")}) {
                    Text("ORDER")
                        .foregroundColor(.white)
                }
                .frame(width: 80, height: 50)
                .background(Color.orange)
                .cornerRadius(10.0)
            }
            .padding(20)
    }
}

Nice, we created the UI for our DetailRows. Now, let’s make the content of these rows dynamic!

Making the food details rows dynamic 🔄

Instead of static data, each row of our detailed food list should display the data of the certain Food instance. There, in our DetailRow class we have to declare a variable of the type Food above our body.

struct DetailRow: View {
    
    var food: Food
    
    var body: some View {
        //...
    }
}

This variable will be assigned to a Food instance for every row that will be initialised. In our RowView’s body we use the properties of the food variable for displaying the products title and price.

struct DetailRow : View {
    
    var food: Food
    
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(food.title)
                    .font(.headline)
                    .padding(.top, 10)
                    Text("\(food.price) $")
                        .font(.caption)
                }
            Spacer()
            Button(action: {print("Order received")}) {
                    Text("ORDER")
                        .foregroundColor(.white)
                }
                .frame(width: 80, height: 50)
                .background(Color.orange)
                .cornerRadius(10.0)
            }
            .padding()
    }
}

Note: Due to a bug in Xcode 11 beta, it’s currently not possible to round Double values to certain decimal places. I will update this as soon as there exist a solution!

Because we used a dynamic variable which is not yet assigned to a Food instance, we need to update our DetailRow_Previews struct and initialise a sample Food instance to provide the preview simulator with sample data to display. For this purpose we can simply choose the first object of our FoodData array.

Our UI should now look as follows:

Building our food detail list 🆕

Now that we created the UI for the rows displaying the products data, we can use these to build our view with the detailed list of the foods.

Let’s create a new SwiftUI file called DetailView and delete the default Text object. For now, this view should contain a list with all the objects inside the FoodData array. Let’s create such a List:

struct DetailView: View {
    
    var body: some View {
        List() {
            
        }
    }
    
}

The List needs the data it should contain as the input. This data must be distinguishable by an id. We did this by conforming the Food class to the identifiable protocol and assigning all instances inside the foodData array to unique id’s. Therefore we can simply insert the foodData array into our list.

struct DetailView: View {
    
    var body: some View {
        List(foodData) {
            
        }
    }
    
}

Inside the curly braces of the List object we have to insert a closure which determines what happens with all the objects in the foodData array. We want to initialise one row for every object in this array. Therefore we write:

struct DetailView : View {
    
    var body: some View {
        List(foodData) { food in
            DetailRow(food: food)
        }
    }
    
}

Great! Our preview now contains a list with our DetailRows for every object in our FoodData array!

Of course the DetailView should only display the foods of the category which the user selected. Thus, the list should only contain the objects of the foodData array which is of the currently selected category.

We therefore declare a variable of the Categories enum type which represents the category that the user selected on the main screen.

struct DetailView : View {
    
    var currentCategory: Categories
    
    var body: some View {
        //...
    }
}

To filter the data of the foodData array by the selected category we have to create a helper function. We put a function called filterData inside our Helper.swift file. This helper function takes the selected category as its input and returns a new array containing the filtered food data.

func filterData(by category: Categories) -> [Food] {

}

To filter the data we declare an empty array inside the functions body. What we now do is to cycle trough all objects of the foodData array and check for every element if it is of the same category as the input category. If this is true it adds the object to the filtered array. If our function cycled through element it eventually returns the filtered array to us.

func filterData(by category: Categories) -> [Food] {
    var filteredArray = [Food]()
    
    for food in foodData {
        if food.category == category {
            filteredArray.append(food)
        }
    }
    
    return filteredArray
}

Back to our DetailView: Instead of the whole foodData array we can now insert the filterData function into the lists which takes the currentCategory and then passes the filteredArray to the list.

We now have to update our DetailView_Previews struct again by determining a sample category.

struct DetailView : View {
    
    var currentCategory: Categories
    
    var body: some View {
        List(filterData(by: currentCategory)) { food in
            DetailRow(food: food)
        }
    }
}

#if DEBUG
struct DetailView_Previews : PreviewProvider {
    static var previews: some View {
        DetailView(currentCategory: .burger)
    }
}
#endif

Our preview should now look as follows:

Next, we have to connect the ContentView and the DetailView and pass the category selected by the user.

Connecting the views ⛓

To enable a connection from the ContentView to the DetailView we have to wrap the List of our ContentView containing the different CategoryViews into a so-called NavigationView.

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

If we stack views into a NavigationView, like we stacked the List with its CategoryViews into a NavigationView, they become part of a navigation hierarchy which enables routing to other views like our DetailView.

“NavigationView: A view for presenting a stack of views representing a visible path in a navigation hierarchy.”

Apple

To enable routing to the DetailView by clicking on a CategoryView we have to wrap these into NavigationButtons. Let’s start by wrapping the “pizza” CategoryView into a NavigationButton. The destination of this NavigationButton is the DetailView. Because we are currently handling the “burger” CategoryView we want to initialise the DetailView with the .burger case of our Categories enum.

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

Let’s repeat this process for the three remaining CategoryViews.


struct ContentView : View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: DetailView(currentCategory: .burger)) {
                    CategoryView(imageName: "burger", categoryName: "BURGER")
                }
                NavigationLink(destination: DetailView(currentCategory: .pizza)) {
                    CategoryView(imageName: "pizza", categoryName: "PIZZA")
                }
                NavigationLink(destination: DetailView(currentCategory: .pasta)) {
                    CategoryView(imageName: "pasta", categoryName: "PASTA")
                }
                NavigationLink(destination: DetailView(currentCategory: .dessert)) {
                    CategoryView(imageName: "cake", categoryName: "DESSERTS")
                }
            }
        }
    }
}

We can now try out the functionality of our app by running the preview simulator in live mode. To do this, click on the play button next to the simulator. Great! We can now click on the different categories and a detailed list with all products of the selected category gets presented to us!

Handling the Navigation Bars 👁

Currently the Navigation Bar in our ContentView is empty. To change this add a .navigationBarTitle modifier to the List (not to the Navigation View!). We can now insert a Text object containing “Food Delivery”


struct ContentView : View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: DetailView(currentCategory: .burger)) {
                    CategoryView(imageName: "burger", categoryName: "BURGER")
                }
                NavigationLink(destination: DetailView(currentCategory: .pizza)) {
                    CategoryView(imageName: "pizza", categoryName: "PIZZA")
                }
                NavigationLink(destination: DetailView(currentCategory: .pasta)) {
                    CategoryView(imageName: "pasta", categoryName: "PASTA")
                }
                NavigationLink(destination: DetailView(currentCategory: .dessert)) {
                    CategoryView(imageName: "cake", categoryName: "DESSERTS")
                }
            }
                .navigationBarTitle(Text("Food Delivery"))
        }
    }
}

We also want a Navigation Bar title for our DetailView. But it should not display a static text, but instead the name of category the user selected. To do this we need to write a function that returns a appropriate string to us depending on the 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 .dessert:
        return "Desserts"
    }
}

We can now add a .navigationBarTitle modifier to the List of our DetailView and insert our created method as the Text object’s input. To make to Navigation Bar to be small we can use the .inline option as the .displayMode.


struct DetailView : View {
    
    var currentCategory: Categories
    
    var body: some View {
        List(filterData(by: currentCategory)) { food in
            DetailRow(food: food)
        }
            .navigationBarTitle(Text(categoryString(for: currentCategory)), displayMode: .inline)
    }
}

You can find the full source code on GitHub!

Conclusion 🎊

Perfect, let’s run our app again, but this time in the “normal” simulator! Everything works fine: we can select each category and get a list of all of the products of that category.

We are now finished with creating our Food Delivery app in SwiftUI! We learned a lot stuff like creating lists with custom rows, using data models to feed our lists and how to transfer data between different views.

I hope you enjoyed this tutorial. Make sure you subscribe to our newsletter to not miss any updates, a lot of stuff about SwiftUI is coming. Also visit us on Facebook and Instagram. If you have any questions do not hesitate to contact us or leave a comment below!

Categories
Uncategorized

SwiftUI – Mastering Table Views (Lists) #1

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.

Categories
Uncategorized

Everything you need to know about SwiftUI to get started

Boom .. here is it .. this thing Apple announced at the WWDC2019 and the reason why the whole iOS development community is going mad.

If you are into iOS development, you probably heard about SwiftUI in the last days. Everybody is writing about it and everyone is really excited. But at this very moment there are not many resources about SwiftUI und many are confused by it. But be sure, after reading this guide, you’ll know exactly what SwiftUI is about and how to get started using it.

What is SwiftUI?! 🤯

First of all, let’s talk about what SwiftUI is and what it is used for. Apples describes its new innovation as follows:

“SwiftUI is an innovative, exceptionally simple way to build user interfaces across all Apple platforms with the power of Swift.”

Apple

So basically, SwiftUI is a completly new way of building user interfaces for apps. Until now the most common way to build the UI in Xcode was to use storyboards and XIB files. With SwiftUI, Apple provides an alternative to these.

With SwiftUI you can build your app by using a new declarative syntax. With this you describe how your interface should look like and what it should do.

With the declarative syntax you describe in code how your view should look like

You see that the syntax really differs from what you saw before in iOS programming. This is because it follows a declarative approach instead of an imperative one. Imperative programming was the common programming approach. But what’s the difference? In a nutshell: With imperative programming we write how we react when the state of our app changes, and accordingly execute code. With declaraive programming instead, we describe the interface of our app for all possible states in advance. Here’s a great article if you want to dive deeper.

In addition to a more simple and intuitive syntax, with SwiftUI Xcode finally serves us with a live preview while building the app’s UI. Everything we write will be instantly be shown our live preview which saves us much time compared to building the app and running it in the simulator every time.



Not only do we have a live preview with SwiftUI while writing our code, we can even edit our interface in the preview, which affects our code immediatly. This link between code and preview makes programming with Swift and Xcode even easier and more intuitive.



There are some more advantages coming with SwiftUI:

  • Connection similar to outlets and actions you know from using storyboards are permanently checked by the compiler. So no runtime errors anymore due to lost connections!
  • SwiftUI makes it easy to develop for mutiple platforms like iOS, macOS, watchOS etc.

Are storyboards dead? 😵

To keep it short: Storyboards aren’t dead. As the past has shown, Apple isn’t throwing away established concepts overnight. A comparison: Meanwhile it is 5 years ago that Swift replaced Objective-C as the main programming language for iOS development. Nevertheless, iOS apps can still be written in Objective-C without any problems. What we can assume, however, is that Apple will focus strongly on interface development with SwiftUI in the future. So although it will take time to establish (SwiftUI will be officially released together with Xcode 11 this fall), we recommend you to get in touch with it.

The same applies to UIKit, including UIViewControllers. With SwiftUI it’s still possible to include the different objects of the UIKit and to integrate them into SwiftUI.

“SwiftUI works seamlessly with the existing UI frameworks on all Apple platforms. For example, you can place UIKit views and view controllers inside SwiftUI views, and vice versa.”

Apple

How do I run SwiftUI? 🏃‍♀️

SwiftUI comes with Xcode 11 which will be published this fall. However, SwiftUI is already available as part of the beta version of Xcode 11.

Currently, there are two ways to run SwiftUI:

1: Mac OS XY (Catalina) beta + Xcode 11 beta

In order to use SwiftUI properly, you need to run the Xcode 11 beta on a Mac with MacOS Catalina, which is currently only available as a beta version too.

Here’s a great tutorial on how to install MacOS Catalina beta and Xcode 11 beta!

2: MacOS Mojave + Xcode 11 beta (limited possibilties)

If you don’t want to switch to Catalina beta, you can still explore SwiftUI in Xcode 11 running on a Mac with Mojave.

This option does not offer you all the features of SwiftUI, but lets you try out the basic concepts.

Here’s a great video on how to achieve this!

How do I start learning SwiftUI? 📚🤓

Now you are probably excited and want to get started with SwiftUI right away. Here are some resources to help you explore and learn SwiftUI.

Conclusion 🎊

Hopefully, you enjoyed reading this guide and now know about SwiftUI and how to get started.

Make sure you stay tuned and sign-up to the newsletter.