If you’ve been in the Apple development ecosystem for a while, you surely remember the days of UIKit and the dreaded UICollectionView. Configuring a UICollectionViewFlowLayout, managing delegates, data sources, and manually calculating cell sizes was a task that consumed hours. With the arrival of declarative Swift programming, all of that has changed.
Today, as an iOS Developer, you have one of the most powerful and flexible tools in SwiftUI at your disposal: Grids.
In this tutorial, we will break down from scratch what a Grid in SwiftUI is, how its internal architecture works, and how you can implement it to create adaptable and complex interfaces on iOS, macOS, and watchOS using Xcode.
What is a Grid in SwiftUI?
In simple terms, a Grid is a container that organizes views in a two-dimensional layout: rows and columns. Unlike a VStack or HStack which align elements in a single line, a Grid allows for content distribution across multiple axes, making the most of screen space.
However, in SwiftUI, there isn’t just one type of Grid. Depending on your performance and design needs, you will choose between two main families:
- Lazy Grids (
LazyVGridandLazyHGrid): Introduced in iOS 14. They are “lazy”. They only render views that are visible on the screen. They are ideal for large data collections (photo galleries, product feeds). - Grid (Static Layout): Introduced in iOS 16. It loads all views at once. It works similarly to an HTML table or a spreadsheet. It is ideal for fixed structure designs, such as a calendar, a calculator, or a settings form.
Understanding this difference is vital for memory optimization in your application.
The Heart of the Layout: GridItem
Before writing a single line of code, we must understand the brain behind the operation: GridItem. A GridItem defines how a column (in a LazyVGrid) or a row (in a LazyHGrid) behaves. There are three types of sizes:
- .fixed(CGFloat): The most rigid. The size will be immutable regardless of the device.
- .flexible(minimum: CGFloat, maximum: CGFloat): Default behavior. It attempts to fill all available space by dividing it equally.
- .adaptive(minimum: CGFloat, maximum: CGFloat): The jewel for responsive design. SwiftUI will automatically calculate how many columns fit on the screen based on the minimum size you define.
Practical Tutorial: Creating Your First Gallery with LazyVGrid
Let’s open Xcode and get our hands dirty. We will create an image gallery that adapts to any device.
Step 1: Data Model Preparation
As a good iOS Developer, we always start with the data.
import SwiftUI
struct GridTutorialView: View {
// Generate test data: 50 elements
let data = (1...50).map { "Item \($0)" }
var body: some View {
// Implementation will go here
}
}
Step 2: Defining the Columns
This is where we configure the layout. We’ll start with a fixed 3-column design to understand the basics.
// Definition of 3 flexible columns
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
Step 3: Implementing the View
The LazyVGrid does not have its own scrolling. Therefore, it must almost always be wrapped in a ScrollView.
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(data, id: \.self) { item in
VStack {
Image(systemName: "photo")
.resizable()
.scaledToFit()
.frame(height: 50)
.foregroundColor(.white)
Text(item)
.font(.caption)
.foregroundColor(.white)
}
.frame(height: 100)
.frame(maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(10)
}
}
.padding() // External margin so it doesn't touch the edges
}
.navigationTitle("SwiftUI Gallery")
}
}
Running this in the Xcode simulator, you will see three perfect columns stretching to occupy the width.
Taking It to the Next Level: Adaptive Design (Responsive)
The code above has a problem: on an iPad, three columns will look huge. This is where the power of .adaptive comes in. Modify your column definition:
// We only need to define ONE GridItem, SwiftUI will repeat it automatically
let columns = [
GridItem(.adaptive(minimum: 100), spacing: 15)
]
What did we just do? We told SwiftUI: “Place as many columns as you can, as long as each cell is at least 100 points wide.” This converts your application into a top-tier native experience for both iOS and macOS.
LazyHGrid: Horizontal Scroll
Sometimes, vertical layout isn’t what we’re looking for (think of the Netflix interface). For this, we use LazyHGrid. The logic is inverted: we define rows instead of columns.
struct HorizontalGridExample: View {
let rows = [
GridItem(.fixed(150)), // Row 1: Fixed height
GridItem(.fixed(150)) // Row 2: Fixed height
]
var body: some View {
ScrollView(.horizontal) {
LazyHGrid(rows: rows, spacing: 20) {
ForEach(0..<20) { index in
RoundedRectangle(cornerRadius: 15)
.fill(Color.orange)
.frame(width: 200) // Fixed width for each card
.overlay(Text("Card \(index)"))
}
}
.padding()
}
}
}
Sections and “Sticky Headers”
A highly requested feature in professional applications is having headers that stick to the top while scrolling. The Grid in SwiftUI makes this incredibly easy with the pinnedViews parameter.
LazyVGrid(
columns: columns,
spacing: 20,
pinnedViews: [.sectionHeaders] // <--- The magic happens here
) {
Section(header: HeaderView(title: "Favorites")) {
ForEach(favorites) { item in
ItemView(item: item)
}
}
Section(header: HeaderView(title: "All Items")) {
ForEach(allData) { item in
ItemView(item: item)
}
}
}
Grid API (iOS 16+): The Power of Static Layout
Imagine you are designing a profile form in your app. You don’t have 500 elements, you have 4 or 5 fixed fields. For this, Apple introduced Grid and GridRow in Xcode 14.
Grid(alignment: .leading, horizontalSpacing: 20, verticalSpacing: 10) {
GridRow {
Text("Name").bold()
TextField("Your name", text: $name)
}
Divider()
GridRow {
Text("Email").bold()
TextField("hello@example.com", text: $email)
}
GridRow {
Text("Notifications").bold()
Toggle("", isOn: $notifications)
}
}
You can also use gridCellColumns to make a cell span multiple columns, similar to HTML’s “colspan”:
GridRow {
Text("Save Changes")
.frame(maxWidth: .infinity)
.background(Color.blue)
.gridCellColumns(2) // Occupies the 2 columns of the grid
}
Cross-Platform Considerations
As a modern iOS Developer, your code must be versatile:
- macOS: Use
.adaptivewhenever possible to support window resizing. - watchOS: A
LazyVGridwithGridItem(.adaptive(minimum: 50))will automatically create a honeycomb layout perfect for the watch.
Conclusion
The Grid in SwiftUI has matured to become an indispensable tool. It is no longer necessary to struggle with complex layout math. Quick summary:
- Lots of data (infinite scroll)? Use LazyVGrid.
- Responsive design? Use GridItem(.adaptive).
- Fixed structure (forms)? Use Grid (iOS 16+).
If you have any questions about this article, please contact me and I will be happy to help you . You can contact me on my X profile or on my Instagram profile.