قالب وردپرس درنا توس
Home / IOS Development / More UISplitViewController training | raywenderlich.com

More UISplitViewController training | raywenderlich.com



An important part of a modern iOS app is the need to adapt to the device and environment it's running on. An app running on iPad has a lot of horizontal space to play with unless it is running in Split Screen or Slide Over column mode. Your app should adapt to every possible set-up with grace.

UISplitViewController is a very versatile component that can adapt to all property collections with very little need for modification of the developer for simple cases. In this tutorial you will learn the simple case, after which you want to double the fun by placing a shared view in your shared view to add an additional level of hierarchy!

Get Started [19659004] Download the launcher with Download Materials button at the top or bottom of the page.

The project app, TreeWorld looks like the Notes app ̵

1; it's a simple file system with folders, which can contain files. The files have text that you can edit.

Take a look around the project first. Open the TreeWorld.xcodeproj file in the TreeWorld-Starter folder. Expand TreeWorld folder.

 Extended Project

Model

Expand Folder Model Layer . Inside is a Core Data model with two devices:

  • File represents a node in the file system.
  • Content represents a container for text.

  core data model

Xcode automatically generates File.swift and Content.swift so that you can use them in the project.

CoreDataStack.swift is a simple Core Data setup

The model obeys these arbitrary rules:

  1. A folder is a file that has isFolder set to true to true
  2. A folder never has a content object.
  3. A folder can not contain folders.
  4. A file is a file which has ] is set to false .
  5. A folder can contain any number of child files.
  6. A file must have a content 19659017] A file never has children.

FileDataSource.swift is a set of query and business logic for File objects and it complies with the rules above.

Views

Inline Group Presentation Team ▸ Member View Controllers is a set of predefined view controllers and storyboards that you will use in this app.

EditorViewController.swift is a view on allowing you to edit the item Content of a file .

FileListViewController.swift is a UITableViewController to present file objects. The class FileDataSource gives UITableViewDataSource and UITableViewDelegate implementations to keep unnecessary logic out of the display controller.

PlaceholderViewController.swift displays a message.

You can investigate them later if you want. They are composed of well-known UIKIT components and should not have any surprises for you.

Inside Presentation Team ▸ Root View Controller is RootViewController.swift . This file is where you want to make the majority of the changes in this tutorial. It will act as coordinator for all views.

Select iPad Pro (9.7-inch) simulator in the target settings. Build and drive.

 target settings

You should see a green view displayed.

 Startup Mode

Installing the Rotor Rotor Split View

In this section you will create and install the root UISplitViewController that will exist for all layouts.

Creating a Split View Controller

Select Folder Presentation Team ▸ Check Controllers ▸ Root View Controller in Project Navigator. Add a new file with Command-N (or File ▸ New ▸ New File ... ).

 New File Dialogue

<img src = "https://koenig-media.raywenderlich.com/uploads/2018/09/new-swift-file2-404x320.png" alt = " New ]

Name RootViewController + ViewFactory . . . 19659017] Click Create .

Open RootViewController + ViewFactory.swift and add the following code:

  import UIKit

extension RootViewController {
func freshSplitViewTemplate () -> UISplitViewController {
let split = UISplitViewController ()
split.preferredDisplayMode = .allVisible
leave navigation = UINavigationController ()
split.viewControllers = [navigation]
return shared
}
}

Here you will find a UISplitViewController and an UINavigationController . You then set the navigation control as the primary display control to the split.

 split view primer

UISplitViewController has a primary and a secondary view control. You can specify and get these controls via the viewControllers property.

To ensure that both primary and secondary views are visible at the same time, you preferDisplayMode the property to .allVisible .

Open RootViewController.swift and add this code to the main body's body:

  la rootSplitSmallFraction: CGFloat = 0.25
la rootSplitLargeFraction: CGFloat = 0.33

lat was rootSplitView: UISplitViewController = {
let split = freshSplitViewTemplate ()
split.preferredPrimaryColumnWidthFraction = rootSplitLargeFraction
split.delegate = self
return shared
} ()

override func viewDidLoad () {
super.viewDidLoad ()
installRootSplit ()
}

