Where Does the Indexpath of Dequeuereusablecellwithidentifier:Forindexpath: Get Used

Where does the indexPath of dequeueReusableCellWithIdentifier:forIndexPath: get used?

According to WWDC 2012 Session 200 - What's New In Cocoa Touch,

If you use - dequeueReusableCellWithIdentifier:forIndexPath: to dequeue your cell, it will be the right size and you'll be able to do layout inside your cell's contentView.

That's pretty much a quote from Chris Parker, a UIKit Engineer.

Up until iOS 6, you had to subclass your UITableViewCell and override - layoutSubviews if you wanted to make layout adjustments. From encapsulation point of view, this might still be the better solution – however, sometimes you just need a tiny adjustment, and now you can do that in - tableView:cellForRowAtIndexPath: instead.

When to use dequeueReusableCellWithIdentifier vs dequeueReusableCellWithIdentifier : forIndexPath

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.

tableView.dequeueReusableCellWithIdentifier VS tableView.cellForRowAtIndexPath

The tableview tries not to have instances of cell objects for all the indexpaths at all times in memory:

  • 'dequeueReusableCellWithIdentifer' recycles a cell or creates it and should be called in 'cellForRow...'. This always returns a cell instance, either a new one or one that was no longer visible previously. You prepare the cell for display after that.

  • 'cellForRowAtIndexPath(indexPath: NSIndexPath) -> UITableViewCell? // returns nil if cell is not visible or index path is out of range' this doesn't create cells, only gives you access to them. I think it should be avoided as much as possible to prevent accidental leaks and other interference with the tableView.

About your 'setSelected' problem:

  • it's good ideea to implement prepare for reuse (set all values to defaults) in the cell class.
  • you shouldn't need to load cell content again in 'didSelectRow' (the user just tapped on it at that point, so it's loaded)
  • you may need to change between orange/clear colors in the cell's 'setSelected'

Assertion failure in -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:]

My bad! It seems to be you are using dequeueReusableCellWithIdentifier:forIndexPath: method for dequeueing cell. Please try dequeueReusableCellWithIdentifier: instead and it should work.

dequeueReusableCellWithIdentifier:forIndexPath: expects a nib file or a registered class for dequeueing cells but you are using storyboard prototype cell. And dequeueReusableCellWithIdentifier: is guaranteed to return a cell from storyboard if you use correct identifier.

Hope it helps.

Program crashes at dequeueReusableCellWithIdentifier:

Thats because [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; was added in iOS 6 but not recognized by iOS 5. For iOS 5 compatibility use below method instead:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

Assertion failure in dequeueReusableCellWithIdentifier:forIndexPath:

You're using the dequeueReusableCellWithIdentifier:forIndexPath: method. The documentation for that method says this:

Important: You must register a class or nib file using the registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: method before calling this method.

You didn't register a nib or a class for the reuse identifier "Cell".

Looking at your code, you seem to expect the dequeue method to return nil if it doesn't have a cell to give you. You need to use the dequeueReusableCellWithIdentifier: for that behavior:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

Notice that dequeueReusableCellWithIdentifier: and dequeueReusableCellWithIdentifier:forIndexPath: are different methods. See doc for the former and the latter.

If you want to understand why you'd want to ever use dequeueReusableCellWithIdentifier:forIndexPath:, check out this Q&A.

Swift - How to use dequeueReusableCellWithIdentifier with two cell types? (identifiers)

The basic approach is that you must implement numberOfRowsInSection and cellForRowAtIndexPath (and if your table has multiple sections, numberOfSectionsInTableView, too). But each call to the cellForRowAtIndexPath will create only one cell, so you have to do this programmatically, looking at the indexPath to determine what type of cell it is. For example, to implement it like you suggested, it might look like:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventsToday.count + eventsTomorrow.count + eventsNextWeek.count + 3 // sum of the three array counts, plus 3 (one for each header)
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var index = indexPath.row

// see if we're the "today" header

if index == 0 {
let separator = tableView.dequeueReusableCellWithIdentifier("separator", forIndexPath: indexPath) as SeparatorTableViewCell

// configure "today" header cell

return separator
}

// if not, adjust index and now see if we're one of the `eventsToday` items

index--

if index < eventsToday.count {
let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
let event = eventsToday[index]

// configure "today" `eventCell` cell using `event`

return eventCell
}

// if not, adjust index and see if we're the "tomorrow" header

index -= eventsToday.count

if index == 0 {
let separator = tableView.dequeueReusableCellWithIdentifier("separator", forIndexPath: indexPath) as SeparatorTableViewCell

// configure "tomorrow" header cell

return separator
}

// if not, adjust index and now see if we're one of the `eventsTomorrow` items

index--

if index < eventsTomorrow.count {
let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
let event = eventsTomorrow[index]

// configure "tomorrow" `eventCell` cell using `event`

return eventCell
}

// if not, adjust index and see if we're the "next week" header

index -= eventsTomorrow.count

if index == 0 {
let separator = tableView.dequeueReusableCellWithIdentifier("separator", forIndexPath: indexPath) as SeparatorTableViewCell

// configure "next week" header cell

return separator
}

// if not, adjust index and now see if we're one of the `eventsToday` items

index--

assert (index < eventsNextWeek.count, "Whoops; something wrong; `indexPath.row` is too large")

let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
let event = eventsNextWeek[index]

// configure "next week" `eventCell` cell using `event`

return eventCell
}

Having said that, I really don't like that logic. I'd rather represent the "today", "tomorrow" and "next week" separator cells as headers, and use the section logic that table views have.

For example, rather than representing your table as a single table with 8 rows in it, you could implement that as a table with three sections, with 2, 1, and 2 items in each, respectively. That would look like:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}

override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch section {
case 0:
return "Today"
case 1:
return "Tomorrow"
case 2:
return "Next week"
default:
return nil
}
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return eventsToday.count
case 1:
return eventsTomorrow.count
case 2:
return eventsNextWeek.count
default:
return 0
}
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell

var event: Event!

switch indexPath.section {
case 0:
event = eventsToday[indexPath.row]
case 1:
event = eventsTomorrow[indexPath.row]
case 2:
event = eventsNextWeek[indexPath.row]
default:
event = nil
}

// populate eventCell on the basis of `event` here

return eventCell
}

The multiple section approach maps more logically from the table view to your underlying model, so I'd to adopt that pattern, but you have both approaches and you can decide.

How tableView:cellForRowAtIndexPath: is supposed to be used?

There's two different methods being called here that have similar names.

tableView:cellForRowAtIndexPath: is a UITableViewDataSource method that you implement to create the cell for the table to show. The tableview calls this method when a new cell is going to come on screen.

controller:didChange:... is calling a different method called cellForRowAtIndexPath: which is a UITableView method. This method queries the table for a cell that is already being displayed after previously being created using the tableView:cellForRowAtIndexPath: datasource method and doesn't result in it being called again. It's calling this method to access the already displayed cell and update it with new info when the object changes.

Both calls to configureCell are necessary.



Related Topics



Leave a reply



Submit