PRO 30% Free • PayPal ($15) & Razorpay (₹899)
30%

Grids in SwiftUI

Top Course Staff · 8 min read · Mobile development · PRO · 30% Free Preview Available

Grids in SwiftUI

Introduction

The grid layout has become a familiar paradigm for mobile user experiences. The canonical example of grid layout is in the built-in camera roll app for iOS. It can also be found on an extremely popular social photo-sharing app in the feature called (unsurprisingly) the grid. It is an intuitive, efficient means for presenting large collections of views to the user.

Figure 0

Figure 0

Luckily, SwiftUI gives us a native structure for creating impressive grid layouts with only a few lines of expressive code. TheGridstructure is highly performant and provides helpful functionality so that your scrollable grid layout can adapt to various device sizes and orientations. Without further ado, let’s experience the power of grids by building some in SwiftUI!

Create the project

To explore the power and flexibility of theGridstructure, we’ll create an app that presents a calendar view. Let’s start by creating a new project inXcodecalled “GridCalendar”. We won’t need to use Core Data or include tests.

Figure 1

Figure 1

We should now be at the starting point of a standard SwiftUI project with the `ContentView.swift’ file open:

![Figure 2)

Creating the data structures for the calendar grid

We’re going to start by making a couple ofstructsthat will represent months and days of a year. We’ll populate each month with the correct number of days, and display it all in a grid view. Define theDaystructure by adding the following code underneath theContentViewstructure:

struct Day: Identifiable {    let id = UUID()    let value: Int}
Copy to clipboard
struct Day: Identifiable {    let id = UUID()    let value: Int}

Thevalueconstant will be used to store the order of each particular day of the month, i.e. the 2nd of June will have a value of 2. Since we’ll be using aForEachstructure to iterate through the days, we’ll need to assign a unique identifier to them. It may be tempting to use the value as the unique identifier, but this won’t work since we will be iterating through all of the months so there will be duplicated values across all of the months, i.e. there is an April 10th as well as an October 10th.

We make theDaystructure conform to theIdentifiableprotocol, which simply requires that it has anidvalue that is unique. We assignidby initializing aUUIDstructure, which is built into Foundation and guaranteed to be a unique value.

Next, we’ll define theMonthstructure by adding the following code underneath the day definition:

struct Month {    let name: String    let numberOfDays: Int    var days: [Day]        init(name: String, numberOfDays: Int) {        self.name = name        self.numberOfDays = numberOfDays        self.days = []                for n in 1...numberOfDays {            self.days.append(Day(value: n))        }            }}
Copy to clipboard
struct Month {    let name: String    let numberOfDays: Int    var days: [Day]        init(name: String, numberOfDays: Int) {        self.name = name        self.numberOfDays = numberOfDays        self.days = []                for n in 1...numberOfDays {            self.days.append(Day(value: n))        }            }}

We’ll want to display the name of each month in our calendar, so we’ll store that in thenameconstant. ThenumberOfDaysconstant will hold the correct number of days for each month. We’ll want to iterate over an array ofDaystructures, so we’ll store that in thedaysarray. When we initialize eachMonthstructure, we’ll generate thedaysarray on the fly based on thenumberOfDaysfor that particular month. So we create a custom initializer that assigns the correct values ofnameandnumberOfDaysto itself and initializesdayswith an empty array. Then we use a for loop to iterate over a range from 1 tonumberOfDays, initialize aDaystructure for each value in the range, and append that structure to thedaysarray.

Now that we have defined structures to represent the months and days of our calendar, we have laid the foundation to build an array that will represent the entire year. Add the following code underneath theMonthstructure:

let year = [        Month(name: "January", numberOfDays: 31),        Month(name: "February", numberOfDays: 28),        Month(name: "March", numberOfDays: 31),        Month(name: "April", numberOfDays: 30),        Month(name: "May", numberOfDays: 31),        Month(name: "June", numberOfDays: 30),        Month(name: "July", numberOfDays: 31),        Month(name: "August", numberOfDays: 31),        Month(name: "September", numberOfDays: 30),        Month(name: "October", numberOfDays: 31),        Month(name: "November", numberOfDays: 30),        Month(name: "December", numberOfDays: 31),    ]
Copy to clipboard
let year = [        Month(name: "January", numberOfDays: 31),        Month(name: "February", numberOfDays: 28),        Month(name: "March", numberOfDays: 31),        Month(name: "April", numberOfDays: 30),        Month(name: "May", numberOfDays: 31),        Month(name: "June", numberOfDays: 30),        Month(name: "July", numberOfDays: 31),        Month(name: "August", numberOfDays: 31),        Month(name: "September", numberOfDays: 30),        Month(name: "October", numberOfDays: 31),        Month(name: "November", numberOfDays: 30),        Month(name: "December", numberOfDays: 31),    ]

To build theyeararray we initialize aMonthstructure for each month in a year and pass in the correct number of days for each month.

Awesome, now we have structured data that will form the informational architecture of our calendar grid view!

IntroducingGridItemandLazyVGrid

In order to create our grid-based view, we’ll need to define an array ofGridIteminstances first. TheseGridIteminstances serve to describe the layout properties of the grid. We can use various types ofGridIteminstances to get the spacing and alignment of the columns or rows of the grid exactly how we wish. We can even use them to describe a grid that adjusts dynamically based on the size and orientation of the device. Truly the best way to grok them is to see them in action.

Let’s add a constant namedlayoutinside of ourContentViewstructure, right over thebodyvariable:

let layout = [        GridItem(.fixed(40))    ]
Copy to clipboard
let layout = [        GridItem(.fixed(40))    ]

Defining a singleGridItemlike this will create a single fixed column of width 40, when used as input for aLazyVGrid. As you may have suspected, theVinLazyVGridstands for vertical. When used in aLazyHGrid(a horizontal grid) it will create a single fixed row of height 40.

Now we’re finally ready to see our grid in action! Replace the contents of thebodyvar so that it looks like this:

var body: some View {        LazyVGrid(columns: layout) {            ForEach(year[0].days) { day in                Capsule()                    .overlay(Text("\(day.value)").foregroundColor(.white))                    .foregroundColor(.blue)                    .frame(height: 40)            }        }    }
Copy to clipboard
var body: some View {        LazyVGrid(columns: layout) {            ForEach(year[0].days) { day in                Capsule()                    .overlay(Text("\(day.value)").foregroundColor(.white))                    .foregroundColor(.blue)                    .frame(height: 40)            }        }    }

Let’s break down what’s going on in that code snippet. We pass thelayoutconstant we created in the earlier step as an argument to define the columns for ourLazyVGridstructure. Then we get the value of the 0 index of theyeararray, which is equal to theMonthof January. We iterate through thedaysproperty of thatMonthinstance, which is an array ofDayinstances that was created when we initialized thatMonth. For eachDay, we create aCapsulewith overlay text content that displays a string equal to thevalueproperty of theDay.

Figure 3

Figure 3

Who is lazy?

You might be curious why the grid view is namedLazyVGrid. Similar to the concept oflazy properties, theLazyinLazyVGridandLazyHGridrefers to the fact that the elements of the grid aren’t created until they are needed to display in the view. This is a smart strategy because in practice we might want to create grids with a large count of complex items. Loading thousands of images would cause the app to slow down. By using lazy loading, we create much smoother, more performant user experiences.

Make the grid scroll

As we expected, we have a vertical grid layout with one column of fixed width! However, there is one slight issue: we can’t see all of the content items of the grid. It would be great if there was a way to scroll the view. Luckily with SwiftUI, all we need to do is wrap ourLazyVGridwith aScrollView. Modify yourbodyvariable so it looks like this:

var body: some View {        ScrollView {            LazyVGrid(columns: layout) {                ForEach(year[0].days) { day in                    Capsule()                        .overlay(Text("\(day.value)").foregroundColor(.white))                        .foregroundColor(.blue)                        .frame(height: 40)                }            }        }    }
Copy to clipboard
var body: some View {        ScrollView {            LazyVGrid(columns: layout) {                ForEach(year[0].days) { day in                    Capsule()                        .overlay(Text("\(day.value)").foregroundColor(.white))                        .foregroundColor(.blue)                        .frame(height: 40)                }            }        }    }

If you’re using the live preview, you might have to click the play button to make the view respond to user interaction.

Figure 4

Figure 4

Aha! The view is free to scroll about the window 🕊

Exploring layout withGridItem

But you may be thinking, this isn’t really a grid, it’s more of a list. Let’s explore the power ofGridItemto create incredible, adaptable grid layouts! Change the layout constant so that we have four instances ofGridItemlike so:

let layout = [        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.fixed(40))    ]
Copy to clipboard
let layout = [        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.fixed(40))    ]

As you might expect, this gives us four columns of fixed width:

Figure 5

Figure 5

Now let’s change the lastGridItemto use theflexibleoption:

let layout = [        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.flexible(minimum:40))    ]
Copy to clipboard
let layout = [        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.flexible(minimum:40))    ]
Figure 6

Figure 6

The flexible option creates a column that takes up the available width it has. You must define a minimum width, and you can optionally specify a maximum width. Finally, there is also anadaptiveoption that fits multiple columns inside the width of a singleGridItem. This can be super helpful when designing layouts that adapt to phones and tablets of varying sizes and orientations. Modify thelayoutconstant so it looks like this:

let layout = [        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.adaptive(minimum: 80))    ]
Copy to clipboard
let layout = [        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.fixed(40)),        GridItem(.adaptive(minimum: 80))    ]
Figure 7

Figure 7

The adaptiveGridItemfills up the remaining width with three columns of at least width 80 in this case. But if we rotate the device to landscape orientation we can see the layout dynamically adapts:

Figure 8

Figure 8

Since we’re building a calendar view, let’s use seven flexible grid items like so:

let layout = [        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40))    ]
Copy to clipboard
let layout = [        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40)),        GridItem(.flexible(minimum: 40))    ]
Figure 9

Figure 9

Adding sections to the grid

So far we have been looking at the first month of the year, but in our calendar we’d like to view all of the months at the same time. We can use theSectionstructure to separate each month into a section with its own title. Modify yourbodyto look like this:

var body: some View {        ScrollView {            LazyVGrid(columns: layout) {                ForEach(year, id: \.name){ month in                    Section(header: Text(verbatim: month.name).font(.headline)) {                        ForEach(month.days) { day in                            Capsule()                                .overlay(Text("\(day.value)").foregroundColor(.white))                                .foregroundColor(.blue)                                .frame(height: 40)                        }                    }                }            }        }    }
Copy to clipboard
var body: some View {        ScrollView {            LazyVGrid(columns: layout) {                ForEach(year, id: \.name){ month in                    Section(header: Text(verbatim: month.name).font(.headline)) {                        ForEach(month.days) { day in                            Capsule()                                .overlay(Text("\(day.value)").foregroundColor(.white))                                .foregroundColor(.blue)                                .frame(height: 40)                        }                    }                }            }        }    }

We wrapped theForEachwe had with aSectionview that gives us a header view that we can use to display the name of the month. Then we wrapped theSectionwith anotherForEachthat iterates through all of the months of the year. Remember that we didn’t make theMonthstructure conform toIdentifiable, so we need to specify a unique key path in theForEachstructure. As long as we don’t have the same name of the month in our year, we can use it as the unique id.

Figure 10

Figure 10

Another useful feature of grids is the ability to pin views while scrolling. Modify the definition of yourLazyVGridlike so:

LazyVGrid(columns: layout, pinnedViews: [.sectionHeaders]) {
Copy to clipboard
LazyVGrid(columns: layout, pinnedViews: [.sectionHeaders]) {

Now you can see the section header view sticks to the top of the screen while you are scrolling through that particular section. This can help create a smooth, intuitive user experience.

Horizontal grid views

There may be situations where you want the grid to be laid out in horizontal rows instead of vertical columns. Changing this configuration is trivial with SwiftUI! Modify yourbodyvariable so it looks like this:

var body: some View {        ScrollView(.horizontal) {            LazyHGrid(rows: layout, pinnedViews: [.sectionHeaders]) {                ForEach(year, id: \.name){ month in                    Section(header: Text(verbatim: month.name).font(.headline)) {                        ForEach(month.days) { day in                            Capsule()                                .overlay(Text("\(day.value)").foregroundColor(.white))                                .foregroundColor(.blue)                                .frame(width: 40)                        }                    }                }            }        }    }
Copy to clipboard
var body: some View {        ScrollView(.horizontal) {            LazyHGrid(rows: layout, pinnedViews: [.sectionHeaders]) {                ForEach(year, id: \.name){ month in                    Section(header: Text(verbatim: month.name).font(.headline)) {                        ForEach(month.days) { day in                            Capsule()                                .overlay(Text("\(day.value)").foregroundColor(.white))                                .foregroundColor(.blue)                                .frame(width: 40)                        }                    }                }            }        }    }

We made four modifications:

  • We changed theScrollViewto the horizontal option
  • We changedLazyVGridtoLazyHGrid
  • We changedcolumns:torows:
  • We changed the.frameof theCapsuleto specify width instead of height

And voila, our grid is now laid out horizontally with seven flexible height rows:

Figure 11

Figure 11

In the case of our calendar, it definitely makes more sense to display it in a vertical grid. Let’s change it back to vertical:

var body: some View {        ScrollView {            LazyVGrid(columns: layout, pinnedViews: [.sectionHeaders]) {                ForEach(year, id: \.name){ month in                    Section(header: Text(verbatim: month.name).font(.headline)) {                        ForEach(month.days) { day in                            Capsule()                                .overlay(Text("\(day.value)").foregroundColor(.white))                                .foregroundColor(.blue)                                .frame(height: 40)                        }                    }                }            }        }    }
Copy to clipboard
var body: some View {        ScrollView {            LazyVGrid(columns: layout, pinnedViews: [.sectionHeaders]) {                ForEach(year, id: \.name){ month in                    Section(header: Text(verbatim: month.name).font(.headline)) {                        ForEach(month.days) { day in                            Capsule()                                .overlay(Text("\(day.value)").foregroundColor(.white))                                .foregroundColor(.blue)                                .frame(height: 40)                        }                    }                }            }        }    }
Figure 12

Figure 12

Conclusion

In this article, we explored the power and flexibility of theLazyVGridandLazyHGridstructures by building a simple calendar app. We saw how to useGridItemto generate highly customizable grid layouts. We learned how to make the grid scrollable and how to add pinnable sections to the view. You can download the complete code for the application at the linkhere.

As a challenge exercise, you might try to add functionality to the calendar to make it more realistic, such as labels for the days of the week (Sunday – Monday) and making the dates line up accurately with the labels given a specific year.

'The Codecademy Team, composed of experienced educators and tech experts, is dedicated to making tech skills accessible to all. We empower learners worldwide with expert-reviewed content that develops and enhances the technical skills needed to advance and succeed in their careers.'

30% Free Preview Reached · Unlock Remaining 70%

Complete Payment to Unlock Full Guide & Source Code

You are viewing the 30% preview. Join TopCourseAI Pro to unlock the remaining 70% of this article, unblur all code snippets, and gain full access to all 412 technical projects and guides.

✓ Full Access to All 160+ Projects
✓ Instant Blur Removal Everywhere
✓ Production Source Code & Blueprints
✓ All Future Deep Dives Included
Instant Unhindered Activation ·
End of Preview · Unlock Complete Blueprint & Source Code

Ready to access the complete step-by-step tutorial?

Gain full unhindered access to all 412 technical projects and guides, unblur all source code snippets, and download runnable scripts.

Step-by-Step Project Library

Ready to start building your next project?

Enjoyed this guide? Continue exploring our library of 412+ step-by-step guides, starter files, and practical projects across Web Development, Mobile Apps, AI, and Design.