65

I'd like a UITableView with subtitle-style cells that use dequeueReusableCellWithIdentifier.

My original Objective-C code was:

static NSString *reuseIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if(!cell)
{
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier];
}

After searching the few UITableView questions here already on SO, I thought to write it in Swift like so:

tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "Cell")

let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell

But that doesn't let me say I want a subtitle style. So I tried this:

var cell :UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "Cell")

Which gives me a subtitle cell, but it doesn't let me dequeueReusableCellWithIdentifier.

I've researched some more and looked at this video tutorial, but he creates a separate subclass of UITableViewCell which I assume is unnecessary as I accomplished this same effect previously in Obj-C.

Any ideas? Thanks.

Alex Zavatone
  • 4,106
  • 36
  • 54
WunDaii
  • 2,322
  • 4
  • 18
  • 26
  • Can you please clarify what you mean by "it doesn't let me `dequeueReusableCellWithIdentifier`"? It is unclear to me. – 67cherries Jun 05 '14 at 14:04
  • @68cherries Sorry I didn't mean 'feature', didn't know what to call it. Method? Basically, I'd like my cells to dequeue as I've read on SO that if you use dequeueReusableCellWithIdentifier, it reuses the cells so it's more efficient. – WunDaii Jun 05 '14 at 14:06
  • Why don't you use the same code as before ? dequeue a cell, if it is `nil` initialise a new cell using swift [constructor](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITableViewCell_Class/#//apple_ref/occ/instm/UITableViewCell/initWithStyle:reuseIdentifier:). – A-Live Jun 05 '14 at 14:09
  • Since the cell is no longer `nil`, answers here are all outdated **except** for [Meilbn's answer](https://stackoverflow.com/a/43131560/5175709) – mfaani Oct 16 '19 at 21:27

13 Answers13

81

Keep in mind that UITableView is defined as an optional in the function, which means your initial cell declaration needs to check for the optional in the property. Also, the returned queued cell is also optional, so ensure you make an optional cast to UITableViewCell. Afterwards, we can force unwrap because we know we have a cell.

var cell:UITableViewCell? = 
tableView?.dequeueReusableCellWithIdentifier(reuseIdentifier) as? UITableViewCell
if (cell == nil)
{
   cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, 
                reuseIdentifier: reuseIdentifier)
}
// At this point, we definitely have a cell -- either dequeued or newly created,
// so let's force unwrap the optional into a UITableViewCell
cell!.detailTextLabel.text = "some text"

return cell
dustinrwh
  • 888
  • 1
  • 14
  • 16
memmons
  • 40,222
  • 21
  • 149
  • 183
  • Thanks a lot, it's that if statement which I was getting stuck on, and then the unwrapping! Funnily enough, I watched the intermediate Swift talk from WWDC just now and they talk about this unwrapping! Thanks again :) – WunDaii Jun 05 '14 at 14:50
  • 1
    NP. The syntax is still a little tricky for all of us, but it gets more natural the more you use it. – memmons Jun 05 '14 at 14:51
  • I'm getting fatal error: Can't unwrap Optional.None when I run this. – Rafael Barros Jun 05 '14 at 16:55
  • @RafaelBarros You set something up wrong and must not be getting a cell back. – memmons Jun 05 '14 at 21:56
  • I fixed it with the question here: http://stackoverflow.com/questions/24056183/custom-uitableview-cell-iphone-using-swift-language?rq=1 – Rafael Barros Jun 05 '14 at 22:53
  • 1
    @RafaelBarros I don't see how that relates to this question since that one is asking about custom cells. – memmons Jun 06 '14 at 00:59
  • @MichaelG.Emmons if I register the class as UITableViewCell.classForCoder() and try this, it just doesn't work. All I want is a subtitle and a thumbnail on the left. This just didn't work for me, with the exact same code. – Rafael Barros Jun 06 '14 at 15:27
  • @RafaelBarros The OP asked _specifically_ about dequeuing a standard `UITableViewCell` with subtitle style, _not_ a custom cell that needs to be registered to be dequeued. So I still fail to understand what your comment has to do with _this question_. – memmons Jun 06 '14 at 15:35
  • I'm not going to get into a flamewar with you defending your answer that is not working for me while trying to replicate exactly what this question is about, with only the subtitle. – Rafael Barros Jun 06 '14 at 15:39
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/55213/discussion-between-michael-g-emmons-and-rafael-barros). – memmons Jun 06 '14 at 15:52
  • 3
    dequeueReusableCell always returns a cell since iOS 4. – pronebird Mar 21 '15 at 17:51
  • For Swift 2 you have force it to: cell.detailTextLabel!.text = "some text" return cell – Roberto Aug 28 '15 at 17:10
  • 2
    shouldn't the check be `if cell == nil { // instantiate a new one }` ? – boliva May 06 '16 at 04:24
  • As @Andy said above `dequeueReusableCell()` no longer returns an optional in Swift 4. @Meilbn 's answer below is updated & correct. – LShi Feb 12 '18 at 11:01
  • 2
    @LShi Not quite. `dequeueReusableCell(withIdentifier:forIndexPath:)` does indeed return a non-optional cell, but `dequeueReusableCell(withIdentifier:)` still returns an optional. – adamjansch Jul 02 '18 at 11:02
  • This is answer is no longer valid. Because the cell is never `nil`. Can you udpate to match: https://stackoverflow.com/a/43131560/5175709 ? – mfaani Oct 16 '19 at 21:28
