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
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
}
}
NavigationView SwiftUI shows split view in iPad
Use stack navigation view style explicitly (by default it is platform-dependent)
NavigationView {
Text("Hello")
.navigationBarTitle("Home")
}
.navigationViewStyle(StackNavigationViewStyle())
Split view on iPad portrait mode results in a useless view being presented on startup
@Yrb comment pointed to the right track with tinkering with underlying UIKit UISplitViewController
to be forced to show the primary column. However, with somewhat heavy views in the Detail, there is some flashing of the primary column appearing and disappearing on slower iPads. So I did this with the Introspect library:
struct ContentView: View {
@State var selection: Int? = 0
var body: some View {
NavigationView {
List {
NavigationLink("Link 1", tag: 0, selection: $selection) {
Text("Link 1 destination")
}
NavigationLink("Link 2", tag: 1, selection: $selection) {
Text("Link 2 destination")
}
}
//In my case I never have a 'Nothing Selected' view, you could replace EmptyView() here
//with a real view if needed.
EmptyView()
}
.introspectSplitViewController { svc in
if isPortrait { //If it's done in landscape, the primary will be incorrectly disappeared
svc.show(.primary)
svc.hide(.primary)
}
}
}
}
This presents as we'd expect, with no flashing or other artifacts.
SwiftUI NavigationView content only visible in iPad sidebar
As noted in the comments on the linked post, the .navigationViewStyle(StackNavigationViewStyle())
must be applied directly to the NavigationView
, and not a view contained therein as with .navigationTitle
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 !!
Disable split view using Swift UI on iPad
By setting the NavigationViewStyle
import SwiftUI
struct NavView: View {
var body: some View {
NavigationView{
List{
NavigationLink(destination: TestView(), label: {Text("TestView")})
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct NavView_Previews: PreviewProvider {
static var previews: some View {
NavView()
}
}
Related Topics
Convert String to Nsdate in Swift
How to Customize the Font and Appearance of a Uialertcontroller in the New Xcode W/ iOS8
Display Table View When Searchbar (From Searchcontroller) Begin Edited Swift
Using Codable to Encode/Decode from Strings to Ints with a Function in Between
How Set Custom Fonts for iOS13 Context Menu Actions
How to Create a Circle with Rounded Ends for Each Quadrant
How to Stop a Dispatchqueue in Swift
How to Properly Check If Non-Optional Return Value Is Valid
How to Delete Item from Collection View
Swift 2: Invalid Conversion from Throwing Function of Type to Non-Throwing Function
Update Nstouchbar on the Fly to Add/Remove Items Programmatically
Swiftui Behavior of .Frame(Height: Nil)
How to Handle Menu Button Action in Tvos Remote
Ios-Charts Set Maximum Visible X Axis Values
Unsafemutablepointer<Cftyperef> in Swift 3
Is There *Any* Situation Under Which "For _ in [1,2,3]" Will Not Loop at All