func installRootSplit () {
view.addSubview (rootSplitView.view)
view.pinToInside (rootSplitView.view)
addChild (rootSplitView)
}

Add this extension to the end of the file:

  extension RootViewController: UISplitViewControllerDelegate {
// you add this later
}

In this fragment, create a UISplitViewController and insert it into RootViewController . You also set the primary view to take 33% of the available width.

Build and drive. You should see the empty navigation bar for the primary view in the upper left. There is not much to look at yet.

 initial split install

Managing State

You must now set up some infrastructure. The app can have three possible options you want to keep track of:

  1. Nothing selected
  2. Folder Selected
  3. File Selected

Select Folder Presentation Team ▸ See State in Project Navigator. Create a new Swift file ( Command-N ) and name it SelectionState .

Add this code to the file:

  enum SelectionState {
case noSelection
case folder Selected
case file Selected
}

Create a new file in the View State folder. Give this code to StateCoordinator.swift

// 1
protocol StateCoordinatorDelegate: class {
func gotoState (_ nextState: SelectionState, file: File?)
}

// 2
Class State Coordinator: NSObject {
// 3
private (set) was state: SelectionState = .noSelection
Private weak was delegated: StateCoordinatorDelegate?

init (delegate: StateCoordinatorDelegate) {
self.delegate = delegate
}

// 4
private (set) was selectedFile: File? {
didSet {
watch leave file = selectedFile other {
state = .noSelection
return
}
state = file.isFolder? .folderSelected: .fileSelected
}
}

// 5
was selectedFolder: File? {
watch leave file = selectedFile other {
return null
}
return file.isFolder? file: file.parent
}
}

Step-by-step review:

  1. First, you declare a delegate protocol, StateCoordinatorDelegate to allow another object to respond to changes in the StateCoordinator .
  2. You Declare Class StateCoordinator . This class has the job of keeping track of current selected file . The class will be changed correctly SelectionState when a file is selected or deleted. StateCoordinator has two properties: one for SelectionState which is The property selectedFile is only read-only and uses a property monitor to change states .
  3. The property selectedFolder is calculated and gives you the ability to get the selected folder at the top level.

Add this extension to the end of StateCoordinator.swift :

  Extension StateCoordinator {
func didSelectFile (_ file: File?) {
selectedFile = file
delegate? .gotoState (state, file: selectedFile)
}

func didDeleteFile (parentFolder: File?) {
selectedFile = parentFolder
state = .noSelection
delegate? .gotoState (state, file: selectedFile)
}
}

Here you have two public APIs to allow callers to change the state in a managed way.   State Coordinator Api

didSelectFile (_ :) tells StateCoordinator that a file was selected. This implies that sets and informs the downstream delegate that this occurred.

didDeleteFile (parentFolder :) is almost the same as didSelectFile but it ensures that the state is set to noSelection before informing the delegate.

Root View Controller Update

RootViewController must own a StateCoordinator to coordinate state changes.

Open RootViewController.swift .

Add this extension to the end of RootViewController.swift :

  extension RootViewController: StateCoordinatorDelegate {
func gotoState (_ nextState: SelectionState, file: File?) {

}
}

Add these two properties above viewDidLoad () :

  leave dataStack = CoreDataStack ()
Lat was stateCoordinator: StateCoordinator = StateCoordinator (Delegate: Self)

Here you add a StateCoordinator example to RootViewController and ensure that RootViewController complies with StateCoordinatorDelegate . RootViewController can now respond to sample changes in the app.

dataStack will keep the data you create in the app.

Updates file source

FileDataSource will send selection actions to StateCoordinator . You must add a property to FileDataSource .

Open the Model Layer folder in Project Navigator. Open FileDataSource.swift .

Add this property to the top of the main class above init (context: presenter: rootFolder :) :

  was stateCoordinator: StateCoordinator?

Installing File List Table View

You now have almost enough structure to install a FileListViewController in root sharing view. First, add a few helpers to the display factory extension.

Updates View Factory

Open RootViewController + ViewFactory.swift .

