Using a Swiftui List Sidebar in a UIsplitviewcontroller

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



Leave a reply



Submit