Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to run the split view on iPad using SwiftUI?

Tags:

ios

ipad

swiftui

It seems like there would be an easy way to make the good looking iOS app run on an iPad space. One that didn't involve rewriting the SwiftUI app.

The basic SplitView & SplitViewController issue seems ... well like a poorly thought out problem space... I've got a basic - display cards with the ContentView - I'd like it to run on iPad... just a bigger viewport...

Any easy way to accomplish this using SwiftUI?

like image 206
David Avatar asked Dec 31 '22 21:12

David


1 Answers

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
  }
}
like image 130
Roland Lariotte Avatar answered Jan 05 '23 16:01

Roland Lariotte