UISplitView button missing after detail replace segue
Asked Answered
C

2

6

I have a master detail iPad interface set up with storyboard to provide a replace segue on the detail view controller. This works fine to replace the detail controller, however the bar button to display the master controller is missing in certain situations.

If I do the segue while in portrait, the bar button is missing because the willHideViewController: delegate method is never called. I am setting the delegate to the new detail controller when prepareForSegue: is called from the master.

When the button is missing, I can rotate the iPad to landscape then back to portrait and the button will then appear.

In prepareForSegue:

UINavigationController *nav = [segue destinationViewController];
    UIViewController *destinationViewController = nav.topViewController;
    if ([destinationViewController conformsToProtocol:@protocol(UISplitViewControllerDelegate)]) {
        self.splitViewController.delegate = destinationViewController;
    }
    else {
        self.splitViewController.delegate = nil;
    }

In the detail controllers:

#pragma mark - Split view

- (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController
{
    barButtonItem.title = NSLocalizedString(@"MasterButton", @"Master");
    [self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];
    self.masterPopoverController = popoverController;
}

- (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
    // Called when the view is shown again in the split view, invalidating the button and    popover controller.
    [self.navigationItem setLeftBarButtonItem:nil animated:YES];
    self.masterPopoverController = nil;
}
Charybdis answered 6/1, 2014 at 16:14 Comment(1)
I've just encountered this. It seems the methods above don't get re-called after the detail view is loaded from storyboard (with a fresh toolbar w/o the main button and these delegate methods are not being re-called. I don't have a solution for this yet, but if you found one please share/answer your own question.Psf
G
1

I finally cracked it, after looking at the iOS8 Day by Day course, namely the splitViewController chapter 18.

What happened for me was similar to this thread. Initially the SpliViewController made the DetailViewController show the 'Expand' button. After a segue, this would disappear. So the initial thought was that my SplitViewController, onViewLoad(), touched the DetailViewController. When the DetailViewController got recreated, this custom behavior would not be reproduced. Here is my Storyboard just so you can identify the objects in code more easily: enter image description here Please note both Master and Detail Controller have Navigation Controllers (I understand this is how you can get them to behave with nav bars, I don't really know though I am a hobbyist at iOS).

So after commenting some code block by block, in TopStuffSpliViewController I found the code line that made this behavior:

let detailNavVC = self.childViewControllers.last as! UINavigationController
    detailNavVC.topViewController!.navigationItem.leftBarButtonItem = self.displayModeButtonItem()

Now it was just a matter of recreating this behavior EACH TIME the DetailController loaded:

override func viewDidLoad() {
        super.viewDidLoad()
....
if (self.navigationController?.splitViewController?.collapsed == false {
    self.navigationItem.leftBarButtonItem = self.navigationController?.splitViewController?.displayModeButtonItem()}

Careful, if you ignore the if statement you will also hide the back button on the single detail controller screens like iPhone 4S! And that's it, it works as expected now!

Gi answered 11/11, 2015 at 13:44 Comment(0)
N
0

I ran into the same situation, and I resolved if by passing the barButton value from the current detailViewController (which is being replaced) to the destination detailViewController (The one thats replacing) in the prepareForSegue method.

The steps are:

  1. Store the barButton as a property in the UISplitViewDelegate methods

    So, in the DetailViewControllers add:

    @property (nonatomic, strong) UIBarButtonItem *rootPopoverButtonItem;
    

    And in the delegate methods:

    -(void)splitViewController:(UISplitViewController *)svc willHideViewController:
    (UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem *)barButtonItem
     forPopoverController:(UIPopoverController *)pc
    {
         barButtonItem.title = @"Master";
         [self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];
         self.rootPopoverButtonItem = barButtonItem; //Storing the barButton
         self.masterPopoverController = pc;
    
    }
    
    -(void)splitViewController:(UISplitViewController *)svc willShowViewController:
    (UIViewController *)aViewController 
    invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
    {
    
       // Called when the view is shown again in the split view, invalidating the button and popover controller.
    
        [self.navigationItem setLeftBarButtonItem:nil animated:YES];
        self.rootPopoverButtonItem = nil;  //Storing the barButton
        self.masterPopoverController = nil;
    
    }
    
  2. Now in your - (void)viewDidLoad read the stored Value and display. If the barButton was showing in the viewController that is being replaced, the current VC will display it.

      [self.navigationItem setLeftBarButtonItem:self.rootPopoverButtonItem animated:YES];
    
  3. Now In the master view controller include the following in the prepareForSegue...

          UISplitViewController *splitViewController = (UISplitViewController *)self.view.window.rootViewController;
          UINavigationController *currentNavigationController = [splitViewController.viewControllers lastObject];
          UINavigationController *navigationController = [segue destinationViewController];
    
          DetailViewController *destinationDetailViewController=(DetailViewController *)[navigationController topViewController];
          DetailViewController *currentDetailViewController =(DetailViewController *)[currentNavigationController topViewController];
    
          splitViewController.delegate = destinationDetailViewController;//Needed for passing the delegate
    
          if(currentDetailViewController.rootPopoverButtonItem !=nil)
          {
    
          destinationDetailViewController.rootPopoverButtonItem = currentDetailViewController.rootPopoverButtonItem;
    
          }
    

This is not an elegant method..but worked for me without much overhead. There are other methods (more elegant) available if you can use SubstitutableDetailViewController protocol. But it looked like too much work, and beyond by capability.

Ne answered 29/8, 2014 at 21:33 Comment(1)
Do you happen to have a Swift (short) version of the code? Basically what you're doing is saving context and restoring it on instance change? (From an Android's programmer POV)Gi

© 2022 - 2024 — McMap. All rights reserved.