---
title: 'SwiftUI Prototype Tutorial 3 of 5: Categories List'
teaser: 'In this installment of our SwiftUI tutorial series, we''ll build out our
  Categories view.

  '
tags: swift,swiftui,ios,design
author: Devin Jameson
published_on: 2020-07-09
---

In this installment of our SwiftUI tutorial series, we'll build out our
Categories view.

**Posts in this series:**

- [Part 1: Project Setup](https://thoughtbot.com/blog/swiftui-prototype-tutorial-1-of-5-project-setup)
- [Part 2: Category Card View](https://thoughtbot.com/blog/swiftui-prototype-tutorial-2-of-5-category-card-view)
- [Part 3: Categories List](https://thoughtbot.com/blog/swiftui-prototype-tutorial-3-of-5-categories-list)
- [Part 4: Dynamic Categories & Navigation](https://thoughtbot.com/blog/swiftui-prototype-tutorial-4-of-5-dynamic-categories-and-navigation)
- [Part 5: Profile View](https://thoughtbot.com/blog/swiftui-prototype-tutorial-5-of-5-profile-view)

Here's what our prototype will look like at the end of this section.

![An iPhone 11 Pro max running our prototype. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The categories tab is blue and the profile tab is black. Above the bottom tab bar, there are six identical images of a foggy New York City taken from above a road, looking down it. There are deep green trees on either side of the road. The images are rectangular, with more height than width, and they are arranged in two columns and three rows. The image has rounded corners and, overlayed in the bottom right corner is the text "Business" in white.](https://images.thoughtbot.com/blog-vellum-image-uploads/CHUBNlKRhiYo6UEP55qO_4.jpg)

Now let's get down to business.

:)

To get started, we'll create a view that contains each row of categories and
call it `CategoryRow`. Inside this view, we'll include two instances of
`CategoryCard` within an `HStack`.

```swift
struct CategoryRow: View {
    var body: some View {
        HStack { // Like ZStack, but positions views horizontally
            CategoryCard()
            CategoryCard()
        }
    }
}
```

Now we can use our `CategoryRow` view in our Categories view. Let's add four
instances of it within a `VStack`.

```swift
struct Categories: View {
    var body: some View {
        VStack { // Like HStack, but positions views vertically
            CategoryRow()
            CategoryRow()
            CategoryRow()
            CategoryRow()
        }
    }
}
```

Now we should have four rows of Category Cards visible.

![An iPhone 11 Pro max running our prototype. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The categories tab is blue and the profile tab is black. Above the bottom tab bar, there are six identical images of a foggy New York City taken from above a road, looking down it. There are deep green trees on either side of the road. The images are square and they are arranged in two columns and three rows. The image has rounded corners and, overlayed in the bottom right corner is the text "Business" in white. When compared to the previous image, there is less white space above the set of images.](https://images.thoughtbot.com/blog-vellum-image-uploads/gCUG6PilROuKn0VQG8fD_2.jpg)

We want to be able to scroll through this list, so let's put our `VStack` within
a `ScrollView`. While we're at it, we can add some default padding to our
`VStack` to give our content some room to breathe.

```swift
struct Categories: View {
    var body: some View {
        ScrollView {
            VStack {
                CategoryRow()
                CategoryRow()
                CategoryRow()
                CategoryRow()
            }
            .padding()
        }
    }
}
```

We should now have a scrollable list of Category Cards.

![An iPhone 11 Pro max running our prototype. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The categories tab is blue and the profile tab is black. Above the bottom tab bar, there are six identical images of a foggy New York City taken from above a road, looking down it. There are deep green trees on either side of the road. The images are arranged in two columns and three rows. The image has rounded corners and, overlayed in the bottom right corner is the text "Business" in white. When compared to the previous image, in this image there is more white space above the set of images.](https://images.thoughtbot.com/blog-vellum-image-uploads/MQHi9RIjSgCtAfl2KDuV_3.jpg)

This is looking great already, but we want each Category Card to have a
rectangular shape. You might think we could just change the hardcoded height and
width arguments in the frame method on our Image. However, this solution
wouldn't account for various device sizes.

In this case, we want our width and height values to be dynamic, so we'll use
`GeometryReader`. `GeometryReader` is a container view which provides access to
its own size and coordinate space. We'll use a fraction of the width component
of this size to set the width and height of each Category Card.

We also we need to instantiate `GeometryReader` *outside* our `ScrollView` or it
won't behave properly.

Let's start by doing that.

```swift
struct Categories: View {
    var body: some View {
        GeometryReader { geometry in // Add GeometryReader
            ScrollView {
                VStack {
                    CategoryRow()
                    CategoryRow()
                    CategoryRow()
                }
                .padding()
            }
        }
    }
}
```

Great! Now we have access to our screen size in our `Categories` view via
`geometry`. Let's pass `geometry` down to our  `CategoryRow` view.

```swift
struct Categories: View {
    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                VStack {
                    CategoryRow(geometry: geometry) // Pass geometry to CategoryRow
                    CategoryRow(geometry: geometry)
                    CategoryRow(geometry: geometry)
                    CategoryRow(geometry: geometry)
                }
                .padding()
            }
        }
    }
}
```

Since CategoryRow doesn't take any arguments yet, we'll have to add a parameter
`geometry` of type `GeometryProxy`.

```swift
struct CategoryRow: View {
    let geometry: GeometryProxy // Add geometry parameter to CategoryRow
    
    var body: some View {
        HStack {
            CategoryCard()
            CategoryCard()
        }
    }
}
```

Now we have access to `geometry` in CategoryRow. We'll repeat this process to
pass `geometry` down to our CategoryCard.

```swift
struct CategoryRow: View {
    let geometry: GeometryProxy

    var body: some View {
        HStack {
            CategoryCard(geometry: geometry) // Pass geometry to CategoryCard
            CategoryCard(geometry: geometry)
        }
    }
}
```

```swift
struct CategoryCard: View {
    let geometry: GeometryProxy // Add geometry parameter to CategoryCard

    var body: some View {
        ZStack(alignment: .bottomTrailing) {
            Image("business")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 200, height: 200)

```

Now, we can finally use `geometry` in place of our static values for our frame
width and height.

```swift
Image("business")
    .resizable()
    .aspectRatio(contentMode: .fill)
    .frame(
        width: geometry.size.width * 0.45, // Set width and height to a fraction of view width
        geometry.size.width * 0.55)
```

![An iPhone 11 Pro max running our prototype. On the prototype is a bottom tab bar with two options: Categories and Profile. Above the categories tab label is a globe icon, and above the profile tab label is a person icon. The categories tab is blue and the profile tab is black. Above the bottom tab bar, there are six identical images of a foggy New York City taken from above a road, looking down it. There are deep green trees on either side of the road. The images are rectangular, with more height than width, and they are arranged in two columns and three rows. The image has rounded corners and, overlayed in the bottom right corner is the text "Business" in white.](https://images.thoughtbot.com/blog-vellum-image-uploads/CHUBNlKRhiYo6UEP55qO_4.jpg)

Nice! Now we have a list of Category Cards. See you for
[Part Four](https://thoughtbot.com/blog/swiftui-prototype-tutorial-4-of-5-dynamic-categories-and-navigation),
in which we'll make our categories list dynamic and add some navigation.
