Correct way to create and restore UIViewControllers during state restoration?
Asked Answered
T

1

9

I'd like to do state restoration in my app that doesn't use story boards. I'm seeing my primary app ViewController instantiated twice during state restoration - how do you ensure that it's only created once?

The way I understand the flow, application:willFinishLaunchingWithOptions and application:didFinishLaunchingWithOptions would use a commonInit method which would setup the applications UIWindow and its rootViewController. In my case, the rootViewController is a UINavigationController with a class named 'MyMainViewController' serving as UINavigation's rootViewController.

Along side this I'm also handling willEncodeRestorableStateWithCoder and didDecodeRestorableStateWithCoder, respectively. But it seems that by the time I get to my didDecodeRestorableStateWithCoder, I see two separate instances of MyMainViewController created.

What's the way to ensure that only one UIViewController is created during restoration?

Order of calls during restoration:

  • Create new instance MyMainViewController (#1) via application:willFinishLaunchingWithOptions:
  • MyMainViewController's viewControllerWithRestorationIdentifierPath:coder invoked and MainViewController is restored (#2)
  • application:didDecodeRestorableStateWithCoder: is called and UINavigationController is decoded and assigned to self.window

Here's what I'm doing in my AppDelegate:

NSString * const kRootViewControllerKey = @"RootViewControllerKey";

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self commonInitWithOptions:launchOptions];
    return YES;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self commonInitWithOptions:launchOptions];
    return YES;
}

- (void)commonInitWithOptions:(NSDictionary *)launchOptions {

    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^ {

        // While this will be called only once during the lifetype of the app, when the process is killed 
        // and restarted, I wind up with an instance of MyMainViewController created first from here
        // and then once again, during MyMainViewController's viewControllerWithRestorationIdentifierPath:coder 
        // that's invoked later on. 

        UIViewController *rootViewController = [MyMainViewController alloc] init];
        UINavigationController *aNavController = [[UINavigationController alloc] initWithRootViewController:rootViewController];

        aNavController.navigationBarHidden = YES;
        aNavController.restorationIdentifier = NSStringFromClass([aNavController class]);

        UIWindow *aWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        aWindow.rootViewController = aNavController;
        aWindow.restorationIdentifier = NSStringFromClass([window class]);

        self.window = aWindow;
    });
}

// Encode app delegate level state restoration data
- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.window.rootViewController forKey:kRootViewControllerKey];
}

// Decode app delegate level state restoration data
- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder {

    // Find the preserved root view controller and restore with it
    UINavigationController *navControlller = [coder decodeObjectForKey:kRootViewControllerKey];

    if (navControlller) {
        self.window.rootViewController = navControlller;
    }

}
Trudge answered 18/7, 2013 at 22:30 Comment(2)
did you ever find a fix for this? I'm running into the exact same problem, seeing my view controller being init'd twice.Oquinn
Nope - never did. I'm not sure how to work around it as I cannot move to using storyboards.Trudge
P
0

There's only ever supposed to be one instance of my root view class, so I solved it by adding a class method to alloc and init the class only once and return the value otherwise:

+ (id) initOnce {
    static id view_ref;

    if(!view_ref)
        view_ref = [[UIViewController alloc] init];

    return view_ref;
}

Now when the class is initialized via [UIViewController initOnce], the same view reference always gets returned, whether during willFinishLaunchingWithOptions or viewControllerWithRestorationIdentifierPath.

Perfect answered 18/2, 2014 at 19:33 Comment(1)
Also, you can just not set the .restorationClass on the root view and UIKit state restoration seems to do "The Right Thing".Perfect

© 2022 - 2024 — McMap. All rights reserved.