UIViewController lifecycle calls in combination with state restoration

Funny enough the decoding sequence is even different and exactly:

 +viewControllerWithRestorationIdentifierPath:coder:
 awakeFromNib
 viewDidLoad
 decodeRestorableStateWithCoder:
 viewWillAppear
 viewDidAppear

and it totally makes sense like this.


@property (nonatomic) BOOL firstLoad;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.firstLoad = YES;
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    if (self.firstLoad) {
        [self setupUI];
        self.firstLoad = NO;
    }
}

Thanks to @calvinBhai for the suggestion.


From the book "Programming iOS 9: Dive Deep into Views, View Controllers, and Frameworks" pages 386-387

The known order of events during state restoration is like this:

  1. application:shouldRestoreApplicationState:
  2. application:viewControllerWithRestorationIdentifierPath:coder:
  3. viewControllerWithRestorationIdentifierPath:coder:, in order down the chain
  4. viewDidLoad, in order down the chain; possibly interleaved with the foregoing
  5. decodeRestorableStateWithCoder:, in order down the chain
  6. application:didDecodeRestorableStateWithCoder:
  7. applicationFinishedRestoringState, in order down the chain

You still don’t know when viewWillAppear: and viewDidAppear: will arrive, or whether viewDidAppear: will arrive at all. But in applicationFinishedRestoringState you can reliably finish configuring your view controller and your interface.


If you're doing state restoration programatically (i.e. not using storyboards), you can use + viewControllerWithRestorationIdentifierPath:coder:, init the view controller there and use whatever you need from the coder to do your pre-viewDidLoad initialization.

+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
    if ([[identifierComponents lastObject] isEqualToString:kViewControllerRestorationIdentifier]) {
        if ([coder containsValueForKey:kIDToRestore]) {
            // Can only restore if we have an ID, otherwise return nil.
            int savedId = [coder decodeIntegerForKey:kIDToRestore];
            ViewController *vc = [[ViewController alloc] init];
            [vc setThingId:savedId];
            return vc;
        }
    }

    return nil;
}

I've found that trying to implement state restoration has shown up bad programming practices in my code, like packing too much into viewDidLoad. So while this works (if you're not using storyboards), the other option is to refactor how you're setting up your view controllers. Instead of using a flag, move code pieces to their own methods and call those methods from both places.