11

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.

Gary
  • 815
  • 10
  • 16
  • 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 Aug 14 '20 at 23:56
  • Unfortunately, no, I wasn't able to find a solution. – Gary Aug 15 '20 at 06:58

2 Answers2

0

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.

  • A master View Controller does not know whether it is re-appearing because a detail View Controller is being dismissed (the horizontally Compact size class, collapsed) or because a child of a detail View Controller is being dismissed (the horizontally Regular size class, side-by-side). Thus, a master VC cannot know when to mark a detail VC as empty. Moreover, this kind of a solution would create a tight coupling between a particular class of a master VC with a particular class of a detail VC, which is probably worse than the knowledge of the Domain Model layer (a data source). – Gary Oct 26 '14 at 08:03
  • I see your point, but I do not agree that "whether a secondary VC should be [hidden] or [cleared] depends on whether the detail has contents to show". In the collapsed state, secondary VC dismissal triggering and animation look the same as non-splitter VC dismissal, so I feel it should always invalidate secondary VC contents. *** I believe a master VC and a detail VC should be decoupled: a master VC notifies a Domain Model of a particular data filtering condition and a detail VC gets notified by a Domain Model that it should update (no direct messaging between a master VC and a detail VC). – Gary Oct 27 '14 at 06:41
  • Thank you for the suggestion, but in case of `collapseSecondaryViewController:ontoPrimaryViewController:` it is the same problem back again: the delegate has no way to tell whether a detail VC has been previously expressly dismissed by a user, or not, so it cannot ever decide to hide away the secondary VC. I want to discard secondary VC contents ONLY if it has been explicitly "abandoned" by a user, who does not want to see it anymore and explicitly GOES BACK to a master VC. Rotation alone should never affect (discard) content. – Gary Oct 27 '14 at 17:27
  • Think, say, a trading application. While in the splitter collapsed mode, a user chooses the Commodities category in a master VC, enters a Trade in a detail VC, receives a Quote and decides to DISMISS. After rotation to side-by-side (landscape), the Trade re-appears. Surprise. Does it mean it was executed? Is the visible Quote still valid? When a Trade is dismissed, the contents of a detail VC should be explicitly cleared. I wholeheartedly appreciate your help, but you are answering the question of "how do I inexpensively paint my car?" with "buy a bicycle - you'll pay way less for the paint". – Gary Oct 28 '14 at 06:33
-1

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.

bio
  • 669
  • 4
  • 14
  • `viewWillAppear:` in a master View Controller is inconclusive, please see the first comment under PetahChristian's answer: "A master View Controller does not know whether it is re-appearing because a detail View Controller is being dismissed (the horizontally Compact size class, collapsed) or because a child of a detail View Controller is being dismissed (the horizontally Regular size class, side-by-side)." – Gary Oct 14 '15 at 05:03
  • An example of a detail View Controller showing irrelevant content is a time-sensitive stock quote, a financial transaction in progress or any sensitive security information. The moment a detail View Controller is dismissed, it should be explicitly cleared and the (canceled) information should never magically re-appear only because a device was rotated. – Gary Oct 14 '15 at 05:03
  • I think a UISplitViewController may not be the UI of choice if you want the detail to be dismissed and avoid its reappearance when rotating. The master should be seen as an optional view, even in a collapsed arrangement. – bio Oct 15 '15 at 06:59