Search code examples
swiftuiswiftui-navigationviewipadosswiftui-navigationsplitview

How to show the sidebar in iPad and portrait mode


I have an master detail app in iPad, and when run the app in portrait mode the sidebar is hidden. I need to push Back button to open the sidebar.

Can anyone help me to show the sidebar by default? I found an answer that suggest to use StackNavigationViewStyle when the app is in portrait, but then the app seems like a giant iPhone and dissapears the master class like a sidebar to appear like a view.

Thats my code.

struct ContentView: View {
    var body: some View {
        NavigationView {
            MyMasterView()
            DetailsView()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct MyMasterView: View {

    var people = ["Option 1", "Option 2", "Option 3"]

    var body: some View {

        List {
            ForEach(people, id: \.self) { person in
                NavigationLink(destination: DetailsView()) {
                    Text(person)
                }
            }
        }

    }
}

struct DetailsView: View {

    var body: some View {
        Text("Hello world")
            .font(.largeTitle)
    }
}

Thank you


Solution

  • 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
            }
        }
      }
    }