Restoration Classes and UIWebViews

I previously covered the basics of using state preservation and restoration but for the sake of brevity I did not provide an example of how to use a Restoration Class. This post will fix that omission and also take a look at how you can implement state restoration for a UIWebView.

A Quick Recap

If you need a recap on the basics of state preservation and restoration you should refer back to the previous post. In brief though the key points are as follows:

  • The Application Delegate must opt-in
  • All View Controllers and Views to be preserved/restored must have a restoration identifier.
  • View Controllers which are part of the main storyboard or Nib file will be created on restoration as part of the application launch.
  • View Controllers which are not otherwise created on application launch should either implement a restoration class or rely on the Application Delegate to create them.
  • For restored view controllers implement encodeRestorableStateWithCoder: and decodeRestorableStateWithCoder: where necessary to also restore the state of the view controller.

The rest of this post is going to examine the situation where we need a restoration class to restore a view controller by building on the example App I introduced in the last post.

Using A Restoration Class

Up to now all of the view controllers in our sample App are contained in the main storyboard. This means that once we have assigned restoration identifiers they will all get restored with the Storyboard on application launch.

To illustrate the use of a restoration class to restore a view controller that is not loaded as part of the storyboard I have added a button to the settings screen which can be used to display a web view:

The action method invoked by the button creates a new view controller from a Nib file which is used to show the web view. The view controller is pushed onto the existing navigation controller stack and an initial page is loaded:

- (IBAction)pushMe {
  UYLWebViewController *wvc = [[UYLWebViewController alloc] initWithNibName:@"UYLWebViewController" bundle:nil];
  wvc.restorationIdentifier = @"UYLWebViewController";
  wvc.restorationClass = [UYLWebViewController class];
  [self.navigationController pushViewController:wvc animated:YES];
  [wvc showPage:@"http://useyourloaf.com"];
}

Note that we set the restoration identifier of the view controller to indicate that we want it restored. We also need to set the restoration class that will take care of creating the new instance of the controller during a restore - it is common to have a class act as its own restoration class. We will see how to implement the restoration class in a moment but we also want to restore the state of the web view. Luckily the UIWebView class already takes care of restoring itself, the details are in the UIKit documentation:

In iOS 6 and later, if you assign a value to this view’s restorationIdentifier property, it attempts to preserve its URL history, the scaling and scrolling positions for each page, and information about which page is currently being viewed. During restoration, the view restores these values so that the web content appears just as it did before.

We can set the restoration identifier of the UIWebView in the Nib file:

To implement the restoration class we need the web view controller to adopt the UIViewControllerRestoration protocol:

@interface UYLWebViewController () <UIViewControllerRestoration>

We then need to implement the class method to actually create the new instance of a view controller:

+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
  UIViewController *viewController = [[UYLWebViewController alloc] initWithNibName:@"UYLWebViewController" bundle:nil];
  viewController.restorationIdentifier = [identifierComponents lastObject];
  viewController.restorationClass = [UYLWebViewController class];
  return viewController;
}

Note that we need to remember to set the restorationIdentifier and restorationClass when we create the restored view controller. You can get the restorationIdentifier from the last object of the array of identifier components.

Now during application restoration when UIKit encounters the restoration identifier of this web view controller it will invoke the restoration class method to create a new instance of the class. Note that if we have view controller state to restore we would do that by implementing the encodeRestorableStateWithCoder: and decodeRestorableStateWithCoder: methods.

At this point you can build and run the app, navigate to the settings tab and use the button to display the web view. If you background the App and use Xcode to stop and then relaunch you should see the web view restored. However what I find actually happens is that the web view appears to be blank. I am not sure if this intended behaviour but it seems to require that the web view is reloaded after restoring the state.

Restoring the State of a UIWebView

What we need to do is force a reload of the web view when we are restoring state. One way we can do that is to introduce an instance variable to indicate when we are performing a restoration:

@property (nonatomic) BOOL restoringState;

We can then implement decodeRestorableStateWithCoder: to flag when we are doing a restore (remembering that we always need to call super):

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
  [super decodeRestorableStateWithCoder:coder];
  self.restoringState = YES;
}

Now we can test for a restore in viewDidAppear: and force the reload:

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  if (self.restoringState)
  {
    [self.webView reload];
    self.restoringState = NO;
  }
}

Now when you test restoration of the web view you should find that the URL, scaling and scrolling positions are indeed restored.

Wrapping Up

As I play more with state restoration I am finding that getting the basics to work is pretty straightforward. However as in this example with a UIWebView and as we saw previously with table views embedded in a navigation controller there are a few rough edges. You can find the updated version of the Restorer Xcode project in my GitHub CodeExamples repository.