Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a SplitViewController #2

Open
helje5 opened this issue Apr 29, 2022 · 0 comments
Open

Add a SplitViewController #2

helje5 opened this issue Apr 29, 2022 · 0 comments
Labels
enhancement New feature or request

Comments

@helje5
Copy link
Member

helje5 commented Apr 29, 2022

For common master/detail setups. A SwiftUI NavigationView really is a UI/NSSplitView already :-) But the semantics wrt to show and showDetail would be different.

Implementation shouldn't be too hard, depending on how many features are to be replicated.

A first attempt, to be finished:

/**
 * Type erased version of the ``SplitViewController``. Check that for more
 * information.
 */
public protocol _SplitViewController: _ViewController {
  
  typealias Style  = SplitViewControllerStyle
  typealias Column = SplitViewControllerColumn
  
}

public enum SplitViewControllerStyle: Equatable {
  case doubleColumn
  case tripleColumn
}

public enum SplitViewControllerColumn: Equatable {
  case primary
  case supplementary
  case secondary
}


/**
 * A simple wrapper around SwiftUI's `NavigationView`.
 *
 * Should be used as a root only.
 *
 * This adds a few `UISplitViewController` like behaviour, but in the end just
 * hooks into `NavigationView`
 * (which is a SplitViewController in wider layouts).
 *
 * Unlike `UISplitViewController`, this does not wrap the children in
 * `NavigationController`s (this is handled by SwiftUI itself).
 *
 * Example:
 * ```swift
 * struct ContentView: View { // the "scene view"
 *
 *   var body: some View {
 *     MainViewController(SplitViewController(style: .doubleColumn))
 *   }
 * }
 * ```
 *
 * Note that this works quite differently to a `UISplitViewController`.
 *
 * 2022-04-25: Note that programmatic navigation in SwiftUI is still a mess,
 *             i.e. popping in a 3-pane controller may fail.
 */
open class SplitViewController: ViewController, _SplitViewController {
  // TBD: We could probably make this more typesafe if we tie it to three
  //      columns?
  
  @Published public var style           : SplitViewControllerStyle
  @Published public var viewControllers : [ AnyViewController ]

  init(style: SplitViewControllerStyle = .doubleColumn,
       viewControllers: [ AnyViewController ] = [])
  {
    self.style           = style
    self.viewControllers = viewControllers
  }
  
  convenience
  public init<PrimaryVC, SupplementaryVC, SecondaryVC>(
    _ primary       : PrimaryVC,
    _ supplementary : SupplementaryVC,
    _ secondary     : SecondaryVC
  ) where PrimaryVC       : ViewController,
          SupplementaryVC : ViewController,
          SecondaryVC     : ViewController
  {
    self.init(style: .tripleColumn, viewControllers: [
      AnyViewController(primary),
      AnyViewController(supplementary),
      AnyViewController(secondary)
    ])
    addChild(primary)
    addChild(supplementary)
    addChild(secondary)
  }
  convenience
  public init<PrimaryVC, SecondaryVC>(_ primary   : PrimaryVC,
                                      _ secondary : SecondaryVC)
    where PrimaryVC: ViewController, SecondaryVC: ViewController
  {
    self.init(style: .doubleColumn, viewControllers: [
      AnyViewController(primary),
      AnyViewController(secondary)
    ])
    addChild(primary)
    addChild(secondary)
  }
  
  
  // MARK: - View
  
  public struct ContentView: View {
    
    @EnvironmentObject private var viewController : SplitViewController
    
    public init() {}
    
    struct EmbedChild: SwiftUI.View {
      
      let vc : _ViewController?
      
      var body: some View {
        if let vc = vc {
          vc.anyControlledContentView
        }
      }
    }
    
    public var body: some View {
      // SwiftUI switches the mode based on the _static_ style of the View
      switch viewController.style {
        case .doubleColumn:
          NavigationView {
            EmbedChild(vc: viewController.children.first)
            EmbedChild(vc: viewController.children.count > 1
                       ? viewController.children.dropFirst().first
                       : nil)
          }
        case .tripleColumn:
          NavigationView {
            EmbedChild(vc: viewController.children.first)
            EmbedChild(vc: viewController.children.count > 1
                       ? viewController.children.dropFirst().first
                       : nil)
            EmbedChild(vc: viewController.children.count > 2
                       ? viewController.children.dropFirst(2).first
                       : nil)
          }
      }
    }
  }
}

public extension AnyViewController {

  @inlinable // Note: not a protocol requirement, i.e. dynamic!
  var splitViewController : _SplitViewController? {
    viewController.splitViewController
  }
}

public extension _ViewController {
  
  /**
   * Return the ``SplitViewController`` presenting/wrapping this controller.
   */
  var splitViewController : _SplitViewController? {
    /// Is this VC itself being presented?
    if let presentingVC = presentingViewController {
      if let nvc = presentingVC as? _SplitViewController { return nvc }
      return presentingVC.splitViewController
    }
    if let parent = parent as? _SplitViewController {
      return parent
    }
    return parent?.splitViewController
  }
}
@helje5 helje5 added the enhancement New feature or request label Apr 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant