iOS state restoration discarding navigation stack

655 views Asked by At

tl;dr State restoration process appears to be happening, but the stack (non-root) view controllers are not ending up in the app after restoration completes.

I'm trying to implement state restoration in an app, which doesn't use any .nibs or storyboards. It's a fairly basic structure: the window's rootViewController is a UINavigationController whose own rootViewController and all other child view controllers are UITableViewControllers (eg, Window > Nav Ctrl > Table View Ctrl > Table View Ctrl > etc). None of the view controllers are ever repeated (ie each item on the stack is a distinct UITableViewController subclass)

As I have no view controllers being created with storyboards, I have the designated initializer for each view controller class setting the restorationIdentifier and restorationClass.

When the app is being restored, I am seeing decoding happening for each view controller that was present when the app went into the background (eg, the nav controller, the podcast list controller, and the podcast detail controller), but the end result of the restoration is always the navigation controller showing up with it's normal root view controller (the podcast list controller).

The problem seems very similar to this question, but I am definitely calling super in the encode- and decodeRestorableStateWithCoder: methods on my view controllers (when present), so that solution doesn't help me.

I have watched the WWDC videos and gone through many tutorials, and while I seem to be hitting all the requirements, something is not working the way it should be.

The only thing I can think is happening is the restoration is happening, but my default initialization code is replacing the restored navigation view controller stack with a "fresh" one that only includes the root. According to the WWDC video, the window and root view controller should be set up normally prior to state restoration, and that shouldn't impact the final app state after restoration.

I guess the one question mark for me is what should actually be happening in the viewControllerWithRestorationIdentifierPath: method of my UINavigationController. Should the rootViewController be set as I am doing? And if not, what else would happen? I couldn't actually find any working example code where a navigation controller was being restored and it wasn't created from a nib or storyboard. Other than that I'm stumped.

Implementation code

FLAppDelegate.m

# pragma mark - UIApplicationDelegate
# pragma mark Monitoring App State Changes

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  // Window and root VC set up in window getter
  [self.window makeKeyAndVisible];

  return YES;
}

# pragma mark Managing App State Restoration

- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder {
  return YES;
}

- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder {
  return YES;
}

#pragma mark Providing a Window for Storyboarding

- (UIWindow *)window {
  if (!_window) {
    _window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
    _window.rootViewController = self.navigationController;
  }

  return _window;
}

- (FLNavigationController *)navigationController {
  if (!_navigationController) {
    FLPodcastTableViewController* podcastViewController = [[FLPodcastTableViewController alloc] initWithStyle:UITableViewStylePlain];
    _navigationController = [[FLNavigationController alloc] initWithRootViewController:podcastViewController];
  }

  return _navigationController;
}

FLNavigationController.m

- (id)initWithRootViewController:(UIViewController *)rootViewController {
  self = [super initWithRootViewController:rootViewController];
  if (self) {
    self.restorationIdentifier = @"FLNavigationController";
    self.restorationClass = self.class;
  }
  return self;
}

#pragma mark - UIViewControllerRestoration

+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
  FLPodcastTableViewController* podcastViewController = [[FLPodcastTableViewController alloc] initWithStyle:UITableViewStylePlain];
  return [[self alloc] initWithRootViewController:podcastViewController];
}

FLPodcastTableViewController.m

- (id)initWithStyle:(UITableViewStyle)style {
  self = [super initWithStyle:style];
  if (self) {
    [self.tableView registerClass:FLPodcastTableViewCell.class forCellReuseIdentifier:FLPodcastTableViewCellIdentifier];

    self.restorationIdentifier = @"FLPodcastTableViewController";
    self.restorationClass = self.class;
  }
  return self;
}

#pragma mark - UIViewControllerRestoration

+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
  return [[self alloc] initWithStyle:UITableViewStylePlain];
}

FLPodcastEpisodeTableViewController.m

- (id)initWithPodcast:(FLPodcast *)podcast {
  self = [self initWithStyle:UITableViewStylePlain];
  if (self) {
    self.podcast = podcast;

    self.restorationIdentifier = @"FLPodcastEpisodeTableViewController";
    self.restorationClass = self.class;

    [self.tableView registerClass:FLPodcastEpisodeTableViewCell.class forCellReuseIdentifier:FLPodcastEpisodeTableViewCellIdentifier];
  }
  return self;
}

#pragma mark - UIViewControllerRestoration

+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
  FLPodcastEpisodeTableViewController* viewController = nil;

  NSString* podcastURI = [coder decodeObjectForKey:kPodcastURLKey];
  NSURL* podcastURL = [NSURL URLWithString:podcastURI];

  FLPodcast* podcast = [FLPodcast podcastWithURL:podcastURL];

  if (podcast) {
    viewController = [[self alloc] initWithPodcast:podcast];
  }

  return viewController;
}

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
  [coder encodeObject:self.podcast.feedURL.absoluteString forKey:@kPodcastURLKey];

  [super encodeRestorableStateWithCoder:coder];
}

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
  [super decodeRestorableStateWithCoder:coder];
}
0

There are 0 answers