LAST CHANCE: Save 50% on all my Swift books and bundles! >>

Working with two side by side views in SwiftUI

Paul Hudson    @twostraws   

You’re already familiar with the basic usage of NavigationStack, which allows us to create views like this one:

struct ContentView: View {
    var body: some View {
        NavigationView {
            Text("Hello, world!")
                .navigationTitle("Primary")
        }
    }
}

That works great on iPhone, but on iPad it's less than ideal – there we have much larger screens to deal with, and having one large view for navigation is a poor use of the space, and also creates quite a jarring experience when new views slide in all the way across.

In these situations, it's a better idea to use a different view for navigation, called NavigationSplitView. This lets us specify either two or three columns of data to show side by side, and iPadOS takes care of showing or hiding them at the right times depending on the exact configuration.

Try it out with this simple code:

NavigationSplitView {
    Text("Primary")
} detail: {
    Text("Content")
}

When you launch the app what you see once again depends on your device and orientation:

  • On iPhone you'll see Primary.
  • On iPad in landscape you'll see Primary in a sidebar along the leading edge of your device, and Content filling the rest of the screen.
  • On iPad in portrait mode you'll see Content filling the screen.

iPadOS does two very clever things here.

First, regardless of your device orientation you'll see a button that shows or hides the primary view, so users can choose what layout they want.

Second, when you activate multi-tasking – if you bring a Safari window alongside, for example – you'll see our app's views adapt so that the Primary view is hidden, because the system recognizes the amount of free space has decreased.

SwiftUI automatically links the primary and secondary views, which means if you have a NavigationLink in the primary view it will automatically load its content in the secondary view:

NavigationSplitView {
    NavigationLink("Primary") {
        Text("New view")
    }
} detail: {
    Text("Content")
}

There are some ways you can customize this split view's behavior.

For example, you can tell iOS to prefer to keep the primary view around when space is partially limited like this:

NavigationSplitView(columnVisibility: .constant(.all)) {
    NavigationLink("Primary") {
        Text("New view")
    }
} detail: {
    Text("Content")
        .navigationTitle("Content View")
}
.navigationSplitViewStyle(.balanced)

That requests that all columns be shown in a balance style, and the result is that iPads in portrait mode will now show the primary view.

Tip: columnVisibility is actually provided as a binding, so you could store your option in some state and update it dynamically.

Second, you can tell the system to prefer the detail view by default, which is helpful on iPhone where the primary view is selected as standard:

NavigationSplitView(preferredCompactColumn: .constant(.detail)) {

Again, you can use that with a binding, so you can change it as your app runs if you want.

Finally, although you can use .toolbar(.hidden, for: .navigationBar) to hide the toolbar in your detail view, be careful because it will hide the button to toggle the sidebar!

Tip: You can even add a third view to NavigationSplitView, which lets you create a sidebar. You’ll see these in apps such as Notes, where you can navigate up from from the list of notes to browse note folders. So, navigation links in the first view control the second view, and navigation links in the second view control the third view – it’s an extra level of organization for times when you need it.

Hacking with Swift is sponsored by Essential Developer.

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until July 28th.

Click to save your free spot now

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

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!

Average rating: 4.6/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.