NavigationSplitView two and three column interfaces in the same app

I am currently learning to create multiplatform applications using SwiftUI and NavigationSplitView, and I faced the problem of arranging different Views in the same App.

Let's open the default Notes application, and here, we can see a switch between two and three-column views of the content.

So my question is, how to arrange this kind of view for different App pages using NavigationSplitView?

  • First page has two columns interface
  • Second has three columns

Answered by BabyJ in 723300022

From the way Apple made the NavigationSplitView API, it is clear that having two and three column layouts shouldn't happen or should be avoided.


Nevertheless, you can still achieve this through the use of two split views wrapped in a condition. Something like this, which I have tested, will work:

struct ContentView: View {
    @State private var selectedInt: Int? = 1
    @State private var columnVisibility: NavigationSplitViewVisibility = .all

    var sidebarList: some View {
        List(1...10, id: \.self, selection: $selectedInt) { int in
            NavigationLink("Row \(int)", value: int)
        }
        .navigationTitle("Sidebar")
    }

    var contentView: some View {
        Text("Content \(selectedInt ?? 0)")
    }

    var detailView: some View {
        Text("Detail \(selectedInt ?? 0)")
    }

    var body: some View {
        Group {
            // Only rows that are a multiple of 2 will have a two-column layout
            if selectedInt?.isMultiple(of: 2) == true {
                NavigationSplitView(columnVisibility: $columnVisibility) {
                    sidebarList
                } detail: {
                    detailView
                }
            } else {
                NavigationSplitView(columnVisibility: $columnVisibility) {
                    sidebarList
                } content: {
                    contentView
                } detail: {
                    detailView
                }
            }
        }
        .navigationSplitViewStyle(.balanced)
    }
}


I don't know, however, what impact this has on performance or rendering, if any.

Accepted Answer

From the way Apple made the NavigationSplitView API, it is clear that having two and three column layouts shouldn't happen or should be avoided.


Nevertheless, you can still achieve this through the use of two split views wrapped in a condition. Something like this, which I have tested, will work:

struct ContentView: View {
    @State private var selectedInt: Int? = 1
    @State private var columnVisibility: NavigationSplitViewVisibility = .all

    var sidebarList: some View {
        List(1...10, id: \.self, selection: $selectedInt) { int in
            NavigationLink("Row \(int)", value: int)
        }
        .navigationTitle("Sidebar")
    }

    var contentView: some View {
        Text("Content \(selectedInt ?? 0)")
    }

    var detailView: some View {
        Text("Detail \(selectedInt ?? 0)")
    }

    var body: some View {
        Group {
            // Only rows that are a multiple of 2 will have a two-column layout
            if selectedInt?.isMultiple(of: 2) == true {
                NavigationSplitView(columnVisibility: $columnVisibility) {
                    sidebarList
                } detail: {
                    detailView
                }
            } else {
                NavigationSplitView(columnVisibility: $columnVisibility) {
                    sidebarList
                } content: {
                    contentView
                } detail: {
                    detailView
                }
            }
        }
        .navigationSplitViewStyle(.balanced)
    }
}


I don't know, however, what impact this has on performance or rendering, if any.

I work on a predominantly SwiftUI app, but I managed to achieve this using two UISplitViewControllers and contain my SwiftUI views inside that.

I basiccally made an outer two column UISplitViewController. The detail view there is a SwiftUI view that either shows one big view or a UIVIewRepresentable that houses another UISplitViewController, depending on sidebar selection. When that inner splitview is shown, I set the outer nav bar to hidden so just the inner splitview's navbar shows.

It took a lot of fiddling with the column behaviours to get it to work just right, but it does work. Although once in a while I see crashes when rotating the iPad which may be attributed to SwiftUI trying to deal with two navigation controllers in the two different splitviews.

I've also raised FB12586131 to ask that UISplitViewController and NavigationSplitView support proper switching between two and three column modes. All we really need is a way to hide the middle column only.

Also worth noting that Apple does do two to three column transitions in the Music app on iPad. If you open Music in Landscape mode you see two columns. But if you tap Artists in the sidebar you see three columns. So I suggest filing Feedback asking for official support of this behaviour.

NavigationSplitView two and three column interfaces in the same app
 
 
Q