How to show in SwiftUI the sidebar in iPad and portrait mode
It can be done, but for now it requires access to UIKit's UISplitViewController via UIViewRepresentable. Here's an example, based on a solution described here.
import SwiftUI
import UIKit
struct UIKitShowSidebar: UIViewRepresentable {
let showSidebar: Bool
func makeUIView(context: Context) -> some UIView {
let uiView = UIView()
if self.showSidebar {
DispatchQueue.main.async { [weak uiView] in
uiView?.next(of: UISplitViewController.self)?
.show(.primary)
}
} else {
DispatchQueue.main.async { [weak uiView] in
uiView?.next(of: UISplitViewController.self)?
.show(.secondary)
}
}
return uiView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
DispatchQueue.main.async { [weak uiView] in
uiView?.next(
of: UISplitViewController.self)?
.show(showSidebar ? .primary : .secondary)
}
}
}
extension UIResponder {
func next<T>(of type: T.Type) -> T? {
guard let nextValue = self.next else {
return nil
}
guard let result = nextValue as? T else {
return nextValue.next(of: type.self)
}
return result
}
}
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink("Primary view (a.k.a. Sidebar)", destination: DetailView())
}
NothingView()
}
}
}
struct DetailView: View {
var body: some View {
Text("Secondary view (a.k.a Detail)")
}
}
struct NothingView: View {
@State var showSidebar: Bool = false
var body: some View {
Text("Nothing to see")
if UIDevice.current.userInterfaceIdiom == .pad {
UIKitShowSidebar(showSidebar: showSidebar)
.frame(width: 0,height: 0)
.onAppear {
showSidebar = true
}
.onDisappear {
showSidebar = false
}
}
}
}
SwiftUI - 3 Column/Sidebar Layout with All Visible?
Update: Better solution
extension UISplitViewController {
open override func viewDidLoad() {
super.viewDidLoad()
self.show(.primary)
}
}
===========================
Take a look at this github project: SwiftUI-Introspect
import Introspect
@main
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
Text("Primary View")
Text("Supplementary View")
Text("Secondary View")
// show primary view at startup
.introspectSplitViewController(customize: { splitViewController in
splitViewController.show(.primary)
})
}
}
}
}
// Implement UISplitViewControlle selector
extension View {
public func introspectSplitViewController(customize: @escaping (UISplitViewController) -> ()) -> some View {
return inject(UIKitIntrospectionViewController(
selector: { introspectionViewController in
// Search in ancestors
if let splitViewController = introspectionViewController.splitViewController {
return splitViewController
}
// Search in siblings
return Introspect.previousSibling(containing: UISplitViewController.self, from: introspectionViewController)
},
customize: customize
))
}
}
SwiftUI List Rows Different Heights for Different System Images
If you want to ensure that the rows are even, you can use PreferenceKeys
to set the row heights to the height of the tallest row like this:
struct EvenHeightListRow: View {
@State var rowHeight = CGFloat.zero
var body: some View {
List {
NavigationLink(destination: Text("MyView1")) {
Label("Test Row 1)", systemImage: "list.bullet")
// This reads the height of the row
.background(GeometryReader { geometry in
Color.clear.preference(
key: HeightPreferenceKey.self,
value: geometry.size.height
)
})
.frame(height: rowHeight)
}
NavigationLink(destination: Text("MyView2")) {
Label("Test Row 2)", systemImage: "shippingbox")
.background(GeometryReader { geometry in
Color.clear.preference(
key: HeightPreferenceKey.self,
value: geometry.size.height
)
})
.frame(height: rowHeight)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
// this sets backgroundSize to be the max value of the tallest row
.onPreferenceChange(HeightPreferenceKey.self) {
rowHeight = max($0, rowHeight)
}
}
}
// This is the actual preferenceKey that makes it work.
fileprivate struct HeightPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {}
}
Hide only .supplementary column in UISplitViewController
In a .tripleColumn
split view controller, by design, it is impossible for the .primary
column to appear without also showing the .supplementary
column.
And you cannot change one and the same split view controller from being a .tripleColumn
to being a .doubleColumn
. I suppose you could just rip the entire split view controller right out of the interface and substitute a different one, but is that really what you want to do? I think it would be better to use the split view controller the way it is designed to be used.
SwiftUI NavigationView Secondary not showing
This is expected behavior on devices with a "compact width" size class.
On devices that report "regular width", you will see the split NavigationView
.
You can see the table of device and size classes at: https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/
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.
Related Topics
iOS 14 Widget Detect System Theme Change
iOS Charts - Single Values Not Showing Swift
How to Cut a Hole in a Sprite Image or Texture to Show What Is Behind It Using Spritekit in Swift
How to Bind My Array Controller to My Core Data Model
Transparent Sticky Header UI Collectionview Don't Show Cells Underneath
Creating Decoration View as Custom Column in UIcollection View
Swift - Rotate Gesture and Rotation Increments of 90 Degrees
Swift Execute Command Line Command in Sandbox Mode
Arkit: Plot a Node at a Specific Pixel at a Specific Z Distance from Camera
Redirect Process Stdout to Apple System Log Facility in Swift
Write Data to Firebase in The Background After Retrieving Steps with Healthkit's Background Delivery
Animation Triggered Using a Button Stops a Repeatforever Animation Added Onappear
How to Have a Searchbar Which Shows Suggestions with Different UItableview
Understanding UIviewrepresentable
Using a Swiftui List Sidebar in a UIsplitviewcontroller
Given a Swift 'Any' Type How to Determine If It's an 'Optional'
How to Disabled Some Default Functionality in Scene View When Allowscameracontrol = True