Add these methods to the body's body extension:

  func configureFileList (_ fileList: FileListViewController,
Title: String,
rootFolder: File?) {
add data source = FileDataSource (context: dataStack.viewContext,
presenter: fileList,
rootFolder: rootFolder)
datasource.stateCoordinator = stateCoordinator
fileList.fileDataSource = datasource
fileList.title = title
}

func primaryNavigation (_ split: UISplitViewController)
-> UINavigationController {
watch la nav = split.viewControllers.first
as? UINavigationController else {
fatalError ("Project Configuration Error - Primary View Does not Have Navigation")
}
return hub
}
 configureFileList (_: title: rootFolder :)  takes a  FileListViewController  creates a  FileDataSource  and assigns it to  FileListViewController ].  FileDataSource  is given a reference to  StateCoordinator . 

Next, primaryNavigation (_ :) restores UINavigationController from the main view of a shared view. For this app, the navigation check should always exist, so you cast a fatalError to warn you when an error has been created.

Installing the File List

Now you are ready to install FileListViewController .

Open RootViewController.swift and find the method installRootSplit () .

Add this code to the line addChild rootSplitView) :

  la fileList = FileListViewController.freshFileList ()
leave navigation = primarynavigation (rootSplitView)
navigation.viewControllers = [fileList]
configureFileList (fileList, title: "Folders", rootFolder: null)

In this clip you create a FileListViewController from a storyboard. You then restore UINavigationController from shared view and assign the file list as the navigation rotation control.

Build and drive. You can now view FileListViewController in a parent UINavigationController .

 root directory list

You can add folders to the database, rename a sweep

: : Most of the logic in FileDataSource is that you will delete a storage. taken from Xcode Master-Detail App + Core Data project template. Check out this Core Data Tutorial to learn more about Core Data and NSFetchedResultsController .

Fill out the details

Take a look at all the empty space to the right of the table. In this section of the tutorial, fill in that space with cool content.

Is I Horizontal Common?

The iOS display system uses a system of size classes to let developers know how to layout views. The size class of a view can be changed at any time, either by system or user interaction. As in iOS 12, the size class for a given axis may be compact or common .

As a rough rule of thumb, all of the iPad screens split in full screen and two thirds of the width of the screen are common and the rest is compact . In most cases, modern iOS apps should no longer change layout based on device type or screen size.

You want this app to respond to size-size changes depending on whether the app is running in a horizontal compact or common environment

Add this extension to the top of RootViewController.swift under import UIKit :

  Extension UIViewController {
Where is HorizontalRegular: Bool {
return traitCollection.horizontalSizeClass == .regular
}
}

This code is an easy-to-use method to tell if the app is in plain layout on the horizontal axis.

Adding space holders

When nothing is selected, you will fill out the details with a placeholder.

