31

Need help with understanding how to use prepareForReuse() in UIKit. The documentation says

you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state

but what about resetting individual property attributes such as isHidden?

Assuming my cell has 2 labels where should I reset:

  1. label.text
  2. label.numberOfLines
  3. label.isHidden

My tableView(_:cellForRowAt:) delegate has conditional logic to hide/show labels per cell.

mfaani
  • 33,269
  • 19
  • 164
  • 293
Kira
  • 387
  • 1
  • 3
  • 8

2 Answers2

67

tldr: use prepareForReuse to cancel out existing network calls that can can finish after downloading a different indexPath. For all other intents and purposes just use cellForRow(at:. This slightly against Apple docs. But that's how most devs do stuff. It's inconvenient to have cell configuration logic at both places...


Apple docs say use it to reset attributes not related to content. However based on experience it might easier to do just do everything inside cellForRow for content and not. The only time that it actually makes sense is to

Quoting from Apple's docs for prepareForReuse:

For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state.

e.g. if a cell was selected, you just set it to unselected, if you changed the background color to something then you just reset it back to its default color.

The table view's delegate in tableView(_:cellForRowAt:) should always reset all content when reusing a cell.

This means if you were trying to set the profile images of your contact list you shouldn't attempt to nil images in prepareforreuse, you should correctly set your images in the cellForRowAt and if you didn't find any image then you set its image to nil or a default image. Basically the cellForRowAt should govern both the expected/unexpected status.

So basically the following is not suggested:

override func prepareForReuse() {
    super.prepareForReuse()
    imageView?.image = nil
}

instead the following is recommended:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
    let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)

     cell.imageView?.image = image ?? defaultImage // unexpected situation is also handled. 
     // We could also avoid coalescing the `nil` and just let it stay `nil`
     cell.label = yourText
     cell.numberOfLines = yourDesiredNumberOfLines

    return cell
}

Additionally default non-content related items as below is recommended:

override func prepareForReuse() {
    super.prepareForReuse()
    isHidden = false
    isSelected = false
    isHighlighted = false
    // Remove Subviews Or Layers That Were Added Just For This Cell

}

This way you can safely assume when running cellForRowAt then each cell's layout is intact and you just have to worry about the content.

This is the Apple's suggested way. But to be honest, I still think it's easier to dump everything inside cellForRowAt just like Matt said. Clean code is important, but this may not really help you achieve that. But as Connor said the only time it's necessary is, if you need to cancel an image that is loading. For more see here

ie do something like:

override func prepareForReuse() {
    super.prepareForReuse()
    
    imageView.cancelImageRequest() // this should send a message to your download handler and have it cancelled.
    imageView.image = nil
}

Additionally in the special case of using RxSwift: See here or here

Saranjith
  • 11,242
  • 5
  • 69
  • 122
mfaani
  • 33,269
  • 19
  • 164
  • 293
  • But what if you add a variable number views to a cell's `contentView` based on a data property of a cell? Don't these view need to be removed to properly reset the cell's content? In my specific context, this is more relevant to collection views. – Mox Apr 21 '20 at 15:37
  • Will you be (re)setting that variable number for every cell? – mfaani Apr 21 '20 at 15:47
  • Yes, the data property is an optional that I set to `nil` in `prepareForResue` and only add views if the property is not `nil`. – Mox Apr 21 '20 at 16:06
  • you don't need to do that in `prepareForCell`, just in your `cellForRow` for every cell either set the number to a number or just set it to `nil`. If you don't set it to `nil` then the cell would just use the last number that was placed in it – mfaani Apr 21 '20 at 16:29
  • @Mox what you really need to understand is that a cell doesn't go out of memory as soon as the cell is scrolled out of the screen. It just gets re-used. The cell goes out of memory when either you do something that reduces the number of visible cells on the screen or just deallocate the table view... – mfaani Nov 03 '20 at 14:57
  • what is this method removeSubviewsOrLayersThatWereAddedJustForThisCell? – Saranjith Sep 07 '21 at 20:41
  • 1
    @Saranjith I just named that function as such to communicated to readers that you'd only attempt to get layout back to its original state. Nothing more – mfaani Sep 07 '21 at 21:08
  • maybe use tableView:didEndDisplayingCell:forRowAtIndexPath: to cancel the network calls when that cell has disappeared from the screen? – RobotX Nov 03 '21 at 22:55
-4

As stated by the documentation you only have to use the said method to reset attributes that are not related to the content. As for reseting the text/number of lines.... of your labels you could do it from within tableView(_:cellForRowAt:) just before you set their new value, like so:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    cell.label.text = "" //reseting the text
    cell.label.text = "New value"
    return cell
    }

OR

you could take a more object oriented approach and create a subclass of UITableViewCell and define a method say configureCell() to deal with all the resetting and value setting of newly dequeued cells.

fja
  • 1,823
  • 1
  • 15
  • 28
  • 4
    You don't need to reset the text before setting the new value. As long as you populate all labels and set all UI states in `cellForRowAt`, there is no need to "reset" simple properties. See the other answers to this question. – Sunkas Oct 24 '18 at 11:56