SwiftUI: adapting UI to different screen sizes (with code examples)

Alla Dubovska
3 min readMay 29, 2024

--

Adapting your app to different screen sizes is essential to provide a great user experience across all devices. SwiftUI provides various tools and techniques to achieve this. This post lists some examples to help you adapt your UI.

Photo by Oliur on Unsplash

1. Using @Environment to get device traits

SwiftUI’s @Environment property wrapper can be used to get information about the current environment, such as the horizontal and vertical size classes.

import SwiftUI

struct ContentView: View {
enum Constants {
static let threeColumnLayout = [GridItem(.flexible(), spacing: 16),
GridItem(.flexible(), spacing: 16),
GridItem(.flexible(), spacing: 0)]
static let oneColumnLayout = [GridItem(.flexible(), spacing: 0)]
}

@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?

@State var gridLayout = Constants.oneColumnLayout

let items = Array(0...50).map { "\($0)"}

var body: some View {
ScrollView {
LazyVGrid(columns: gridLayout) {
ForEach(items, id: \.self) { item in
Text("Cell title № \(item)")
.padding(16)
.frame(maxWidth: .infinity)
.background(.mint)
}
}
}
.onChange(of: horizontalSizeClass) { _, newValue in
setGridLayout(for: newValue)
}
.onAppear {
setGridLayout(for: horizontalSizeClass)
}
}

private func setGridLayout(for sizeClass: UserInterfaceSizeClass?) {
gridLayout = sizeClass == .regular ? Constants.threeColumnLayout : Constants.oneColumnLayout
}
}

This example shows how you can display 1 or 3 columns depending on screen width.

Example 1 preview on iPad and iPhone

2. Using GeometryReader and conditional views/modifiers

GeometryReader is a powerful view that gives you access to the size and position of its parent view. You can use it with conditional statements to adapt your UI based on available space.

import SwiftUI

struct ItemView: View {
let textToDisplay: String

var body: some View {
HStack {
GeometryReader { geometry in
HStack {
Text("Cell title № \(textToDisplay)")
Spacer()
Button("Button 1") { }
.padding(8)
.background(.white)
if geometry.size.width > 400 {
Button("Button 2") { }
.padding(8)
.background(.white)
}
}
.padding(16)
}
}
.frame(maxWidth: .infinity)
.background(.mint)
}
}
Example №2 preview on iPad and iPhone

3. Adaptive Grid Layout

Similar to the first example. This approach prefers to insert as many items of the `minimum` size as possible. Personally, I prefer the first approach because we usually want to control the number of columns, but of course, it depends on your requirements.

import SwiftUI

struct ContentView: View {
let items = Array(0...50).map { "\($0)"}

var body: some View {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 60))]) {
ForEach(items, id: \.self) { item in
Text("\(item)")
.padding(16)
.frame(maxWidth: .infinity)
.background(.purple)
}
}
}
}
}
Example №3 preview on iPhone

Of course, there are other modifiers — LayoutPriority , Spacer , AspectRatio that serve the same goal. By leveraging all these techniques, you can create a flexible and adaptive UI that looks great on all devices, from small iPhones to large iPads and beyond.

Source code can be found here — https://github.com/alla-dubovska/AdaptiveSwiftUIExamples

--

--

Alla Dubovska

Software engineer (👩‍💻 native iOS development), Mom 👦, Marathon finisher 🏃🏻‍♀️