iosuisplitviewcontrolleriphone-6-plusstate-restoration

iPhone 6+ state restoration with tab bar in master split view


I have a problem with supporting state restoration in iPhone 6+.

Here's my hierarchy: enter image description here

Problem is when state restoration happens in Portrait orientation and then, at later point, state decoding attempt is made while in Landscape orientation.

Explanation: In portrait mode, there is only TabBar as master view (in fact, detail view doesn't exist) so it is pushed into TabBar navigation's controller.

Then, at later point, when app tries to perform state restoration for landscape, my details view is pushed onto master navigation controller (when it should be in details).

Due to custom hierarchy I have implemented UISplitViewControllerDelegate methods accordingly, and they work fine. UISplitViewControllerDelegate methods also make sure that state restoration works in following cases:

Landscape -> Landscape
Landscape -> Portrait
 Portrait -> Portrait

what's not working is: Portrait -> Landscape because, as I said, delegate method don't get called when in not-collapsed state so view hierarchy doesn't know how to split details from master view and embed that into detail navigation controller.


Solution

  • So far, I have solved it like this: in viewWillAppear all detail controllers are cut from tab bar's navigation controllers. When storing state, device orientation is saved and SplitViewController.viewControllers (for me it won't store them automatically as it does on iOS7).

    - (void) encodeRestorableStateWithCoder:(NSCoder*)coder
    {
        [super encodeRestorableStateWithCoder:coder];
        [coder encodeObject:self.viewControllers forKey:@"viewControllers"];
        [coder encodeInteger:[UIApplication sharedApplication].statusBarOrientation forKey:@"orientation"];
    }
    
    - (void) decodeRestorableStateWithCoder:(NSCoder*)coder
    {
        [super decodeRestorableStateWithCoder:coder];
        NSArray* viewControllers = [coder decodeObjectForKey:@"viewControllers"];
        if (viewControllers.count > 0)
        {
            self.viewControllers = viewControllers;
        }
        restoredOrientation = (UIInterfaceOrientation) [coder decodeIntegerForKey:@"orientation"];
    }
    

    And here is viewWillAppear implementation:

    - (void) viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:animated];
    
        // iPhone6+: if state restoration happened while in portrait orientation and app is launched while in landscape,
        // then all detail views should be cut from master view and split details view is set appropriately
        if (firstLoad &&
                [UIScreen mainScreen].scale > 2.9 && // assure it's iPhone 6+
                UIInterfaceOrientationIsPortrait(restoredOrientation) &&
                UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication.statusBarOrientation))
        {
            UITabBarController* tbc = self.viewControllers[0];
            NSArray* detachedControllers = [tbc cutControllersFrom:DocumentViewController.class];
            if (detachedControllers.count > 0)
            {
                UINavigationController* documentNavigation = [self.storyboard
                        instantiateViewControllerWithIdentifier:@"NavigationController"];
                documentNavigation.viewControllers = detachedControllers;
                self.viewControllers = @[ self.viewControllers[0], documentNavigation ];
            }
            else // place some default no-selection controller in detail
            {
                UINavigationController* noSelectionNavigation = [self.storyboard
                        instantiateViewControllerWithIdentifier:@"NoSelectionSID"];
                self.viewControllers = @[ self.viewControllers.firstObject, noSelectionNavigation ];
            }
        }
        firstLoad = NO;
    }
    

    where cutControllersFrom method is a category on UITabBarController:

    - (NSArray*) cutControllersFrom:(Class)controllerClass
    {
        NSArray* ret;
        for (UIViewController* vc in self.viewControllers)
        {
            if (![vc isKindOfClass:UINavigationController.class])
            {
                continue;
            }
    
            UINavigationController* nc = (UINavigationController*) vc;
            NSArray* removed = [nc cutFrom:controllerClass];
            if (vc == self.selectedViewController)
            {
                ret = removed;
            }
        }
    
        return ret;
    }
    

    which calls cutFrom: method which is a category on UINavigationController:

    - (NSArray*) cutFrom:(Class)controllerClass
    {
        NSMutableArray* toRemove = [NSMutableArray array];
        BOOL startRemoving = NO;
        UIViewController* endingViewController;
    
        for (NSUInteger i = 0; i < self.viewControllers.count; i++)
        {
            UIViewController* vc = self.viewControllers[i];
            if ([vc isKindOfClass:controllerClass])
            {
                startRemoving = YES;
                endingViewController = self.viewControllers[i - 1];
            }
    
            if (startRemoving)
            {
                [toRemove addObject:vc];
            }
        }
        if (endingViewController)
        {
            [self popToViewController:endingViewController animated:NO];
        }
    
        return toRemove;
    }