NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

How to create an adaptive layout with ViewThatFits

Paul Hudson    @twostraws   

Updated for Xcode 14.2

New in iOS 16

SwiftUI gives us ViewThatFits so that we can have it select from one of several possible layouts based on what fits into the available screen space. This makes it a fantastic way to make sure your app looks great from the largest tvOS screen down to the smallest Apple Watch.

In its simplest form, you should list all the layout alternatives you want from most preferred to least preferred, and SwiftUI will try them all until it finds one that fits into the space:

ViewThatFits {
    Label("Welcome to AwesomeApp", systemImage: "bolt.shield")
        .font(.largeTitle)

    Label("Welcome", systemImage: "bolt.shield")
        .font(.largeTitle)

    Label("Welcome", systemImage: "bolt.shield")
}

Download this as an Xcode project

That attempts a long title in large text, a short title in large text, then finally a short title in small text – SwiftUI will try them in that order, and stop as soon as one fits into the available space.

This is particularly useful when you’re working with views that can be arranged vertically or horizontally depending on space. For example, this creates a view with four different buttons, then decides to arrange them horizontally or vertically depending on how much space there is:

struct OptionsView: View {
    var body: some View {
        Button("Log in") { }
            .buttonStyle(.borderedProminent)

        Button("Create Account") { }
            .buttonStyle(.bordered)

        Button("Settings") { }
            .buttonStyle(.bordered)

        Spacer().frame(width: 50, height: 50)

        Button("Need Help?") { }
    }
}

struct ContentView: View {
    var body: some View {
        ViewThatFits {
            HStack(content: OptionsView.init)
            VStack(content: OptionsView.init)
        }
    }
}

Download this as an Xcode project

Where things get more interesting is how ViewThatFits handles handles text layout. Text in SwiftUI prefers to sit on one line, and by default ViewThatFits will prefer to avoid layouts the cause text to wrap. So, when space is limited code like this will default to a VStack rather than use a HStack with wrapping text:

ViewThatFits {
    HStack {
        Text("The rain")
        Text("in Spain")
        Text("falls mainly")
        Text("on the Spaniards")
    }

    VStack {
        Text("The rain")
        Text("in Spain")
        Text("falls mainly")
        Text("on the Spaniards")
    }
}
.font(.title)

Download this as an Xcode project

What’s happening here is that ViewThatFits is measuring our text both horizontally and vertically, and is trying to find something that fits the text in both those dimensions – something where the text fits all on one line, without being truncated vertically.

This sometimes causes problems, but fortunately we can tell ViewThatFits to care about only one dimension so that we can get more control.

For example, say you wanted to display some terms and conditions to the user, and have it as fixed text if it can be fitted into the space, but scrolling text otherwise. This kind of code won’t work as you expect:

struct ContentView: View {
    let terms = String(repeating: "abcde ", count: 100)

    var body: some View {
        ViewThatFits {
            Text(terms)

            ScrollView {
                Text(terms)
            }
        }
    }
}

Download this as an Xcode project

Unless you have a huge screen, that will always choose the scrolling version because we asked ViewThatFits to care about both horizontal and vertical axes for our text. This means as soon as the text runs across more than one line, SwiftUI will prefer to avoid that layout.

To fix this we need to restrict ViewThatFits to measure only the vertical axis, like this:

struct ContentView: View {
    let terms = String(repeating: "abcde ", count: 100)

    var body: some View {
        ViewThatFits(in: .vertical) {
            Text(terms)

            ScrollView {
                Text(terms)
            }
        }
    }
}

Download this as an Xcode project

Now that will allow the text to wrap horizontally, but as soon as it runs out of vertical space SwiftUI will move on to the ScrollView.

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Similar solutions…

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.