175

There are two overloads for dequeueReusableCellWithIdentifier and I'm trying to determine when should I use one vs the other?

The apple docs regarding the forIndexPath function states, "This method uses the index path to perform additional configuration based on the cell’s position in the table view."

I'm not sure how to interpret that though?

GoodSp33d
  • 6,252
  • 4
  • 35
  • 67
Jaja Harris
  • 4,266
  • 4
  • 23
  • 22

6 Answers6

232

The most important difference is that the forIndexPath: version asserts (crashes) if you didn't register a class or nib for the identifier. The older (non-forIndexPath:) version returns nil in that case.

You register a class for an identifier by sending registerClass:forCellReuseIdentifier: to the table view. You register a nib for an identifier by sending registerNib:forCellReuseIdentifier: to the table view.

If you create your table view and your cell prototypes in a storyboard, the storyboard loader takes care of registering the cell prototypes that you defined in the storyboard.

Session 200 - What's New in Cocoa Touch from WWDC 2012 discusses the (then-new) forIndexPath: version starting around 8m30s. It says that “you will always get an initialized cell” (without mentioning that it will crash if you didn't register a class or nib).

The video also says that “it will be the right size for that index path”. Presumably this means that it will set the cell's size before returning it, by looking at the table view's own width and calling your delegate's tableView:heightForRowAtIndexPath: method (if defined). This is why it needs the index path.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • That's really helpful, thanks. Having the cell sized at dequeue time seems less of an advantage with auto sizing and layout constraints? – Benjohn Jan 28 '17 at 12:35
42

dequeueReusableCellWithIdentifier:forIndexPath: will always return a cell. It either re uses existing cells or creates a new one and returns if there are no cells.

While, the traditional dequeueReusableCellWithIdentifier: will return a cell if it exists i.e if there is a cell which can be reused it returns that else it returns nil. So you would have to write a condition to check for nil value as well.

To answer your question use dequeueReusableCellWithIdentifier: when you want to support iOS 5 and lower versions since dequeueReusableCellWithIdentifier:forIndexPath is only available on iOS 6+

Reference : https://developer.apple.com/library/ios/documentation/uikit/reference/UITableView_Class/Reference/Reference.html#//apple_ref/occ/instm/UITableView/dequeueReusableCellWithIdentifier:forIndexPath:

GoodSp33d
  • 6,252
  • 4
  • 35
  • 67
  • No, it doesn't *always* return a cell 2014-12-26 07:56:39.947 testProg[4024:42920390] *** Assertion failure in -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:], /SourceCache/UIKit_Sim/UIKit-3318.65/UITableView.m:6116 2014-12-26 07:56:39.954 Interphase[4024:42920390] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier MyCustomCellIdentifier - must register a nib or a class for the identifier or connect a prototype cell in a storyboard' – clearlight Dec 26 '14 at 15:58
  • @binarystar You *must* register a nib or class of your custom cell in view did load. like : `[self.tableView registerNib:[UINib nibWithNibName:@"cell" bundle:nil] forCellReuseIdentifier:@"cell"];` – GoodSp33d Dec 27 '14 at 03:47
6

I have never understood why Apple created the newer method, dequeueReusableCellWithIdentifier:forIndexPath:. Their documentation on them is not complete, and is somewhat misleading. The only difference I've been able to discern between the two methods, is that that older method can return nil, if it doesn't find a cell with the identifier passed in, while the newer method crashes, if it can't return a cell. Both methods are guaranteed to return a cell, if you have set the identifier correctly, and make the cell in a storyboard. Both methods are also guaranteed to return a cell if you register a class or xib, and make your cell in code or a xib file.

rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • 3
    The new method uses the index path to determine the proper size for the cell. – rob mayoff Sep 14 '14 at 20:24
  • 1
    @robmayoff But does this have any sense? Without the new method, the size of the cell can still be set properly. Can the new method offer any convenience? – fujianjin6471 Jul 29 '15 at 12:37
  • 1
    Read the last paragraph of my answer for details. – rob mayoff Jul 29 '15 at 16:39
  • So does this mean that, if all my cells are of the same sizes in the table, it doesn't matter which method I call? – Happiehappie Mar 08 '16 at 08:34
  • 3
    If I provide `tableView.estimateHeight`, the cell's size will be determined properly as well. I still don't get the benefit of the new method. – Ryan Nov 01 '17 at 21:54
1

For short:

dequeueReusableCell(withIdentifier, for) only works with prototype cells. If you tried to use it when the prototype cell is absence, it would crash the app.

Hollemans M. 2016, Chapter 2 Checklist, IOS Apprentice (5th Edition). pp: 156.

SLN
  • 4,772
  • 2
  • 38
  • 79
0

The main difference is you can not register two cells for the same indexPath while only using the reuse identifier you can do it, and both can return nil if the cells are not registered against that table view

Muhammad Iqbal
  • 109
  • 1
  • 9
-2

I would recommend to use both if you are using dynamic generated content. Otherwise your app might crash unexpectedly. You could implement your own function to retrieve an optional reusable cell. If it is nil you should return an empty cell that is not visible:

Swift 3

// Extensions to UITableView
extension UITableView
{
    // returns nil, if identifier does not exist. 
    // Otherwise it returns a configured cell for the given index path
    open func tryDequeueReusableCell (
        withIdentifier identifier: String, 
        for indexPath: IndexPath) -> UITableViewCell?
    {
        let cell = self.dequeueReusableCell(withIdentifier: identifier)
        if cell != nil {
            return self.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
        }  
        return nil
    }
}

And the extension to return an empty cell:

// Extension to UITableViewCell
extension UITableViewCell
{
    // Generates an empty table cell that is not visible
    class func empty() -> UITableViewCell
    {
        let emptyCell = UITableViewCell(frame:CGRect(x:0, y:0, width:0, height:0))
        emptyCell.backgroundColor = UIColor.clear
        return emptyCell
    }
}

A complete example of how to use it:

import Foundation
import UIKit

// A protocol is used to identify if we can configure
// a cell with CellData
protocol ConfigureAbleWithCellData
{
    func configure(_ data: CellData)
}

class MyCustomTableViewCell :
    UITableViewCell,
    ConfigureAbleWithCellData
{
    @IBOutlet weak var title:UILabel! = nil
    func configure(_ data: CellData)
    {
        self.title.text = data.title
    }
}

// This actually holds the data for one cell
struct CellData
{
    var title:String = ""
    var reusableId:String = ""
}

class CosmoConverterUnitTableViewController:
    UIViewController,
    UITableViewDelegate,
    UITableViewDataSource
{
    // Storage
    var data = Array<Array<CellData>>()

    func loadData()
    {
        var section1:[CellData] = []
        var section2:[CellData] = []

        section1.append(CellData(title:"Foo", reusableId:"cellType1"))
        section2.append(CellData(title:"Bar", reusableId:"cellType2"))

        data.append(section1)
        data.append(section2)
    }

    func tableView(_ tableView: UITableView,
                   numberOfRowsInSection section: Int) -> Int
    {
        return data[section].count
    }

    public func numberOfSections(in tableView: UITableView) -> Int
    {
        return data.count
    }

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

        guard
            indexPath.row < data[indexPath.section].count
            else
        {
            fatalError("this can't be")
        }

        let cellData = data[indexPath.section][indexPath.row]

        if let cell = tableView.tryDequeueReusableCell(
            withIdentifier: cellData.reusableId,
            for: indexPath)
        {
            if let configurableCell = cell as? ConfigureAbleWithCellData
            {
                configurableCell.configure(cellData)
            }
            else
            {
                // cell is not of type ConfigureAbleWithCellData
                // so we cant configure it.
            }
            return cell
        }
        // id does not exist
        return UITableViewCell.empty()
    }
}
hhamm
  • 1,511
  • 15
  • 22