Skip to content
DeveloperMemos

Building Adaptive Layouts with ViewThatFits in SwiftUI

SwiftUI, iOS Development, Layout2 min read

One of the biggest challenges in mobile development is creating layouts that look good on all screen sizes. From the compact iPhone SE to the expansive iPad Pro, your UI needs to adapt gracefully. In the past, we often relied on GeometryReader to measure available space and make layout decisions, but that approach can be verbose and performance-heavy.

With iOS 16, Apple introduced ViewThatFits, a powerful layout container that simplifies this process significantly. In this article, we'll explore how to use ViewThatFits to build flexible and adaptive interfaces.

What is ViewThatFits?

ViewThatFits is a container view that evaluates its child views in the order you provide them. It selects the first child that fits within the available space on the specified axes. If the first view is too large, it moves to the second, and so on. If none of the views fit, it falls back to the last view provided.

This behavior makes it perfect for scenarios where you want to display a detailed view when space permits, but switch to a more compact representation when space is limited.

Basic Usage

Let's look at a simple example. Imagine you have a text label that you want to display on a single line if possible, but if the text is too long, you want to show a shorter version instead of truncating it.

1import SwiftUI
2
3struct AdaptiveTextLabel: View {
4 var body: some View {
5 ViewThatFits(in: .horizontal) {
6 Text("This is a very long text that might not fit on a small screen.")
7 .lineLimit(1)
8
9 Text("Short version")
10 .lineLimit(1)
11 }
12 .padding()
13 }
14}

In this example, ViewThatFits checks if the long text fits horizontally. If the screen is wide enough, the user sees the full sentence. If the screen is too narrow, the container automatically switches to the "Short version".

Practical Example: Adaptive Button Layouts

A common UI pattern involves displaying a list of actions. On wide screens, you might want these actions to be laid out horizontally in a row. However, on narrower screens, that row might cause overflow or truncation. A vertical stack is often a better choice in those cases.

ViewThatFits makes implementing this pattern trivial:

1struct ActionButtons: View {
2 var body: some View {
3 ViewThatFits(in: .horizontal) {
4 // Preferred layout: Horizontal
5 HStack(spacing: 20) {
6 actionButton(title: "Save Changes", icon: "checkmark")
7 actionButton(title: "Discard Draft", icon: "trash")
8 actionButton(title: "Share", icon: "square.and.arrow.up")
9 }
10
11 // Fallback layout: Vertical
12 VStack(spacing: 12) {
13 actionButton(title: "Save Changes", icon: "checkmark")
14 actionButton(title: "Discard Draft", icon: "trash")
15 actionButton(title: "Share", icon: "square.and.arrow.up")
16 }
17 }
18 .padding()
19 }
20
21 func actionButton(title: String, icon: String) -> some View {
22 Button(action: {}) {
23 Label(title, systemImage: icon)
24 .frame(maxWidth: .infinity)
25 .padding()
26 .background(Color.blue.opacity(0.1))
27 .cornerRadius(8)
28 }
29 }
30}

Here, the container first attempts to render the HStack. If the buttons are too wide for the screen, it seamlessly switches to the VStack. This eliminates the need for complex GeometryReader calculations or size classes for this specific use case.

ScrollView Fallback

Another powerful use case is deciding whether to wrap content in a ScrollView. Sometimes content fits perfectly on a large device but requires scrolling on a smaller one. You can use ViewThatFits to conditionally apply scrolling.

1struct AdaptiveContent: View {
2 var body: some View {
3 ViewThatFits(in: .vertical) {
4 // 1. Try to show content as is (no scroll)
5 VStack {
6 ContentBody()
7 }
8
9 // 2. If it doesn't fit vertically, wrap in ScrollView
10 ScrollView {
11 VStack {
12 ContentBody()
13 }
14 }
15 }
16 }
17}

This ensures that users on large screens don't see unnecessary scroll indicators, while users on small screens can still access all the content.

Conclusion

ViewThatFits is a fantastic addition to the SwiftUI layout system. It allows developers to think declaratively about layout adaptation: "Try this first, and if it doesn't fit, do this instead."

By using ViewThatFits, you can create more robust and polished UIs that respect the user's device size and dynamic type settings without writing imperative layout code. It's a tool that every SwiftUI developer should have in their toolkit.