Proper Use of CellForRowAtIndexPath and WillDisplayCell

by Yunus Güzel - 7 Jul 2016

In iOS development, UITableView works with two methods related to lifecycle of a UITableViewCell. The first is “willDisplayCell:forRowAtIndexPath:” and the other is “cellForRowAtIndexPath:”. I have seen these methods often misused or misunderstood for one another.

There is an article by Alexander Orlov, which includes a lot of advanced programming practices for improving scrolling performance of UITableView. He also discusses an optional method of UITableViewDelegate called willDisplayCell:forRowAtIndexPath: which supplies the cell that will be displayed at the given index path. In the article he says:

Don’t perform data binding at this point, because there’s no cell on screen yet. For this you can use tableView:willDisplayCell:forRowAtIndexPath: method which can be implemented in the delegate of UITableView. The method called exactly before showing cell in UITableView’s bounds.”

However, there is no underlying reason why he makes this statement in the article. His perception of willDisplayCell is that the method should actually be used for data binding, because it will be called just before the cell will be shown on the screen, thus increasing scrolling performance. This assumption may be true, however, without having any concrete facts, it makes it difficult to agree with him.

Proper use and proof points

iOS works with layout cycles. A layout cycle collects information of the views that have been changed compared to the previous layout cycle. Views are layouted at the end of the cycle, not during. As cellForRowAtIndexPath and willDisplayCell methods are called within the same layout cycle, it doesn’t make sense to expect different performance results. Whether heavy data binding is performed in cellForRowAtIndexPath or in willDisplayCell, they will all be executed serially in the main queue and in the same layout cycle. The cells will always be layouted after these two methods.

To prove this, I have put logs for cellForRowAtIndexPath:, willDisplayCell: of tableView, and layoutSubviews of cells. The result is:

cellForRowAtIndexPath: 0
willDisplayCell: 0
cellForRowAtIndexPath: 1
willDisplayCell: 1
cellForRowAtIndexPath: 2
willDisplayCell: 2
cellForRowAtIndexPath: 3
willDisplayCell: 3
cellForRowAtIndexPath: 4
willDisplayCell: 4
layoutSubviews: 0
layoutSubviews: 1
layoutSubviews: 2
layoutSubviews: 3
layoutSubviews: 4

I will also point out that without layouting subviews there is no rendering, and without rendering there is no cell on the screen yet. This means that willDisplayCell is also called when the cell is not on the screen. I couldn’t find proof for Orlov’s argument.

Orlov also states that tableView:cellForRowAtIndexPath should be quick and return the dequeued cell as fast as possible. In general, you should always be quick to prevent poor scrolling, and not only in cellForRowAtIndexPath. You should also return the height of the cell in heightForRowAtIndexPath: as quickly as possible, which is called after cellForRowAtIndexPath. If you have heavy cell height calculation, you could also have poor scrolling performance because of it. And of course, if you have heavy work in willDisplayCell:forRowAtIndexPath you will have poor scrolling issues. Where he is right, however, is when he states that this applies for all the functions that have been called before the cell is displayed on the screen.

In Apple’s documentation for willDisplayCell, there is no explanation regarding performance, and no warning about heavy data binding:

“A table view sends this message to its delegate just before it uses cell to draw a row, thereby permitting the delegate to customize the cell object before it is displayed. This method gives the delegate a chance to override state-based properties set earlier by the table view, such as selection and background color. After the delegate returns, the table view sets only the alpha and frame properties, and then only when animating rows as they slide in or out.”

It only says that this method is a place to override state-based properties, since they may have been changed after receiving the cell from the cellForRowAtIndexPath method.

Conclusion

Orlov’s article is an important guide for advanced programming. However, he is lacking proof for the tableView delegate method willDisplayCell:forRowAtIndexPath:. This has been something that has bothered me for a while, as so many people quoted the paragraph about willDisplayCell from the article. I couldn’t stop myself writing this piece to question why his argument was flawed. However, I am keen to hear his further thoughts on the topic.

If you have any questions, feel free to contact me on Twitter @yunuserenguzel.

Note: With iOS10, willDisplayCell and cellForRowAtIndexPath call orders will be changed. I will follow up with another article about the changes in iOS10 in the near future.

Similar blog posts