SwiftUI: adapting UI to different screen sizes (with code examples)
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.
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.
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)
}
}
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)
}
}
}
}
}
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