3

I'm trying to create a UITableView with regular, non-custom .subtitle cells in pure code. However, the following code never gives me a cell with a proper detailTextLabel, instead opting for a .default cell.

public var cellIdentifier: String { return "wordsCell" }
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell: UITableViewCell
    if let newCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) {
        // Always succeeds, so it never goes to the alternative.
        cell = newCell
    }
    else {
        // This is never reached.
        cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
    }

    let word = wordAtIndexPath(indexPath: indexPath)
    cell.textLabel?.text = word.text
    cell.detailTextLabel?.text = word.subText

    return cell
}

This is apparantly because dequeueReusableCell(withIdentifier:) doesn't actually return nil, even if no cell is currently available. Instead, it always returns a .default if no cells have been created.

The other option, dequeueReusableCell(withIdentifier:for:) also always succeeds, so that wouldn't work either.

So far, it looks impossible to create a non-.default style cell in pure code, without Interface Builder to define the style of a prototype cell. The closest I can come up with is this answer, which notices the same problem. All the other questions I found also address either IB issues or custom cells.

Does anyone know how to dequeue a .subtitle cell for a table view without using Interface Builder?

Community
  • 1
  • 1
SpacyRicochet
  • 2,269
  • 2
  • 24
  • 39
  • Can you swap the order of the conditional expressions? – NRitH Jan 11 '17 at 21:52
  • @NRitH Probably not, since we only want to initialize a new cell when necessary. If I swap the conditionals, it would _always_ initialize a new cell. – SpacyRicochet Jan 11 '17 at 22:03
  • Can you explain what you are trying to achieve? if which case your want the subtitle? – Mat Jan 11 '17 at 22:06
  • @mat I always want the subtitle. But, without using Interface Builder. – SpacyRicochet Jan 11 '17 at 22:12
  • have you register the cell first of all in viewDidLoad using `func register(_ cellClass: AnyClass?, forCellReuseIdentifier identifier: String)` – Mat Jan 11 '17 at 22:15
  • @mat Yes, that's all done. The cell is created properly too (it would crash otherwise), just without the detailTextLabel. – SpacyRicochet Jan 11 '17 at 22:16
  • Yeah, that will work fine. I was just _very_ confused about this not working at all. In the meantime, @silicon_valley gave the answer in a comment below. I shouldn't register the class (thought that was necessary for any newly initialised UITableViewController). – SpacyRicochet Jan 11 '17 at 22:31
  • Possible duplicate of [dequeueReusableCellWithIdentifier never returns nil](http://stackoverflow.com/questions/16382068/dequeuereusablecellwithidentifier-never-returns-nil) GayleDDS & Matthias Bauch combined answers are the response you were looking for. – Larme Jan 11 '17 at 23:19
  • Please don't register any cell if you want to deque subtitle UITableviewCell because it will try to create a default cell first and then deque will always return a cell. – Sourabh Shekhar Dec 02 '20 at 17:45

3 Answers3

5

I tested it and it works. I thought you wanted to subclass UItableViewCell but you don't have to register the cell in this case.

class TableViewController: UITableViewController {

    let wordAtIndexPath = ["one", "two", "three"]
    let cellId = "cellId"
    override func viewDidLoad() {
        super.viewDidLoad()

    }

    // MARK: - Table view data source

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

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return wordAtIndexPath.count
    }


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

        let word = wordAtIndexPath[indexPath.row]
        let cell: UITableViewCell = {
            guard let cell = tableView.dequeueReusableCell(withIdentifier: cellId) else {
                return UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: cellId)
            }    
            return cell
        }()

        cell.textLabel?.text = "My cell number"
        cell.detailTextLabel?.text = word
        return cell
    }

}

enter image description here

Mat
  • 6,236
  • 9
  • 42
  • 55
3

I think your problem may be that you have registered a cell in interface builder (or in viewDidLoad) with the same name as 'cellIdentifier'. You shouldn't register any cell if you want to use the subtitle type cell. By registering a cell, it will try to create that cell first (which will not be a subtitle type of cell).

silicon_valley
  • 2,639
  • 2
  • 16
  • 20
  • Except for the original storyboard, which I didn't touch, there is no Interface Builder file. So no chance of any cell having that identifier. I added the cell identifier for clarity. – SpacyRicochet Jan 11 '17 at 22:15
  • 1
    You shouldn't register any cell, if you remove that code it should work. By registering a cell, it will try to create that cell first (which will not be a subtitle type of cell). – silicon_valley Jan 11 '17 at 22:24
  • That worked! Can you create an answer, so I can accept it? – SpacyRicochet Jan 11 '17 at 22:29
  • Great! I've updated the answer so it also mentions registering a cell in `viewDidLoad`. – silicon_valley Jan 11 '17 at 22:33
0

I had been facing similar issue and found the most elegant way would be to subclass UITableViewCell if you're not using storyboards. And dequeue cell with that custom class.

Two Steps:

  1. Create a subtitle Cell :

     class DetailCell: UITableViewCell {
    
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
     super.init(style: .subtitle, reuseIdentifier: "reuseIdentifier")
    
    }
    
     required init?(coder: NSCoder) {
     super.init(coder: coder)
     }
    
    }
    
  2. Dequeue cell optionally casting it to that class as such:

     override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
     guard let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as? DetailCell else {
         return UITableViewCell.init(style: .subtitle, reuseIdentifier: "reuseIdentifier")
     }
    
     // Configure the cell...
     cell.textLabel?.text =  "Title"
     cell.detailTextLabel?.text = "Subtitle"
     return cell
    
     }