Use Your Loaf

[[brain engage] write]

Bug Table View State Not Restored When Embedded in Navigation Controller

Sharing this bug report (rdar://13438788 duplicate of #12156999) for anybody attempting to get iOS 6 state preservation and restoration working for a table view embedded in a navigation controller.

Problem Description

Summary

Using iOS 6 state preservation and restoration the first visible row of a UITableView is not restored when the table view is embedded in a UINavigationController.

Steps to Reproduce

(using Xcode 4.6.1 and iOS 6.1.3)

  1. Create a new Xcode project using the iOS Single View Application template, specifying iPhone, Use Storyboards, Use Automatic Reference Counting.

  2. Enable state preservation and restoration in the App delegate by implementing application:shouldSaveApplicationState: and application:shouldRestoreApplicationState:. Both methods should return YES.

  3. Add a new class to the project that is a subclass of UITableViewController (without a XIB). Implement the two mandatory UITableViewDataSource delegate methods to provide a basic data source:

     - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
     {
       return 1000;
     }
    
     - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
     {
       static NSString *CellIdentifier = @"BasicCell";
       UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
       cell.textLabel.text = [NSString stringWithFormat:@"Cell #%d",indexPath.row];
       return cell;
     }
    
  4. Replace the storyboard contents with a single Table View Controller. Change the class of the controller to the name of the class created in step 3.

  5. In the storyboard set the Table View Cell style to Basic and set the cell reuse identifier to “BasicCell” as specified in step 3.

  6. Embed the table view controller in a navigation controller (Editor > Embed In > Navigation Controller).

  7. Set the Storyboard ID for the navigation controller and the table view controller and select the “Use Storyboard ID” option to use the same identifier for the restortation ID. Also set the restoration ID for the table view.

  8. Build and run

  9. Scroll down the table view until row 50 is the first visible row. Use the home button to move the App to the background and then use Xcode to stop the App.

  10. Run the app again to see the restored state.

Expected Results

The first visible row should be row 50 which was the first visible row when the App was terminated.

Actual Results

The first visible row is row 0.

Regression

Delete the navigation controller from the storyboard and repeat the test. On restoring the app the first visible row is row 50.

Notes

A sample Xcode project to reproduce the problem can be found in my GitHub Code Examples repository. The project contains two storyboards to demonstrate state restoration of a table view both when it is the root view and when it is embedded in a navigation controller:

  • If the Target settings are used to set the Main Storyboad to NavStoryboard the user interface consists of UITableView embedded in a Navigation Controller. In this use case the table view state is not restored.

  • If the Target settings are used to set the Main Storyboard to MainStoryboard the user interface consists of a single UITableView which does have state restored as expected.

Workarounds

Apple engineers suggested the following workaround which I can confirm resolves the issue:

Please do the following in the ViewController that owns the TableView:

- (void) encodeRestorableStateWithCoder:(NSCoder *)coder {
  // Save anything relevant for our role as the TableView's DataSource
  [super encodeRestorableStateWithCoder:coder];
}

- (void) decodeRestorableStateWithCoder:(NSCoder *)coder {
  [super decodeRestorableStateWithCoder:coder];
  // Restore whatever we need as the TableView's DataSource, and then...
  [self.tableView reloadData];
}

I have also found that implementing a similar workaround with the UIDataSourceModelAssociation protocol methods works:

- (NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view
{
  // Determine identifier for the current index path
  NSString *identifier = ....
  return identifier;
}

- (NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view
{
  // Determine index path for the identifier
  NSIndexPath *indexPath = ...

  // Force a reload when table view is embedded in nav controller
  // or scroll position is not restored
  [self.tableView reloadData];

  return indexPath;
}

The key to both workarounds is to force a reload of the table view data once the state has been restored.

Downloading Old iOS SDK Documentation

I recently did a clean install of Xcode 4.6.1 on a new Mac and found I was missing some of the earlier iOS 4.x and 5.x documentation sets. This post shows how to get that older documentation back should you need it.

Xcode Documentation Sets

Xcode Documentation Sets (“docsets”) are packaged as standard OS X bundles and updated via an RSS or Atom feed. It is beyond the scope of this post but you can find more details on how to integrate your own documentaion with Xcode in the Apple Documentation Set Guide.

A clean install of Xcode will download and install the docsets for the current versions of iOS, OS X and Xcode. For example, Xcode 4.6.1 which is the current version available in the Mac App Store at time of writing includes feed subscriptions to the docsets for iOS 6.1, OS X 10.8 and Xcode 4.6:

The documentation for the most commonly used iOS and Mac OS X developer libraries can be downloaded and installed from the Downloads pane of the Xcode preferences (⌘,).

Unfortunately since in my case I have recently done a clean install on a new machine I am missing some of the earlier iOS 4.x and 5.x documentation sets.

Adding Old Docsets to Xcode

As long as you know the URL of the docset feed you need it is easy to add it back to Xcode. If you have access to an older install of Xcode on a different machine which has the missing docsets you can use it to find the feed URL’s. Luckily even if you cannot find the feed you need on another installation it is not too difficult to guess the URL.

From the Documentation tab of the Xcode Downloads preferences pane you can check the URL of a selected docset in the lower information window. In the example below I have highlighted the feed URL for the iOS 6.1 docset:

The following URL’s cover the most common iOS docsets (I have shown them as http:// but you can also specify https:// or even feed://):

http://developer.apple.com/rss/com.apple.adc.documentation.AppleiPhone6.1.atom
http://developer.apple.com/rss/com.apple.adc.documentation.AppleiPhone6.0.atom
http://developer.apple.com/rss/com.apple.adc.documentation.AppleiPhone5_1.atom
http://developer.apple.com/rss/com.apple.adc.documentation.AppleiPhone5_0.atom
http://developer.apple.com/rss/com.apple.adc.documentation.AppleiPhone4_3.atom
http://developer.apple.com/rss/com.apple.adc.documentation.AppleiPhone4_2.atom
http://developer.apple.com/rss/com.apple.adc.documentation.AppleiPhone4_1.atom

To subscribe to a docset you need to use the small “+” button in the middle of the window (see previous screenshot). Copy and paste the required URL into the dialog and click the Add button. You should then see the new feed in the list of docsets and you can click the Install button to download it.

Manually Download a docset

If for some reason you want to download the docset without using Xcode you can manually fetch the feed and use it to get the direct URL to the docset archive. For example to manually retrieve the iOS 4.3 docset, first retrieve the atom feed using curl and grep for the link elements in the feed (note that I am using https as the http link is actually just a redirect):

$ curl https://developer.apple.com/rss/com.apple.adc.documentation.AppleiPhone4_3.atom | grep link
    <link rel="self" href="http://developer.apple.com/rss/com.apple.adc.documentation.AppleiPhone4_3.atom"></link>
    <link rel="enclosure" type="application/octet-stream" href="http://devimages.apple.com/docsets/20110309/com.apple.adc.documentation.AppleiOS4_3.iOSLibrary.xar" length="477703763"></link>
    <link rel="enclosure" type="application/octet-stream" href="http://devimages.apple.com/docsets/20110720/com.apple.adc.documentation.AppleiOS4_3.iOSLibrary.xar" length="229887175"></link>

In this case the last update to the docset in the feed was on 2011-07-20 so we can retrieve that XML archive file:

$ curl http://devimages.apple.com/docsets/20110720/com.apple.adc.documentation.AppleiOS4_3.iOSLibrary.xar -o AppleiOS4_3.iOSLibrary.xar

To get the docset file package uncompress the archive using the xar command:

$ xar -xf AppleiOS4_3.iOSLibrary.xar

If you want to view the resulting docset package in Xcode you will need to move it to the Xcode docsets directory at the following location:

~/Library/Developer/Shared/Documentation/DocSets

Uninstall Old Docsets

Searching for a class or framework in the Xcode organiser can be confusing when you have lots of old documentation installed. You can end up with at least one result from each version of the documentation which means you need to be careful that you are reading the latest version.

Unfortunately you cannot remove an Apple docset using the ‘-’ button in the preferences pane. If you want to uninstall an Apple docset you can instead manually delete the file from the Xcode DocSets directory. This is usually located under ~/Library/Developer/Shared/Documentation/DocSets. You can also check the location of a docset from the documentation tab of the Downloads pane in the Xcode preferences window. Select the documentation set you want to uninstall and look in the information area at the bottom of the window.

If you click on the installed location of the documentation set it will open in Finder with the doc set selected. Deleting the docset file will remove it from the system.

Note that Xcode does not provide a way for you to completely remove the feed subscription for an Apple docset. Once you have the deleted the documentation set the “Installed” status will change back to an “Install” button which is useful should you need to download it again in the future. It does however mean that you end up with an ever longer list of historical documentation feeds.

Core Data 2nd Edition by Marcus Zarra

There are many books on general iOS development but they often only provide brief coverage of Core Data. I was therefore pleased to see that my favourite book on the topic, Core Data, Second Edition by Marcus S. Zarra (The Pragmatic Bookshelf, January 2013) has recently received a major update.

First Impressions

The first edition of Core Data by Marcus Zarra was the book I turned to when I wanted to learn Core Data. Whilst it is still a great book it was starting to get a little dated. It was also heavily biased to using Core Data on the OS X desktop platform rather than iOS. This was not such a huge problem as many of the key concepts are valid for both platforms. However the use of Core Data for iOS was restricted to a single chapter which meant that iOS specific issues and features such as NSFetchedResultsController got only a brief mention.

The second edition has been substantially updated and the change in emphasis is evident from the change in subtitles of the two editions. The first edition was subtitled Apple’s API for Persisting Data on Mac OS X where for the 2nd Edition it is Data Storage and Management for iOS, OS X and iCloud. The growing army of iOS developers has had an impact:

The largest number of Objective-C developers develop only for iOS, and therefore we will keep our focus there.

It is also worth mentioning that the book covers the latest updates to the Core Data framework as shipped with iOS 6 and OS X 10.8. In fact, Marcus goes as far as to recommend not using the newest Core Data API’s if you need backward compatibility:

However, iOS 5.0 (and Mac OS X 10.7) was a stumble, a misstep. It is far better to skip it and move on to iOS 6.0. But what about when your client/boss/customer requires you to be compatible with iOS 5.0? My advice is to use only the Core Data APIs that were available in iOS 4.x.

In Depth

The first half of the book provides an in depth coverage of the key concepts of Core Data. The example code is for an iOS application and a whole chapter is dedicated to covering NSFetchedResultsController reflecting the new focus of the book.

The next three chapters on Core Data versioning and migration, performance tuning and threading are all essential reading. Even if you consider yourself a Core Data expert there is a lot of good practical advice here. The treatment of migrations covers both lightweight and heavy migrations with sufficient detail to understand why heavy migrations can be a headache. The discussion on performance tuning has good advice about handling binary data and also on when to denormalize data. This is a good lesson to learn if you are coming to Core Data with an SQL database background. Finally the chapter on threading covers how to use Core Data in a way that is thread safe.

This expanded and updated edition now also provides a detailed discussion of using Core Data with iCloud. This covers both the traditional Core Data stack and the newer UIManagedDocument API. Marcus does mention the problems Apple has had getting this to work reliably:

Unfortunately, as has been demonstrated numerous times in the past, syncing is hard, very hard. Apple did not get it working acceptably in iOS 5.0 or OS X 10.7. It was not until iOS 6.0 and OS X 10.8 that iCloud has become truly reliable.

I still do not feel confident syncing the standard Core Data stack with iCloud even with iOS 6.0. The known issue when the user turns off iCloud is covered and a number of experienced developers are still reporting issues. Hopefully when Apple do finally fix it Marcus will update the book with the details.

The second half of the book moves to using Core Data with OS X. If you are primarily an iOS developer this can still be interesting reading. Firstly because you should realise that you already know much of what is required. Secondly you can get a deeper understanding of some key technologies (if you will excuse the pun) such as KVC and KVO when used on the desktop platform.

The last three chapters of the book are somewhat bonus material. There is a discussion on how to integrate Core Data with Spotlight and Quick Look on the desktop. A further example of using KVC to store dynamic paramaters and an “academic” investigation on remotely accessing a Core Data repository using Bonjour. Interesting reading but not essential to understanding Core Data.

Likes and Dislikes

There is not much to dislike. This is one of those books that works well both for introductory and advanced audiences. If you are new to Core Data the basics are covered to get you up to speed. On the other hand if you have been using Core Data for a while you can jump straight to the chapters on migrations, performance and threading and learn something.

I have a preference for technical books that are written by experienced practitioners with a strong point of view. I don’t want a book that simply reformats the information in Apple’s documentation. The parts of this book that I like best are when Marcus is giving us his opinion on how to get the best from Core Data and what you should avoid. Overall a great book that just got better.

Final Comments

You can buy the dead-tree version of the book from the usual places but I prefer to get the ebook version direct from the pragprog.com web site. A final tip if you want more Marcus Zarra on Core Data I can also recommend the videos he has done with Scotty over at iDeveloper TV.