Corporation Unknown Logo

Bughunting a Bashful Table View

I ran down a strange bug yesterday that I thought I would recount in the hopes of saving someone else the half day of frustration.

I was going along, minding my own business, implementing a typical -tableView:didSelectRowAtIndexPath: delegate method to create a view controller, push it onto the navigation stack, and then…nothing. The view didn’t appear, and the app interface became unresponsive. Numerous pauses in the debugger showed what would appear to be normal stacks in the running of the app—no infinite recursion going on, thankfully.

The view was actually in a strange nesting of UITableViewControllers, UINavigationControllers and UITabBarControllers, so I reworked that to the bare bones of a UITableViewController pushing a freshly made UITableViewController onto its navigationController. I verified that it was being initialized properly, and that I had a valid navigationController to push onto (and other view controllers did push with the same navigationController). Still no change.

Then I added breakpoints to every UITableViewDelegate and DataSource method I’d implemented, even the trivial hardcoded ones (“return 3;” for -numberOfSectionsInTableView:) at first to ensure they were being called (they were). Thus began the tedium of following the chain of 4 or 5 calls per table row until I found the offending code.

This new table view is composed of a number of sections each composed of only one cell. The cells are designed in Interface Builder as top-level objects, and are IBOutlets of the File’s Owner View Controller. Technically, it’s one big scroll view but I’m using this design to provide some layout flexibility if the client wants to reorganize sections, and I thought it would be cool to have the dynamic scrolling of section headers, too.

Since I want the IB objects to define the layout, I determine the height of each row based on the view’s frame:

height = CGRectGetHeight( self.playerStatsCell.frame );

The value for height of this cell (the last on the list—the others were fine) was a “whopping” 6.30104785e-38. What was going on here?!

Well, self.playerStatsCell wasn’t actually connected yet because I was testing (trying to test) one row/section at a time. Calling the frame method on a nil object seems like a normal thing to do in messaging-nil-happy Objective-C. But it’s not. The call is effectively short-circuited, and the temporary CGRect variable the return value was supposed to fill is left uninitialized, which is the bizarre height value returned by CGRectGetHeight.

This call is equivalent to:

CGRect frame; // uninitialized struct
frame = self.playerStatsCell.frame;
height = CGRectGetHeight( frame );

A simple change to the following would fix it:

CGRect frame = CGRectZero; // initialized struct
frame = self.playerStatsCell.frame;
height = CGRectGetHeight( frame );

(In my case, I resolved it by connecting a temporary cell object in IB.)

What did I learn from this?

  1. Messaging to nil is normally fine, but definitely not for struct-returning methods.

  2. Returning a cell height that is very, very tiny from -tableView:heightForRowAtIndexPath: results in “strange behavior.”

I still don’t know exactly what the app was doing while it wasn’t displaying the new table view, but now I know what to quickly look for when I can’t push a UITableViewController in the future.