Open RootViewController + ViewFactory.swift and add this extension:

  extension RootViewController {
func freshFileLevelPlaceholder () -> PlaceholderViewController {
leave the placeholder = PlaceholderViewController
.freshPlaceholderController (
message: "Select file or create. Swipe left to delete")
return placeholder
}

func freshFolderLevelPlaceholder () -> PlaceholderViewController {
leave the placeholder = PlaceholderViewController
.freshPlaceholderController (message: "" "
Select folder or create. Swipe left to delete or swipe right to rename
"" ")
return placeholder
}
}

These two methods create PlaceholderViewController instances with contextual messages.

Return to RootViewController.swift .

Add this extension to the end of the file:

  extension RootViewController {
func showFileLevelPlaceholder (in targetSplit: UISplitViewController) {
if is horizontal-regulated {
targetSplit.showDetailViewController (freshFileLevelPlaceholder (), sender: self)
}
}

func showFolderLevelPlaceholder (in targetSplit: UISplitViewController) {
if is horizontal-regulated {
rootSplitView.preferredPrimaryColumnWidthFraction = rootSplitLargeFraction
targetSplit.showDetailViewController (freshFolderLevelPlaceholder (), sender: self)
}
}
}

These two methods will install a placeholder view in the secondary view of a shared view, but only if the current property collection is common. In a compact environment, you'll never see placeholders because if you do not have anything selected in the list you will still look at the list.

You build the set of components you need to handle all pull collections. Soon all your hard work will pay off!

Handling of Navigation Countries

You want to know the state of navigation control in rootSplitView . In this section you add logic to help with it. [19659000] RootViewController.swift Adds this Enen to Class Name [1945900046] RootViewController

  Main Name  RootViewController 

  Enum NavigationStackCompact: Int {
case foldersOnly = 1
case foldersFiles = 2
case foldersFilesEditor = 3
}

This one describes all possible stacks that can exist in a compact environment.

Add this extension to the end of RootViewController.swift :

  Extension RootViewController: UINavigationControllerDelegate {
func navigationController (_ navigationController: UINavigationController,
didShow viewController: UIViewController,
animated: Boolean) {
whose navigationStack (Navigation Controller, isAt: .foldersOnly) {
showFolderLevelPlaceholder (in: rootSplitView)
}
}

func navigationStack (
_ Navigation: UINavigationController,
isAt state: NavigationStackCompact
) -> Boolean {
la telle = navigation.viewControllers.count
if let value = NavigationStackCompact (rawValue: count) {
return value == state
}
return false
}
}

This delegation method, navigationController (_: viewController: animated :) allows you to detect a change in the navigation pile and install a placeholder when needed.

Helper Method NavigationStack in combination with NavigationStackCompact returns an answer to the question "What is the navigation currently displayed?" without spotting magic numbers above the code.

Finally, find the method installRootSplit () in the class definition and add this line at the end of the method:

  navigation.delegate = self

Build and drive.

 folder placeholder installed

Splitting Split

You are now ready to install a UISplitViewController inside a UISplitViewController .

Create a split and add view

You want to place a UISplitViewController in rootSplitView like this:

 Add a subgroup ] [19659004] I RootViewController.swift Insert this method below viewDidLoad () :

  func installDoubleSplitWhenHorizontalallyRegular () -> UISplitViewController? {
guard is horizontalRegular other {
return null
}

if let subSplit = rootSplitView.viewControllers.last
as? UISplitViewController {
return subSplit
}

let split = freshSplitViewTemplate ()
split.delegate = self
rootSplitView.preferredPrimaryColumnWidthFraction = rootSplitSmallFraction
rootSplitView.showDetailViewController (split, sender: self)
return shared
}

This method checks to see if there is a split view in the secondary view, and it returns if it exists. Otherwise, a new shared view will be created and returned.

Selecting the appropriate sharing to be used

When you add the list of files contained in a folder, you want to target another shared view, depending on the current drawing collection. This will happen more places, so you want to place all that logic in an auxiliary method.

Add this method under the method installDoubleSplitWhenHorizontalallyRegular () :

  func targetSplitForCurrentTraitCollection () ->
UISplitViewController {
if is horizontal-regulated {
guard la subSplit = installDoubleSplitWhenHorizontalallyRegular () otherwise {
fatalError ("You must have a UISplitViewController here")
}
return subSplit
} other {
return rootSplitView
}
}

This method always returns the correct shared view, depending on the property collection.

Add Table View of Files

The list of folders is always displayed in the root navigation of rootSplitView but the list of files can either be displayed:

  • The stack of primary navigation.
  • As the root rotary controller to the submarine's primary navigation.

  compact vs common

In RootViewController.swift find the extension containing the showFolderLevelPlaceholder method () .

Add this code to the extension:

  func installFileList (fileList: FileListViewController) {
if
isHorizontallyRegular,
let subSplit = installDoubleSplitWhenHorizontalallyRegular () {
// 1
leave navigation = primary navigation (subSplit)
navigation.viewControllers = [fileList]
// 2
subSplit.preferredDisplayMode = .allVisible
subSplit.preferredPrimaryColumnWidthFraction = rootSplitLargeFraction
rootSplitView.preferredPrimaryColumnWidthFraction = rootSplitSmallFraction
// 3
showFileLevelPlaceholder (in: subSplit)
} other {
leave navigation = primarynavigation (rootSplitView)
navigation.pushViewController (fileList, animated: true)
}
}

When the app has a normal horizontal width:

  1. You install FileListViewController FileListViewController as the root of the subdivision's UINavigationController .
  2. You configure the split PreferredPrimaryColumnWidthFraction property to share the screen 25/25/50.
  3. You install a placeholder in the detail view.

