Hiding the master view controller with UISplitViewController in iOS8

I have an iOS7 application, which was based on the Xcode master-detail template, that I am porting to iOS8. One area that has changed a lot is the UISplitViewController.

When in portrait mode, if the user taps on the detail view controller, the master view controller is dismissed:

  • Navigation bar for split view controller is darker when inside a tab bar controller
  • UISplitViewController in portrait on iPhone shows detail VC instead of master
  • Achieve a Uniform UIBlurEffect in the Primary View of a UISplitView
  • How to make Segue animation Horizontal without UINavigationController?
  • SplitViewController with a Login View Controller as the root
  • How to inform the parent viewcontroller about the changed screen orientation in the modal view controller?
  • enter image description here

    I would also like to be able to programmatically hide the master view controller if the user taps on a row.

    In iOS 7, the master view controller was displayed as a pop-over, and could be hidden as follows:

    [self.masterPopoverController dismissPopoverAnimated:YES];
    

    With iOS 8, the master is no longer a popover, so the above technique will not work.

    I’ve tried to dismiss the master view controller:

    self.dismissViewControllerAnimated(true, completion: nil)
    

    Or tell the split view controller to display the details view controller:

    self.splitViewController?.showDetailViewController(bookViewController!, sender: self)
    

    But nothing has worked so far. Any ideas?

    9 Solutions Collect From Internet About “Hiding the master view controller with UISplitViewController in iOS8”

    Extend the UISplitViewController as follows:

    extension UISplitViewController {
        func toggleMasterView() {
            let barButtonItem = self.displayModeButtonItem()
            UIApplication.sharedApplication().sendAction(barButtonItem.action, to: barButtonItem.target, from: nil, forEvent: nil)
        }
    }
    

    In didSelectRowAtIndexPath or prepareForSegue, do the following:

    self.splitViewController?.toggleMasterView()
    

    This will smoothly slide the master view out of the way.

    I got the idea of using the displayModeButtonItem() from this post and I am simulating a tap on it per this post.

    I am not really happy with this solution, since it seems like a hack. But it works well and there seems to be no alternative yet.

    Use preferredDisplayMode. In didSelectRowAtIndexPath or prepareForSegue:

    self.splitViewController?.preferredDisplayMode = .PrimaryHidden
    self.splitViewController?.preferredDisplayMode = .Automatic
    

    Unfortunately the master view abruptly disappears instead of sliding away, despite the documentation stating:

    If changing the value of this property leads to an actual change in
    the current display mode, the split view controller animates the
    resulting change.

    Hopefully there is a better way to do this that actually animates the change.

    I was able to have the desired behavior in a Xcode 6.3 Master-Detail Application (universal) project by adding the following code in the MasterViewController‘s - prepareForSegue:sender: method:

    if view.traitCollection.userInterfaceIdiom == .Pad && splitViewController?.displayMode == .PrimaryOverlay {
        let animations: () -> Void = {
            self.splitViewController?.preferredDisplayMode = .PrimaryHidden
        }
        let completion: Bool -> Void = { _ in
            self.splitViewController?.preferredDisplayMode = .Automatic
        }
        UIView.animateWithDuration(0.3, animations: animations, completion: completion)
    }
    

    The complete - prepareForSegue:sender: implementation should look like this:

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "showDetail" {
            if let indexPath = self.tableView.indexPathForSelectedRow() {
                let object = objects[indexPath.row] as! NSDate
                let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
                controller.detailItem = object
                controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
                controller.navigationItem.leftItemsSupplementBackButton = true
    
                if view.traitCollection.userInterfaceIdiom == .Pad && splitViewController?.displayMode == .PrimaryOverlay {
                    let animations: () -> Void = {
                        self.splitViewController?.preferredDisplayMode = .PrimaryHidden
                    }
                    let completion: Bool -> Void = { _ in
                        self.splitViewController?.preferredDisplayMode = .Automatic
                    }
                    UIView.animateWithDuration(0.3, animations: animations, completion: completion)
                }
            }
        }
    }
    

    Using traitCollection may also be an alternative/supplement to displayMode in some projects. For example, the following code also works for a Xcode 6.3 Master-Detail Application (universal) project:

    let traits = view.traitCollection
    if traits.userInterfaceIdiom == .Pad && traits.horizontalSizeClass == .Regular {
        let animations: () -> Void = {
            self.splitViewController?.preferredDisplayMode = .PrimaryHidden
        }
        let completion: Bool -> Void = { _ in
            self.splitViewController?.preferredDisplayMode = .Automatic
        }
        UIView.animateWithDuration(0.3, animations: animations, completion: completion)
    }
    

    The code below hides the master view with animation

    UIView.animateWithDuration(0.5) { () -> Void in
                self.splitViewController?.preferredDisplayMode = .PrimaryHidden
            }
    

    Just improving a little bit the answers listed here already, the following code is working properly for me, and it also handles the animation smoothly:

    extension UISplitViewController {
        func toggleMasterView() {
            var nextDisplayMode: UISplitViewControllerDisplayMode
            switch(self.preferredDisplayMode){
            case .PrimaryHidden:
                nextDisplayMode = .AllVisible
            default:
                nextDisplayMode = .PrimaryHidden
            }
            UIView.animateWithDuration(0.5) { () -> Void in
                self.preferredDisplayMode = nextDisplayMode
            }
        }
    }
    

    and then, as mentioned, you just use the extended function anywhere in your View controllers

    self.splitViewController?.toggleMasterView()
    

    Modifying the answers above this is all I needed in a method of my detail view controller that configured the view:

     [self.splitViewController setPreferredDisplayMode:UISplitViewControllerDisplayModePrimaryHidden];
    

    Of course it lacks the grace of animation.

    My solution in the Swift 1.2

      override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
        var screen = UIScreen.mainScreen().currentMode?.size.height
        if (UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad) || screen >= 2000 && UIDevice.currentDevice().orientation.isLandscape == true  && (UIDevice.currentDevice().userInterfaceIdiom == .Phone){
            performSegueWithIdentifier("showDetailParse", sender: nil)
            self.splitViewController?.preferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryHidden
        } else if (UIDevice.currentDevice().userInterfaceIdiom == .Phone) {
            performSegueWithIdentifier("showParse", sender: nil)
        }
    }
    

    for iPad add Menu button like this

    UIBarButtonItem *menuButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"burger_menu"]
                                                                           style:UIBarButtonItemStylePlain
                                                                          target:self.splitViewController.displayModeButtonItem.target
                                                                          action:self.splitViewController.displayModeButtonItem.action];
    [self.navigationItem setLeftBarButtonItem:menuButtonItem];
    

    This work great with both landscape and portrait mode.
    To programmatically close the popover vc you just need to force the button action like this

    [self.splitViewController.displayModeButtonItem.target performSelector:appDelegate.splitViewController.displayModeButtonItem.action];
    

    try

    let svc = self.splitViewController
    svc.preferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryHidden