What's the Equivalent of the Uisplitviewcontroller in Swiftui

How to run the split view on iPad using SwiftUI?

So this is how you would setup your iPhone, iPad and mac applications following the Apple Fruta app example using Xcode12, for iOS14 and macOS11 as a minimum target.

By using this SwiftUI implementation, you will have the split view that the iPad is using as well as for the mac. Regarding the iPhone, you will have the classic tab bar.

This is a ready to use code, so if you don't need the mac app, just delete the part between the #else and #end. I implemented it in case other people would find it handy as it is a perfect fit for a multiplatform app project. The code between #if os(iOS) and #else is for iPhone and iPad.

Look up for the comments I add in the code that explain at which point the split view is being made.

The ContentView that holds the navigation type depending on the device:

struct ContentView: View {

#if os(iOS)
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
#endif

@ViewBuilder
var body: some View {
#if os(iOS)
if horizontalSizeClass == .compact {
TabBarNavigationView() // For iPhone
}
else {
SidebarNavigationView() // For iPad
}
#else
SidebarNavigationView() // For mac
.frame(minWidth: 900, maxWidth: .infinity, minHeight: 500, maxHeight: .infinity)
#endif
}
}

Then, declare the classic iPhone tab bar with an enumeration of your tabs
(The HomeView can be replace by any of your SwiftUI views):

enum TabItem {
case home
}

struct TabBarNavigationView: View {

@State private var selection: TabItem = .home

var body: some View {
TabView(selection: $selection) {

NavigationView {
HomeView() // Create a SwiftUI HomeView or add your view
}
.tabItem {
Image(systemName: "house")
.font(.headline)
.imageScale(.medium) }
.tag(TabItem.home)
}
}
}

And this is the view that will hold the iPad and mac classic split view. When having the iPad on portrait mode, your views will be as navigations and when adding it on landscape, your views will be split.

struct SidebarNavigationView: View {

@SceneStorage("selection")
var selection: String?

var content: some View {
List(selection: $selection) {
NavigationLink(destination: HomeView()) {
Label(title: { Text("Home") },
icon: { Image(systemName: "house")
.font(.headline)
.imageScale(.medium) })
}
.tag(NavigationItem.home)
}
.listStyle(SidebarListStyle())
}

var body: some View {
NavigationView {
#if os(iOS)
content
#else
content
.frame(minWidth: 200, idealWidth: 200, maxWidth: 200, maxHeight: .infinity)
.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: toggleSidebar ) {
Image(systemName: "sidebar.left")
.foregroundColor(.blue)
}
}
}
#endif

// This is the part where the magic happens for the split view.
// Instead of the Text, add any view you want in place.
// Play here to see what fits best for you.
Text("Content List")
.frame(maxWidth: .infinity, maxHeight: .infinity)

#if os(iOS)
Text("Split view for iPad")
.frame(maxWidth: .infinity, maxHeight: .infinity)
#else
Text("Split view for macOS")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.toolbar { Spacer() }
#endif
}
}
}

extension SidebarNavigationView {
/// Show or hide the sidebar list in macOS.
///
/// Needed for when the sidebar is hidden from the user
/// action as there is a bug in this version of SwiftUI
/// that block the user to show the sidebar again without
/// this hack.
func toggleSidebar() {
#if os(macOS)
NSApp
.keyWindow?
.firstResponder?
.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)),
with: nil)
#endif
}
}

How do I create a UISplitViewController in SwiftUI without grouped table style?

Just use plain list style explicitly:

var body: some View {
NavigationView {
List(0..<5) { value in
HStack {
Text("Value \(value)")
}
}
.listStyle(PlainListStyle()) // << here !!
.navigationTitle("List View")

Text("Split View")
}
}

SwiftUI: unwanted split view on iPad

Update July 2022

Using NavigationStack instead of NavigationView should display as the main view as you would expect on iPad:

NavigationStack {
Text("Hello world!")
}

*In newer versions, the navigationViewStyle modifier has been deprecated.

Original answer:

You can apply the .navigationViewStyle(.stack) modifier to the NavigationView.

... 
NavigationView {
Text("Hello world!")
}
.navigationViewStyle(.stack)
...

Edit: Below, I am answering Alexandre's questions from his comment:

  • Why full view is not the default for iPad? That's just a choice made by Apple...

  • Why this modifier goes outside of NavigationView closure, while the Navigation Title goes inside... Maybe this gives clarification: https://stackoverflow.com/a/57400873/11432719

Unwanted SplitView on modal view displayed on iPad

Here is fix (it has to be constructed, ie. StackNavigationViewStyle()):

} // END of Navigation View
.navigationViewStyle(StackNavigationViewStyle()) // << here !!


Related Topics



Leave a reply



Submit