— SwiftUI, iOS Development, Layout — 2 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.
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.
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 SwiftUI2
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".
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: Horizontal5 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: Vertical12 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.
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 ScrollView10 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.
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.