Otherwise, not in plain horizontal width, touch FileListViewController ] on UINavigationController in rootSplitView .

Response to Government Changes

Earlier, you have configured RootViewController as the representative of the StateCoordinator . StateCoordinator calls gotoState (_: file :) on its delegate when the election mode changes.

In this next section, add the password to allow the user interface to respond to these changes.

Configure Rotate View to Respond to Government Changes

Within RootViewController.swift you will find the gotoState (_: file :) method of the StateCoordinatorDelegate extension.

Add these four helpers under this method:

  // 1
func gotoNoSelection (_folder: File?) {
leave navigation = primarynavigation (rootSplitView)
whose navigationStack (Navigation, Ert: .foldersOnly) && Folder == nil {
showFolderLevelPlaceholder (in: rootSplitView)
} other {
showFileLevelPlaceholder (in: targetSplitForCurrentTraitCollection ())
}
}

// 2
func gotoFolderSelected (_folder: File) {
whose folder.isFolder {
leave filenList = FileListViewController.freshFileList ()
la title = folder.name ?? "Untitled"
configureFileList (file list, title: title, rootFolder: folder)
installFileList (fileList: fileList)
}
}

// 3
func gotoFileSelected (_ file: File) {
if! file.isFolder {
let detail = EditorViewController.freshDetailController (file: file)
leave navigation = freshNavigationController (rootViewController: detail)
targetSplitForCurrentTraitCollection ()
.showDetailViewController (Navigation, Sender: Self)
}
}

// 4
func freshNavigationController (rootViewController: UIViewController)
-> UINavigationController {
la nav = UINavigationController (rootViewController: rootViewController)
nav.navigationBar.prefersLargeTitles = true
return hub
}
  1. gotoNoSelection (_ :) controls the state of the rotary navigation and decides which placeholder to install. This method is called when the user has deleted a file or a folder. When the user deletes a file StateCoordinator parent file transfers to StateCoordinatorDelegate . A folder has no parent so the argument will be null .
  2. gotoFolderSelected (_ :) creates a new FileListViewController configures that file list with a reference to the selected parent folder, and then installs that file list in the correct location.
  3. gotoFileSelected (_ :) creates a new EditorViewController with a reference to the selected file, places the editor in an UINavigationController and installs that navigation in the secondary view of the current split .
  4. freshNavigationController (rootViewController :) creates a new UINavigationController instances with the correct configuration.

Finally, enter gotoState (_: file :) with this code:

  if nextState == .folderSelected, leave folder = file {
gotoFolderSelected (folder)
} Other if nextState == .fileSelected, leave file = file {
gotoFileSelected (file)
} Other if nextState == .noSelection {
gotoNoSelection (file)
}

With this code, select the appropriate destination based on the argument nextState .

You're done with RootViewController for now!

Updates File Source for Trigger State Changes

Responding to Selections

RootViewController can now respond to the changes sent by StateCoordinator but nothing triggers these changes yet.

In this section, add the code to let FileDataSource connect to StateCoordinator .

 File data source releases changes

Open Model Layer folder in the project navigator and select FileDataSource.swift .

Find extension extension FileDataSource: UITableViewDelegate .

Add this code into the body's method Tableview (_: didSelectRowAt :) :

  stateCoordinator? .DidSelectFile (object (on: indexPath))

When touching a cell in FileListViewController the delegate method is called tableView (_: didSelectRowAt :) in FileDataSource . FileDataSource tells StateCoordinator that a file was selected, of which StateCoordinator changes state and tells RootViewController ] about that change.

Reply to deletions

You also want to address the deletion of files such that the action can change the selection status. In this section, add the code to address this.

Find extension extension FileDataSource: UITableViewDataSource .

Find the method deleteAction (_ :) and replace the line try operations.delete (file: file, power: true) inside make block this code:

  // 1
let parents = file.parent
// 2
try operations.delete (file: file, power: true)
// 3
stateCoordinator? .didDeleteFile (parentFolder: parents)

In this change you get:

  1. Get a reference to parent of File before deleted from the database.
  2. Delete file.
  3. 19659017] Tell StateCoordinator that a deletion occurred.

