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
How to Reference Instance Function When Calling Sequencetype.Foreach
Getter VS Computed Property. What Would Warrant Using One of These Approaches Over the Other
Swift Formatting String to Have 2 Decimal Numbers If It Is Not a Whole Number
How to Make the View Update Instant in Swiftui
Swift Unrecognized Selector Sent to Instance Error
Astoryboard.Instantiateviewcontrollerwithidentifier("Myid") Returns Nil But Not Nil in Lldb
Dateformatter Returns Unexpected Date for Timezone
Swift - How to Save Memory Using Alamofireimage/Imageshack
How to Save an Array to .Plist in the App's Mainbundle in Swift
How to Load a Url Link That Is Inside a Web View and Keep It in That Web View in Swift
Upload Files to Dropbox from iOS App with Swift
Autolayout Complains About Constraints for 2 Uitextfields with No Borders