Prototype Table View Cells Not Working With VoiceOver

Just when I thought I had learnt everything there was to know about storyboards I hit another bug today when using VoiceOver on iOS 5.

VoiceOver And Out

The problem is easily reproducible using the WorldFacts example app that I first used to examine prototype cells and storyboards. Unfortunately when running on a device with iOS 5.1.1 with VoiceOver turned on the application crashes as follows:

*** Assertion failure in -[UITableView _createPreparedCellForGlobalRow:withIndexPath:], /SourceCache/UIKit/UIKit-1914.85/UITableView.m:6061
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'

Note that you must be running on a device, not the simulator, to reproduce the problem.

For anybody who made it through my post yesterday on adding a search bar to the project this should look familiar. The problem is with the same line of code in tableView:cellForRowAtIndexPath: that is supposedly guaranteed to create or dequeue a table view cell using the prototype cell from the storyboard:

UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:UYLCountryCellIdentifier];

Yesterday the problem was caused by the search results controller creating a new table view that is not associated with our prototype cell. The not too great workaround of using self.tableView to retrieve the cell saved the day even if it makes me uncomfortable.

Today the problem seems to be that VoiceOver or more probably the UIAccessibility API is interfering with the loading of the prototype cell. I cannot see an easy workaround for this other than creating the custom cell in a separate XIB file and loading it manually the old-fashioned way.

Note that the problem does not occur for static table view cells.

Fallback To UINib

Since I first created this app with a manually loaded custom table view cell it is not too difficult to add back the old code. For full details you can refer to the original post. In essence we need to create the custom table view cell in a separate NIB file and connect it to an outlet in our table view controller. We can lazily instantiate a UINib property to cache the table view cell:

- (UINib *)countryCellNib {
  if (!_countryCellNib) {
    _countryCellNib = [UINib nibWithNibName:@"CountryCell" bundle:nil];
  }
  return _countryCellNib;
}

We can now use the UINib object to create a new cell anytime dequeueReusableCellWithIdentifier fails to return a cell. The relevant code in the tableView:cellForRowAtIndexPath: method is now as follows:

UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:UYLCountryCellIdentifier];

if (cell == nil) {
  [self.countryCellNib instantiateWithOwner:self options:nil];
  cell = self.countryCell;
  self.countryCell = nil;
}

This fixes the problem with VoiceOver but means that any time you use a custom prototype cell with storyboards you have to create a duplicate copy of the cell in a standalone XIB file and take care of loading it. This obviously somewhat reduces the benefit of using storyboards in the first place.

Bug Reports

Whilst this is a new problem for me it is actually a long known bug in iOS 5. I have only verified it with iOS 5.1.1 but I found Open Radar bug reports indicating it was present in iOS 5.0. The first report I found was filed back in December 2011, see 10550644 and duplicated many times since then. See also 10675427 and 10763569 for other reports.

I will hold off on commenting on the state of this bug in iOS 6 until we get the final release. However even if it is fixed in iOS 6 that does not help if the bug remains for iOS 5. My guess is that most developers are only now starting to move the minimum deployment target to iOS 5 and it will be some time before iOS 6 can required. In the meantime I will add my own bug report to the list and include workaround code anytime I use storyboards.

I have updated the example WorldFacts Xcode project in my GitHub CodeExamples repository if you want to check out the changes in detail.

Updated 11 September 2012: See this post for an additional workaround to ensure storyboard segues work for cells created with VoiceOver active.