Build and run to see all your hard work pay off.

 Completed Insertion Split

When selecting a folder, one second FileListViewController is displayed where you can create, rename and delete file objects.

Testing in compact environments

Thanks to UISplitViewController your app will also adapt to running on iPhone.

Select iPhone 8 simulator target.

 iPhone 8 target

Build and run. Your UISplitViewController behaves now as a UINavigationController . You start with an empty folder list because this is a new device. Add a Folder:

 Compact Folders

Select it and go to the file list where you can add files:

 Compact Files

Select a File to go to the editor:

 compact editor

It's great! When driving in a compact environment, you'll see a view at a time because there's no room for more. It is the power of UISplitViewController !

Sikre riktig oppførsel når egenskaper endres

Så langt har du satt opp appen for å oppføre seg riktig når den lanseres i et kompakt eller vanlig miljø.

Du så at en UISplitViewController kunne tilpasse seg til enten en kompakt eller vanlig layout. When you place a view controller in the secondary view with showDetailViewController(_:sender:):

  • In compact mode, UISplitViewController shows the detail view on the top of the navigation stack and adds a Back button to the navigation bar.
  • In regular mode, UISplitViewController places the detail view in the right-hand split.

Currently, if you try to go from regular mode to compact mode — or vice versa — the app will not work correctly. This will affect your users when they are using your app in split screen multitasking modes on iPad.

UISplitViewController needs some hints on how to reassemble itself during a trait change.

reassembly for trait change

In this section, you’ll add the code to allow the app to respond correctly to the change from compact to regular.

Controlling the Reassembly

UISplitViewControllerDelegate has a large set of methods that allow you to customize the behavior of UISplitViewController. You’ll use two that are called when the split needs to change its trait collection.

First, you’ll add helpers to the view factory.

Find RootViewController+ViewFactory.swift in the Project navigator (ViewControllers ▸ RootViewController).

Add this extension to the file:

extension RootViewController {
  func rootFileList(_ split: UISplitViewController)
-> FileListViewController? {
    let navigation = primaryNavigation(split)
    guard let fileList = navigation.viewControllers.first as? 
      FileListViewController else {
      assertionFailure("Your split should have a FileListVC in the master nav")
return null
}
    return fileList
}


func activeEditor(_ split: UISplitViewController) -> EditorViewController? {
    guard let navigation = split.viewControllers.last as? UINavigationController,
      let editor = navigation.viewControllers.first as? EditorViewController
      else { return nil }
    return editor
}

}

These methods are helpers used to extract either a FileListViewController or an EditorViewController from a UISplitViewController.

Changing From Regular to Compact Traits

Now, you’ll manage the change from regular to compact traits.

Open RootViewController.swift. Locate the extension:

extension RootViewController: UISplitViewControllerDelegate

Add this method inside the body of the extension:

func splitViewController(_ splitViewController: UISplitViewController,
                         collapseSecondary secondaryViewController: UIViewController,
                         onto primaryViewController: UIViewController) -> Bool {
// 1
  let primaryNav = primaryNavigation(splitViewController)
  var currentStack = primaryNav.viewControllers

// 2
  if let secondarySplit = secondaryViewController as? UISplitViewController {
// 3
    if let fileList = rootFileList(secondarySplit) {
      currentStack.append(fileList)
}
// 4
    if let editor = activeEditor(secondarySplit) {
      currentStack.append(editor)
}
    //5
    primaryNav.viewControllers = currentStack
return true

  } else if let folderList = currentStack.first {
    //6
    primaryNav.viewControllers = [folderList]
return true

}
  return false
}

This delegate method splitViewController(_:collapseSecondary:onto:) allows you to control the process of collapsing the secondary controller onto the primary. You return a result of true if you want to override the default behavior.

  1. First, you get a reference to the root navigation controller and take a snapshot of its view controller stack.
  2. Next, you check if there’s a split in the secondary position.
  3. You append the FileListViewController from that split onto the destination stack.
  4. If the secondary view in the secondary split is an EditorViewControlleryou append that also.
  5. Finally, reset the navigation stack with its new contents.
  6. When there’s no split in the secondary position, you reset the navigation stack with only the folder list.

