TidySplits is another approach to setup Split Layout. It was created because of small configuration options of Apple's original SplitViewController. TidySplits is built using Swift 4.2 and supports iOS 9+.
Personally on my commercial project Apple's SplitViewController overwhelmed me. It was to hard to control transitions between Compact and Regular interface. Project's view controllers structure is
- SplitViewController ->
- NavigationController ->
- TabBarController ->
- Many of...
- TableViewControllers ->
- FilteringController
- Create/EditController (DetailContext on Regular devices) -> Shows on secondary navigation
- TabBarDetailController (DetailContext on Regular devices) -> Shows on secondary navigation
- TabBarController ->
- NavigationController ->
- TabBarController ->
- Many of...
- TableViewControllers ->
- DetailController -> It can go deeper and deeper.
- FilteringController
- Create/EditController
- CollectionControllers ->
- FilteringController
- Create/EditController
- SomeCustomControllers
- TabBarController ->
- NavigationController ->
And this structure can go deeper and deeper, forever. So at some point of time I decided to create my own navigation. For now it works much better with our controllers structure.
You can either install it via CocoaPods or by copy-pasting TidySplits source code into your project.
To install it via CocoaPods just add this line to your pods file:
pod 'TidySplits'
And then run from your console:
pod install
After downloading whole project you can check how it works by running target 'TidySplitsExample'. This project contains simple example how TidySplits should be configured. It's super easy!
Example project has two important files: ExampleViewController.swift and MainSplitViewController.Swift, first is just created to show how some of navigation features works. Second is our start point of everything :).
First of all, like in example project you have to create your SplitViewController which inherits from TidySplitsUISplitViewController
Controller from example code looks like following:
class ExampleSplitViewController: TidySplitsUISplitViewController, TidySplitsSplitViewDelegate {
var shouldOmitDetailChildsForCompactMode: Bool {
return false
}
override func viewDidLoad() {
self.delegate = self
super.viewDidLoad()
}
func getDetailsPlaceholder() -> TidySplitsChildControllerProtocol {
let ctrl = ExampleViewController(.Detail)
ctrl.view.backgroundColor = .green
return ctrl
}
func getInitialPrimaryControllers() -> [TidySplitsChildControllerProtocol] {
let ctrl = ExampleViewController(.Primary)
ctrl.view.backgroundColor = .blue
return [ctrl]
}
func getInitialDetailControllers() -> [TidySplitsChildControllerProtocol] {
let ctrl = ExampleViewController(.Detail)
ctrl.view.backgroundColor = .red
return [ctrl]
}
}
On viewDidLoad
method you have to specify delegate. For simplify our controller is it's own delegate :). Delegate protocol declares required method's and properties for correct configuration.
public protocol TidySplitsSplitViewDelegate: class {
/**
Computed get only property which decides if we should omit details stack when transitioning from **regular** to **compact** layout.
*/
var shouldOmitDetailChildsForCompactMode: Bool { get }
/**
Delegate method to define which controller (or controllers) starts primary stack.
*/
func getInitialPrimaryControllers() -> [TidySplitsChildControllerProtocol]
/**
Delegate method to define which controller (or controllers) starts detail stack.
*/
func getInitialDetailControllers() -> [TidySplitsChildControllerProtocol]
/**
Delegate method to define which controller should be shown if detail stack is empty and we transition to **regular** "splitted" layout.
*/
func getDetailsPlaceholder() -> TidySplitsChildControllerProtocol
}
This protocol is the core of all what is happening in this library.
public protocol TidySplitsChildControllerProtocol: class {
var prefferedDisplayType: TidySplitsChildPreferedDisplayType { get set }
var tidySplitController: TidySplitsUISplitViewController? { get }
var tidyNavigationController: TidySplitsUINavigationController? { get }
func postPopSelfNotification()
func popSelf() -> UIViewController?
}
The most important thing is preferedDisplayType
property. It determines where specific controller should be pushed (Primary or Detail stack).
I have created and defined some base controllers which you can use as a base for your custom controllers:
- TidySplitsUIViewController - subclass of UIViewController
- TidySplitsUITableViewController - subclass of UITableViewController
- TidySplitsUITabBarViewController - subclass of UITabBarController
Okay, I have configured my own split controller and created some childs which conforms to TidySplitsChildControllerProtocol
, what now?
Now you can finally make your app navigating! There are several possibilites to perform a navigation.
open func showDetail(_ controller: TidySplitsChildControllerProtocol, _ animated: Bool = true, _ completion: ((TidySplitsChildControllerProtocol) -> Void)? = nil)
open func push(_ controller: TidySplitsChildControllerProtocol, _ animated: Bool = true, _ completion: ((TidySplitsChildControllerProtocol) -> Void)? = nil)
open func tryPush(_ controller : UIViewController, _ animated: Bool = true, _ completion: ((TidySplitsChildControllerProtocol) -> Void)? = nil) -> Bool
@discardableResult open func pop(from type: TidySplitsChildPreferedDisplayType, _ animated: Bool = true, _ completion: ((UIViewController?) -> Void)? = nil) -> UIViewController?
showDetail
method replaces current detail stack with specified controller.push
method pushes new controller to primary or detail stack - based on controller's prefered display type.tryPush
method tries to push (if it conforms to our ChildProtocol) new controller to primary or detail stack - based on controller's prefered display type.pop
method pops currently displayed controller from specified type
open func pushToMe(_ controller: TidySplitsChildControllerProtocol, _ animated: Bool = true, _ completion: ((TidySplitsChildControllerProtocol) -> Void)? = nil)
open func tryPushToMe(_ controller: UIViewController, _ animated: Bool = true, _ completion: ((TidySplitsChildControllerProtocol) -> Void)? = nil) -> Bool
open func popFromMe(animated: Bool = true, _ completion: ((UIViewController?) -> Void)? = nil)
pushToMe
method pushes controller to current navigation and changes it's prefered type to type of Navigation controller - think about it when using this method!.tryPushToMe
method tries to push controller to current navigation (if child conforms to ChildProtocol) and changes it's prefered type to type of Navigation controller - think about it when using this method!.popFromMe
method pops currently displayed controller in this navigation controller
Child controller protocol has this properties defined:
var tidySplitController: TidySplitsUISplitViewController? { get }
var tidyNavigationController: TidySplitsUINavigationController? { get }
It allows you to use Split/Navigation controllers methods in child code.
Additionally child can popSelf()
:)
That's nice, I wrote it in a hurry in ~8 hours. I am 100% that you will find something. Create an issue and we can make it better, together :).
- Teleports feature
- Expose better configuration options
- Document code
Just submit a pull request.
- Tomasz Kamiński - Creator - TomKaminski
See also the list of contributors who participated in this project.
This project is licensed under the MIT License - see the LICENSE file for details