When using stock
UITableViewCells with any of the detail styles (
UITableViewCellStyleSubtitle), some of the
detailTextLabels of my cells would unpredictably vanish. It would never happen with cells that were initially displayed in the table, only with new cells that got scrolled onto the screen. When a cell presented with a missing label, quickly scrolling it off the screen and on again would fix the problem.
I checked my data source and its data. I checked that I was using bog standard
UITableViewCells and that there weren’t subclasses or extensions doing anything daft. I left Swift and behind wrote a sample project in ObjC. The bug persisted.
Then I ran it on my iOS 7 test device (I highly recommend keeping around an old iPod with iOS 7 installed for just this purpose. You can still find them on eBay ). Success! The problem vanished.
Things were starting to make sense, now. Doesn’t iOS 8 support cells with arbitrary heights? Doesn’t it use autolayout to do this?
Logging the frames of my
deatilLabels yielded something like the following:
(161.0,12.0,143.0,19.5) (161.0,12.0,143.0,19.5) (304.0,12.0,0.0,0.0) ← Wut? (161.0,12.0,143.0,19.5)
So, the invisible label had zero size (and was shifted 143 points to the right). That would certainly explain its invisibility. It had content, but its constraints didn’t know about it. I forced it to recompute these constraints with a call to
cell.layoutIfNeeded() before I returned from
tableView(_, cellForRowAtIndexPath) and all was dandy.
I’ve already come to the solution! Problem solved!
But did I solve the right problem?
Like many of you, I’ve been doing a lot of new things with my app of late. Writing in Swift. Adopting iOS 8. Building for the iPhones 6. I’m not comfortable with all these technologies, and it’s only natural that suspicion should fall on them when something goes wrong.
But like all prejudices that cause us to discriminate against the unfamiliar, this intolerant attitude injects harmful bias into the troubleshooting process.
My bug had some pretty specific traits. It didn’t manifest in any of the initial cells displayed by the table. It only showed up on cells newly scrolled onto the screen. If I scrolled the cell off the screen and back on again, the problem would vanish.
Any iPhone developer who has used
UITableView for more than a few months knows exactly what this means. The problem wasn’t with the new cell that was being scrolled onto the screen, it was with an old cell that had already been scrolled off. The old cell was being dequeued and used as the basis for my new cell, and something in the state of this old cell was the true source of my problem.
Sure enough, once I shifted my search, I found the problem was reliably reproduced immediately after a cell with a
detailTextLabel was scrolled off the screen.
The Advantage of Blaming the Responsible
My original supposition was a bug in iOS 8’s autolayout implementation was at fault. I’m sure this didn’t raise any eyebrows; both iOS 8 and autolayout have had their share of regressions reported.
But it was having a blank label — not any nebulous bug in iOS 8 — that was the true culprit screwing with autolayout. By focusing on responsible party, not only was I able to make an intermittent bug reproducible (mandatory for giving your radar a sporting chance of getting fixed), but I was able to construct a much less invasive workaround (“Don’t set labels to
nil” is, to my ears, much cleaner than “Force every cell to layout before it’s displayed”).
And how much time did I waste chasing down ghosts (I reimplemented most of the project in ObjC!) because my prejudices blinded me to the obvious? To be sure, this is still a bug. Autolayout ought to do the Right Thing when presented with a label that was
(0,0) but now contains text. But it’s not a new bug found by intuition. It’s an old bug uncovered by experience.
It’s an exciting time to be an iOS dev. Craig wasn’t being poetic when he called iOS 8 “The biggest … release since the launch of the App Store.” Metal is a Big Deal. Extensions change what it means to be an app. Any day now, APIs for WatchKit are going to drop. Swift!, for crying out loud.
It’s easy to get swept up in the new — and then blame it for all our problems. But remember that each of these technologies is built on the shoulders of giants; giants we have years of hard-won experience debugging.
Giants that still contain plenty of bugs, all their own.
If you’re running into this problem and would like to see it fixed, please file a dupe for rdar://18976171
You can find a sample project that reproduces this bug on github.