And that’s it! You have covered the case of the trait collection changing from regular to compact.

Changing From Compact to Regular Traits

Now, you’ll manage the change from compact to regular traits.

Add this code to the UISplitViewControllerDelegate extension:

func splitViewController(_ splitViewController: UISplitViewController,
                         separateSecondaryFrom primaryViewController: UIViewController)
-> UIViewController? {

  guard let primaryNavigation = primaryViewController as? UINavigationController else {
return null
}

  return decomposeStackForTransitionToRegular(primaryNavigation)
}

func decomposeStackForTransitionToRegular(_ navigationController: UINavigationController)
-> UIViewController? {
// 1
  let controllerStack = navigationController.viewControllers
  guard let folders = controllerStack.first else {
return null
}

// 2
  defer {
    navigationController.viewControllers = [folders]
    rootSplitView.preferredPrimaryColumnWidthFraction = rootSplitSmallFraction
}

// 3
  if navigationStack(navigationController, isAt: .foldersOnly) {
    //folder list only was presented  - return a placeholder
    return freshFolderLevelPlaceholder()

  } else if navigationStack(navigationController, isAt: .foldersFiles) {
    //folders and file list was presented  - return a split with files + placeholder
    let filesAndPlaceholder = configuredSplit(
      first: controllerStack[1]
      second: freshFileLevelPlaceholder())
    return filesAndPlaceholder

  } else if navigationStack(navigationController, isAt: .foldersFilesEditor) {
    //folders, files and editor was presented  - return a split with files + editor
    let filesAndEditor = configuredSplit(
      first: controllerStack[1]
      second: controllerStack[2])
    return filesAndEditor
}

return null
}

func configuredSplit(first: UIViewController, second: UIViewController)
    -> UIViewController {
  let freshSplit = freshSplitViewTemplate()
  freshSplit.preferredPrimaryColumnWidthFraction = rootSplitLargeFraction
  let fileNavigation = primaryNavigation(freshSplit)
  fileNavigation.viewControllers = [first]
  freshSplit.viewControllers = [fileNavigation, second]
  return freshSplit
}

The delegate method splitViewController(_:separateSecondaryFrom:) allows you to return the UIViewController that is placed in the secondary position when transitioning to regular traits.

In this code above, you cast the primaryViewController as UINavigationControllerthen you pass that controller to decomposeStackForTransitionToRegular(_:):

  1. Take a copy of the current navigation stack and root view controller in that stack.
  2. Use defer to reset the input UINavigationController to only the folder list and correct the column width for double-split views.
  3. Depending on what the navigation stack is currently showing you, extract view controllers from the array and reassemble them into a UISplitViewController.

The method configuredSplit(first:second:) is a helper to create a populated UISplitViewController.

Playing With the Splits

You’re ready to exercise the app, now!

Choose the iPad Pro (9.7-inch) simulator in the Target settings.

Build and run. You can play with a few layout scenarios to see how the trait change works in practice.

Place the app in two-thirds split screen. You can see that the app is still horizontally regular.

2/3 split view

Slide the divider and split the screen to 50/50. The app is now horizontally compact. The active EditorViewController is placed at the top of the navigation stack and a Back button is added to the editor.

equal split

Drag the system control at the top of the app down to convert the app to Slide Over. The editor Back button has been compacted a little.

slide over display

The reverse journey back to horizontally regular will reconstruct the double split. Give it a try!

Where to Go From Here?

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

You have now learned some basic UISplitViewController mechanics and how to handle the transitions from compact to regular, then back again.

You’ve gone beyond the basics and offered an extra level of hierarchical selection by using a split view embedded in a split view. If your app has a simple hierarchy like TreeWorldthis technique is great for giving users visibility of where they are in their data. However, a note of caution — more levels of hierarchy would make the UI quite confusing, and a single split view with navigation on the left and detail on the right is a safer option.

UISplitViewController has a lot more to offer in terms of free behavior and customization using UISplitViewControllerDelegate. This tutorial has just scratched the surface of possibilities.

Hopefully, this tutorial inspires some great adaptive designs. I look forward to seeing some of your results in the discussion forum below!


Source link