In this installment of our SwiftUI tutorial series, we’ll build out our Categories view.
Posts in this series:
- Part 1: Project Setup
 - Part 2: Category Card View
 - Part 3: Categories List
 - Part 4: Dynamic Categories & Navigation
 - Part 5: Profile View
 
Here’s what our prototype will look like at the end of this section.

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.
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.
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.

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.
struct Categories: View {
    var body: some View {
        ScrollView {
            VStack {
                CategoryRow()
                CategoryRow()
                CategoryRow()
                CategoryRow()
            }
            .padding()
        }
    }
}
We should now have a scrollable list of Category Cards.

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.
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.
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.
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.
struct CategoryRow: View {
    let geometry: GeometryProxy
    var body: some View {
        HStack {
            CategoryCard(geometry: geometry) // Pass geometry to CategoryCard
            CategoryCard(geometry: geometry)
        }
    }
}
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.
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)

Nice! Now we have a list of Category Cards. See you for Part Four, in which we’ll make our categories list dynamic and add some navigation.