43

Just as the question's title mentions: What's the difference between "cellForRowAtIndexPath" and "willDisplayCell: forRowAtIndexPath:"?`

I think cell configuration can be done either in cellForRowAtIndexPath or willDisplayCell: forRowAtIndexPath:"!

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
mayqiyue
  • 1,155
  • 1
  • 10
  • 16
  • 1
    similar to `viewWillAppear` and `viewDidlAppear`. – Blind Ninja Aug 13 '15 at 12:38
  • Nice answer! The both are so similar! – mayqiyue Aug 13 '15 at 12:43
  • Basically you provide a cell to system in `cellForRowAtIndexPath` ( configurations are up to you ) but `willDisplayCell: forRowAtIndexPath:` is a way when system sends the cell to you if you want to make any last moment changes (again up to you). – Blind Ninja Aug 13 '15 at 12:53
  • See also [Proper place to update UITableViewCell content](http://stackoverflow.com/questions/38982085/proper-place-to-update-uitableviewcell-content) – Suragch Aug 16 '16 at 22:13

6 Answers6

24

You are right, cell configuration can (in theory) be done in both methods.

However, almost all UITableView have a data source which implements cellForRowAtIndexPath: (it is a required method in the protocol). On the other hand, the willDisplayCell:forRowAtIndexPath: (which is a method of the delegate, not the data source) is optional.

As configuring a cell is usually dependent on the data you want to show, cellForRowAtIndexPath: is by far the most common place to do cell configuration. (I can't even remember using willDisplayCell:forRowAtIndexPath:).

There's one notable exception: when you are using a storyboard and static cells (instead of cell prototypes), you can't do anything useful in cellForRowAtIndexPath: (because dequeueReusableCellWithIdentifier: returns nil), so you have to do configuration in willDisplayCell:forRowAtIndexPath:, viewWillAppear: or other methods.

@NSDeveloper: you're right. Thanks for the hint.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
  • 7
    use [super tableView:tableView cellForRowAtIndexPath:indexPath] instead of dequeue... to get static cell. – NSDeveloper Aug 13 '15 at 13:00
  • 4
    It becomes important in iOS 10, where the collectionView (by default) pre-fetches in advance the cells. As Apple says: Note: When prefetching is enabled the collectionView:cellForItemAtIndexPath: method on the collection view delegate is called in advance of when the cell is required. To avoid inconsistencies in the visual appearance, use the collectionView:willDisplayCell:forItemAtIndexPath: delegate method to update the cell to reflect visual state such as selection. – Tumata Aug 25 '17 at 22:08
23

cellForRowAtIndexPath should actually return a cell instance. It should be a re-used cell, when possible. This method is required for the UITableViewDataSource Protocol. This is normally where you select the data that will be displayed in the cell. With dynamic cells, it's common to set UI properties such as selected state at the same time you set the data here.

willDisplayCell is optional and is called after. This is your last chance to customize the cell before it's displayed. At this point, the cell instance has already been created. You can change things like selected state, etc. here. You should not be changing the data/structure of the cell or instantiating anything new, but only changing the state of UI properties for the cell. This is commonly used with static cells.

Marcus Adams
  • 53,009
  • 9
  • 91
  • 143
  • 1
    Apples Lister Sample Code is a good example for that usage https://developer.apple.com/library/ios/samplecode/Lister/Introduction/Intro.html And there is also a nice blog on that topic http://blog.lazerwalker.com/objective-c/code/2013/12/01/a-tale-of-two-protocols.html – coco Sep 24 '15 at 14:36
  • 1
    Just a little typo, `cellForRowAtIndexPath` belongs to `UITableViewDataSource`, not `UITableViewDelegate`. – Shiva Huang Oct 02 '15 at 08:45
16

I think this answers your question:

But very important thing is still there: tableView:cellForRowAtIndexPath: method, which should be implemented in the dataSource of UITableView, called for each cell and should work fast. So you must return reused cell instance as quickly as possible.

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.

From Perfect smooth scrolling in UITableViews

iTSangar
  • 462
  • 8
  • 10
  • 6
    Is there any experiment data prove the difference between these two? – xi.lin Apr 15 '16 at 07:28
  • 1
    In iOS 9, there is no difference, but there may be a difference from iOS 10. https://tech.zalando.com/blog/proper-use-of-cellforrowatindexpath-and-willdisplaycell/ – jamesk Nov 14 '16 at 15:19
  • 1
    I don't think binding data in `tableView:willDisplayCell:forRowAtIndexPath:` helps. I've tested, couldn't see any difference. I've fixed the `color blended layer`, unchecked the `clear context`, checked `opaque` on everything, and none of these worked. I think the problem is the `autolayout` is too slow. – Kimi Chiu Dec 19 '17 at 04:22
7

Despite what might seem intuitive, willDisplay cell: is called immediately after cellForRowAt indexPath: is called.

I had an app where images and video would be loaded in from either a URLCache or downloaded and displayed in the cell. I noticed whenever I'd start my application all the videos and images would load before I could see them and I noticed this because I could hear the audio from the last video in the tableView while looking at the first item in the tableView. This is with 8 items in the tableView. I printed to the console to get a better idea of what the order of the delegate functions calls were.

The results:

  • Created cell at row 0
    • Drew feed cell at row 0
  • Created cell at row 1
    • Drew feed cell at row 1
  • Created cell at row 2
    • Drew feed cell at row 2
  • Created cell at row 3
    • Drew feed cell at row 3
  • Created cell at row 4
    • Drew feed cell at row 4
  • Created cell at row 5
    • Drew feed cell at row 5
  • Created cell at row 6
    • Drew feed cell at row 6
  • Created cell at row 7
    • Drew feed cell at row 7
  • Stopped showing cell at row 2
  • Stopped showing cell at row 3
  • Stopped showing cell at row 4
  • Stopped showing cell at row 5
  • Stopped showing cell at row 6
  • Stopped showing cell at row 7

The Code:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "feedCell", for: indexPath) as! FeedCell
    print("Created cell at row \(indexPath.row)")
    let post = posts[indexPath.row]
    cell.post = post
    cell.viewController = self
    cell.configureCell()
    return cell
}

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if let feedCell = cell as? FeedCell{
        print("Drew feed cell at row \(indexPath.row)")
        // only want download task to start when the cell will display
        if feedCell.post.isVideo {
            feedCell.loadVideo()
        } else {
            feedCell.loadImage()
        }
    }
}

override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    print("Stopped showing cell at row \(indexPath.row)")
    if let feedCell = cell as? FeedCell{
        feedCell.player?.pause()
    }
}
Vrezh Gulyan
  • 218
  • 3
  • 6
  • can you share code snippet of your feed cell. How you have created the UI. – Rahul Vyas Feb 05 '19 at 07:23
  • 2
    willDisplay is not always called immediately after cellForRow for each row. E.g. go in landscape and scroll to bottom of large table, rotate to portrait, those extra cells needed to fill the portrait screen are all called with cellForRow in a batch first and then are all called willDisplayCell after. – malhal Jun 05 '20 at 18:54
5

I faced an issue with cell configuration in willDisplayCell: when used autolayout and UITableViewAutomaticDimension. The heights of reused cells didn't calculate properly. Printing methods in NSLog shows that willDisplayCell: is called after heightForRowAtIndexPath: (tested on iOS 10.2 simulator)

cellForRowAtIndexPath: <NSIndexPath: 0x7c10daa0> {length = 2, path = 1 - 0}
heightForRowAtIndexPath: <NSIndexPath: 0x7c10daa0> {length = 2, path = 1 - 0}
heightForRowAtIndexPath: <NSIndexPath: 0x7c10daa0> {length = 2, path = 1 - 0}
willDisplayCell: <NSIndexPath: 0x7c10daa0> {length = 2, path = 1 - 0}
cellForRowAtIndexPath: <NSIndexPath: 0x7c4516f0> {length = 2, path = 1 - 1}
heightForRowAtIndexPath: <NSIndexPath: 0x7c4516f0> {length = 2, path = 1 - 1}
heightForRowAtIndexPath: <NSIndexPath: 0x7c4516f0> {length = 2, path = 1 - 1}
willDisplayCell: <NSIndexPath: 0x7c4516f0> {length = 2, path = 1 - 1}

I think this was the reason, because problem has gone after placing configuration code in cellForRowAtIndexPath:

P.S. There is an opposite article to the link and quote posted by @iTSangar: https://tech.zalando.com/blog/proper-use-of-cellforrowatindexpath-and-willdisplaycell/

nemissm
  • 453
  • 6
  • 12
  • 1
    This is the main/only difference. Automatic height does not work correctly when you change anything in `willDisplayCell:` that will change the height of the cell. Ideally ignore `willDisplayCell:` completely and set everything in `cellForRowAtIndexPath:` when you need `UITableView.automaticDimension`. – shelll Feb 15 '21 at 12:25
-1

The delegate method willDisplayCell will be called every time a cell is just about to get displayed on the screen. Say for example - you are downloading any image data, then this is the best place to do the download or call the method that downloads image data.

The problem with downloading image data in cellForRow or cellForItem is, a large number of images/photos comes back from the initial request. The user may never even scroll down far enough to see all of the images. This is very costly as it may eat up users cellular data.

So the big vote here for willDisplayCell is User may never scroll down, so why download all images in cellForRow?. Make use of willDisplayCell to only download those images user is about to see.

So this way you supply the cell in cellForRowAtIndexPath and update the content in willDisplayCell

Also note that cellForRow is called first and then willDisplayCell.

Naishta
  • 11,885
  • 4
  • 72
  • 54
  • The cellForItem delegate method is not called unless the cell is about to appear. If there are 20 items in the data source, cellForItem is not automatically called 20 times in a row. It is called maybe 3 times and then again before the next cell appears, and again before the next one appears, etc. WillDisplayCell is called immediately after cellForItem. – swiftyboi Dec 03 '19 at 22:24