Default behaviour of UISplitViewController collapseSecondaryViewController

Published by malhal on

The documentation for UISplitViewControllerDelegate collapseSecondaryViewController says:

When you return NO, the split view controller calls the collapseSecondaryViewController:forSplitViewController: method of the primary view controller, giving it a chance to do something with the secondary view controller’s content. Most view controllers do nothing by default but the UINavigationControllerclass responds by pushing the secondary view controller onto its navigation stack.

Similarily, for separateSecondaryViewControllerFromPrimaryViewController it says:

When you return nil from this method, the split view controller calls the primary view controller’s separateSecondaryViewControllerForSplitViewController: method, giving it a chance to designate an appropriate secondary view controller. Most view controllers do nothing by default but the UINavigationControllerclass responds by popping and returning the view controller from the top of its navigation stack.

I thought it might be interesting to try to implement this magic behaviour to help understand what is going on and thus provide a starting point for customisation.

- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController {
    
    if ([secondaryViewController isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[DetailViewController class]] && ([(DetailViewController *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)) {
        // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return YES;
    } else {
        
        // push the secondary view controller onto the primary's navigation stack
        UINavigationController *nav = (UINavigationController *)primaryViewController;
        [nav pushViewController:secondaryViewController animated:NO];
        return YES;

    }
}

- (nullable UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController{

    // respond by popping and returning the view controller from the top of its navigation stack
    UINavigationController *nav = (UINavigationController *)primaryViewController;
    return [nav popViewControllerAnimated:NO];

}

It’s strange it is possible to push the secondary navigation controller , since usually that exceptions with “Pushing a navigation controller is not supported”. Turns out within the push method it checks a private property _allowNestedNavigationControllers to allow it to pass in this case, and it must have been set by the split view controller at some point. These kind of tricks are really annoying because gives inconsistent behaviour and thus lowers developer confidence in the APIs.
It’s interesting to call: [(UINavigationController *)secondaryViewController setViewControllers:nil]; before pushing because it throws an exception that proves that nested navigation controllers are being used: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot display a nested UINavigationController with zero viewControllers

I find using the plus iPhone simulator useful for testing the split controller because in portrait it is compact width but rotating to landscape it moves to regular width.


0 Comments

Leave a Reply

Avatar placeholder