11
votes

CIRCUMSTANCES: iPhone 6 Plus, UISplitViewController rotation across horizontally Compact (collapsed) and horizontally Regular (expanded) size classes.

PROBLEM: there seems to be no way to detect - in a collapsed UISplitViewController - when a detail (rightmost, secondary) View Controller, on top of a master (leftmost, primary) View Controller, is being dismissed. In a detail View Controller, both viewWillDisappear: and viewDidDisappear: always report NO for isMovingFromParentViewController and isBeingDismissed. UISplitViewController viewControllers array property is not indicative.

REASON: this problem is relevant because if a detail View Controller is not marked as (logically) "empty" (i.e. "cleared") when dismissed, upon a subsequent UISplitViewController expansion from collapsed, a detail View Controller will be re-shown with potentially (logically) irrelevant content. Moreover, when an expanded UISplitViewController is collapsing, it has no way to choose whether to present a master View Controller only, or a detail View Controller on top of a master View Controller, through the beloved-ly named splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: delegate method.

2
I know it's been a very long time, but did you figure out how to detect the dismissal? With state restoration in iOS 13, I am running into this problem.SAHM
Unfortunately, no, I wasn't able to find a solution.Gary

2 Answers

0
votes

It appears that you're trying to let the detail view controller handle marking itself as "empty."

I'd suggest moving that logic to the master view controller, so it can also handle other cases besides rotation, such as when new results arrive, or the user searches (filters) results.

Let the master view controller handle updating the detail view (either by marking its details as nil, or preferably passing it new details).

Your split view controller delegate will be able to determine how to handle the secondary view controller, and your detail view controller code will also be easier to maintain, since it won't have knowledge of, or be (tightly) coupled to a data source.

Update:

There is no master-only UISplitViewController displayMode where the secondary can't be shown. Display modes all show the secondary VC, but may show, hide, or overlay the primary VC. The user may rotate the device, or display or hide the primary VC (via presentsWithGesture or displayModeButtonItem), but the secondary VC will always shown, blank or not, for a regular size class.

Here's how Apple's Master-Detail template code determines if the secondary VC should be collapsed or discarded, when the device transitions to a horizontally compact size class.

- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController {
    if ([secondaryViewController isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[YHWHDetailViewController class]] && ([(YHWHDetailViewController *)[(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 {
        return NO;
    }
}

Note that Apple uses a property on the detail view controller as a flag.

I understand you believe the decision whether the secondary VC should be collapsed or discarded should be based on whether the user explicitly dismissed the secondary VC (while collapsed in a horizontally compact size).

I understand that you're trying to make the detail VC "clear" itself when it disappears, but assuming it's been dismissed (discarded) and you're not holding a strong reference to it, that would be a non-operation.

It's simpler if the detail goes out of existence (or the dataSource invalidates the detail).

If you're holding on to it, and can't clear the details, you'll either have to:

  • Keep some type of flag (whether a BOOL or nil object) which reflects if the detail's content has been made "irrelevant," and rely that flag to determine in the future if the secondary view controller should (once again) be collapsed or not.
  • Check the detail (before collapsing it again) against against the dataSource to see if it's content is "relevant" or "irrelevant."

If you use a detail VC property as the flag, here's the benefit. When the SVC is collapsed, and the detail VC has been "expressly dismissed," there's no longer a secondary VC to split from the primary VC.

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

    if ([primaryViewController isKindOfClass:[UINavigationController class]]) {
        for (UIViewController *controller in [(UINavigationController *)primaryViewController viewControllers]) {
            if ([controller isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)controller visibleViewController] isKindOfClass:[DetailViewController class]]) {
                return controller;
            }
        }
    }

    // No detail view present
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    UINavigationController *secondaryViewController = [storyboard instantiateViewControllerWithIdentifier:@"SecondaryViewController"];

    // Ensure back button is enabled
    UIViewController *detailViewController = [secondaryViewController visibleViewController];
    detailViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
    detailViewController.navigationItem.leftItemsSupplementBackButton = YES;

    return secondaryViewController;

}

Because you instantiate a "blank" detail VC, when the user rotates the SVC back from regular size to compact size, the SVC delegate can see that there are no details to display, so it discards the secondary VC.

That's Apple's approach, and it works really well, because it leaves the control over whether the SVC is collapsed or expanded to the user, via a rotation, a gesture, or the display mode button.

I'm sure you've seen Apple's mail app on the 6 Plus. Note, though, of what Apple does once the user rotates back to a regular size class. Even if the user pops back to the list of messages, the previously selected message is still shown when the detail reappears.

If it's possible to make your app also behave that way, I'd encourage it. It's friendly, convenient, and rewarding, because it saves the user time from having to make a (re)selection, and shows details instead of a blank view.

-1
votes

When the iPhone transitions from collapsed to expanded display (ie. portrait to landscape) the master will receive a call to -viewWillAppear:. There you can decide to show an empty detail viewcontroller or a populated "valid" one, dependent on what is selected in the master (or nothing selected at all), and by this you discard any previously displayed detail viewcontroller with potentially invalid data.

But I don't see how a detail viewcontroller, in an expanded-to-collapsed transition, should show irrelevant content. Though the master is invisible it is still intact, and so should the detail viewcontroller be.