68

If you'd rather avoid optionality, you can make a subclass of UITableViewCell that looks something like this:

class SubtitleTableViewCell: UITableViewCell {

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Then register it using:

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.register(SubtitleTableViewCell.self, forCellReuseIdentifier: reuseIdentifier)
}

This allows your cell customization code to be really nice:

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

    let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)

    cell.textLabel?.text = "foo"
    cell.detailTextLabel?.text = "bar"

    return cell
}
Jeremy Massel
  • 2,257
  • 18
  • 22
20

Basically the same as other answers, but I get around dealing with nasty optionals (you can't return nil from -tableView:cellForRow:atIndexPath: in Swift) by using a computed variable:

Swift 3

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

    let cell: UITableViewCell = {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell") else {
            // Never fails:
            return UITableViewCell(style: UITableViewCellStyle.value1, reuseIdentifier: "UITableViewCell")
        }
        return cell
    }()

    // (cell is non-optional; no need to use ?. or !)

    // Configure your cell:
    cell.textLabel?.text       = "Key"
    cell.detailTextLabel?.text = "Value"

    return cell
}

Edit:

Actually, it would be better to dequeue the cell using: tableView.dequeueReusableCell(withIdentifier:for:) instead.

This later variant of the function automatically instantiates a new cell if no one is available for reusing (exactly what my code does explicitly above), and therefore never returns nil.

Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189
  • 2
    Rather than doing that interesting thing with the immediately invoked closure, you could just use the nil coalescing operator and say `tableView.dequeueReusableCell(...) ?? UITableViewCell(...)`. – Ryan Pendleton Apr 16 '19 at 04:14
14

Just building upon memmons' answer by cleaning it up Swift 2 style...

let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier) ?? UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: reuseIdentifier)

cell.detailTextLabel?.text = "some text"

return cell

Swift 3:

let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)

cell.detailTextLabel?.text = ""

return cell
Brian
  • 30,156
  • 15
  • 86
  • 87
jonlambert
  • 432
  • 7
  • 14
8

Since tableView.dequeueReusableCell(withIdentifier:, for:) return a non-nil cell, the if cell == nil check is always be false. But I found a solution, to makes default style cell become what style(value1, value2 or subtitle) you want, because default style cell's detailTextLabel is nil, so check the detailTextLabel if it's nil, then create new style cell, and give it to dequeue cell, like:

Swift 3:

var cell = tableView.dequeueReusableCell(withIdentifier: yourCellReuseIdentifier, for: indexPath)

if cell.detailTextLabel == nil {
    cell = UITableViewCell(style: .value1, reuseIdentifier: repeatCellReuseIdentifier)
}

cell.textLabel?.text = "Title"
cell.detailTextLabel?.text = "Detail"

return cell

That's works for me.

Hope it's help.

Meilbn
  • 582
  • 7
  • 8
  • Awesome! Also, to work with above guard statements use: `guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell"), cell.detailTextLabel != nil else { ` – HelloimDarius Feb 28 '18 at 17:53
  • @HelloimDarius Thank you for your complement. – Meilbn Jun 07 '18 at 05:15
  • 1
    That code is inefficient because line 4 creates an extra cell, and if `repeatCellReuseIdentifier != yourCellReuseIdentifier`, that extra cell will never get reused. – ma11hew28 Aug 14 '18 at 05:49
  • @ma11hew28 As I said: ``tableView.dequeueReusableCell(withIdentifier:, for:)`` return a non-nil cell, if you want to avoid create extra cells, you can make your own subtitle style cell, my way just a lazy way. :) – Meilbn Aug 15 '18 at 06:13
5
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let reuseIdentifier = "cell"
    var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier) as UITableViewCell?
    if (cell == nil) {
        cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: reuseIdentifier)
    }
    cell!.textLabel?.text = self.items[indexPath.row]
    cell!.detailTextLabel?.text = self.items[indexPath.row]
    return cell!
}
zirinisp
  • 9,971
  • 5
  • 32
  • 38
Geoffrey
  • 51
  • 1
  • 1
2

You can use a slightly different syntax than the one from memmons to prevent the forced unwrapping:

let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier) as? UITableViewCell ?? UITableViewCell(style: .Subtitle, 
            reuseIdentifier: reuseIdentifier)

cell.detailTextLabel?.text = "some text"

return cell

This is using XCode 6.1 7, Swift 1.2 2.0 syntax where UITableView is no longer an optional.

lammert
  • 1,456
  • 14
  • 20
1

If you are not using your own Custom Cell. Simply register UITableViewCell through code. Then you can prefer code.

Else select storyboard, select your TableViewCell -> Goto Attribute Inspector and choose the desired style as shown below.

enter image description here

Sudhi 9135
  • 745
  • 8
  • 17
1

Make sure that you are not registering any cell to the tableview.

If you did so, dequeueReusableCellWithIdentifier will always give a non optional cell so UITableViewCellStyle.Subtitle will never initiate.

Murali
  • 1,869
  • 1
  • 14
  • 22
0

Perfect as suggested by Michael G. Emmons, but in Xcode 6.1 using

if !cell { .....

I get this error:

Optional type '@|value UITableViewCell?' cannot be used as boolean ; test for '!= nil' instead

The accepted syntax is:

if cell == nil { ...
0
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath:
 NSIndexPath) -> UITableViewCell {  
     var CellIdentifier:String = "Cell"  
     var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier(CellIdentifier) as? UITableViewCell  
     if cell == nil {  
        cell = UITableViewCell(style:UITableViewCellStyle(rawValue:3)!,reuseIdentifier:CellIdentifier)   
      }
    return cell!
}
Amit Jagesha シ
  • 1,092
  • 12
  • 21
0
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{

    var cell:UITableViewCell? =
        tableView.dequeueReusableCell(withIdentifier: "cell")
    if (cell != nil)
    {
        cell = UITableViewCell(style: UITableViewCellStyle.subtitle,
                               reuseIdentifier: "cell")
    }
    cell!.textLabel?.text = "ABC"
    cell!.detailTextLabel?.text = "XYZ"

    return cell!

  }
iOS Lifee
  • 2,091
  • 23
  • 32
-2

I engage you to look at this little UITableView-Example on Github: https://github.com/YANGReal/UITableView-Swift

They do like follows:

func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!
{
   let cell = tableView .dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
   cell.textLabel.text = String(format: "%i", indexPath.row+1)
   // set any other property of your cell here
   return cell
}
Dennis Zoma
  • 2,621
  • 2
  • 17
  • 27
  • 2
    This is what the person in the video tutorial (link in original question) did but I'd like to use the cell style subtitle. To do this, he created a separate subclass of UITableViewCell which I thought was unnecessary. I hope it is, at least! – WunDaii Jun 05 '14 at 14:08