163

I'm trying to create a custom table view cell from a nib. I'm referring to this article here. I'm facing two issues.

I created a .xib file with a UITableViewCell object dragged on to it. I created a subclass of UITableViewCell and set it as the cell's class and Cell as the reusable identifier.

import UIKit

class CustomOneCell: UITableViewCell {

    @IBOutlet weak var middleLabel: UILabel!
    @IBOutlet weak var leftLabel: UILabel!
    @IBOutlet weak var rightLabel: UILabel!

    required init(coder aDecoder: NSCoder!) {
        super.init(coder: aDecoder)
    }

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

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

In the UITableViewController I have this code,

import UIKit

class ViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate {

    var items = ["Item 1", "Item2", "Item3", "Item4"]

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // MARK: - UITableViewDataSource
    override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
        let identifier = "Cell"
        var cell: CustomOneCell! = tableView.dequeueReusableCellWithIdentifier(identifier) as? CustomOneCell
        if cell == nil {
            tableView.registerNib(UINib(nibName: "CustomCellOne", bundle: nil), forCellReuseIdentifier: identifier)
            cell = tableView.dequeueReusableCellWithIdentifier(identifier) as? CustomOneCell
        }

        return cell
    }
}

This code complies with no errors but when I run it in the simulator, it looks like this.

enter image description here

In the UITableViewController in the storyboard I haven't done anything to the cell. Blank identifier and no subclass. I tried adding the Cell identifier to the prototype cell and ran it again but I get the same result.

Another error I faced is, when I tried to implement the following method in the UITableViewController.

override func tableView(tableView: UITableView!, willDisplayCell cell: CustomOneCell!, forRowAtIndexPath indexPath: NSIndexPath!) {

    cell.middleLabel.text = items[indexPath.row]
    cell.leftLabel.text = items[indexPath.row]
    cell.rightLabel.text = items[indexPath.row]
}

As shown in the article I mentioned I changed the cell parameter's type form UITableViewCell to CustomOneCell which is my subclass of UITableViewCell. But I get the following error,

Overriding method with selector 'tableView:willDisplayCell:forRowAtIndexPath:' has incompatible type '(UITableView!, CustomOneCell!, NSIndexPath!) -> ()'

Anyone have any idea how to resolve these errors? These seemed to work fine in Objective-C.

Thank you.

EDIT: I just noticed if I change the simulator's orientation to landscape and turn it back to portrait, the cells appear! I still couldn't figure out what's going on. I uploaded an Xcode project here demonstrating the problem if you have time for a quick look.

Isuru
  • 30,617
  • 60
  • 187
  • 303

12 Answers12

241

With Swift 5 and iOS 12.2, you should try the following code in order to solve your problem:

CustomCell.swift

import UIKit

class CustomCell: UITableViewCell {

    // Link those IBOutlets with the UILabels in your .XIB file
    @IBOutlet weak var middleLabel: UILabel!
    @IBOutlet weak var leftLabel: UILabel!
    @IBOutlet weak var rightLabel: UILabel!

}

TableViewController.swift

import UIKit

class TableViewController: UITableViewController {

    let items = ["Item 1", "Item2", "Item3", "Item4"]

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "CustomCell")
    }

    // MARK: - UITableViewDataSource

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell

        cell.middleLabel.text = items[indexPath.row]
        cell.leftLabel.text = items[indexPath.row]
        cell.rightLabel.text = items[indexPath.row]

        return cell
    }

}

The image below shows a set of constraints that work with the provided code without any constraints ambiguity message from Xcode:

enter image description here

Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
  • 2
    Thanks for the response. But that didn't work either. Do I need to change anything in the table view controller? Because Its still set to prototype cells. – Isuru Aug 28 '14 at 10:23
  • 1
    Keep using prototype cells. But make sure that you have set the good Auto layout constraints (if you use Auto layout). – Imanou Petit Aug 28 '14 at 10:59
  • 1
    I uploaded a test project [here](https://www.dropbox.com/s/f239q1hoagzmcqy/CustomCells.zip?dl=0) demonstrating the issue I'm having. Can you please have a look at it if you have time? – Isuru Aug 28 '14 at 11:09
  • 1
    Your test project confirms it: I was able to make your app work fine after I set some auto layout constraints to your custom cell in your .xib file. Have a look at [this video](http://www.lynda.com/Xcode-tutorials/New-Auto-Layout-editor/147016/156483-4.html) if you need to know more about Auto layout. – Imanou Petit Aug 28 '14 at 12:29
  • I'm aware of Auto Layout but I just can't figure out what kinda constraints I should use? Can you please tell me what constraints I should add to the custom cell. Or attach the Xcode project you got working? – Isuru Aug 28 '14 at 17:34
  • Your constraints could look like this (Visual Format Language style): `"H:|-(10)-[leftLabel(60)]-(10)-[middleLabel(60)]-(10)-[rightLabel(60)]-(>=10)-|‌​"`, `"V:|-(10)-[leftLabel]-(10)-|"`, `"V:|-(10)-[middleLabel]-(10)-|"`, `"V:|-(10)-[rightLabel]-(10)-|"`. – Imanou Petit Aug 29 '14 at 09:01
  • @Isuru: As an example, I've edited my post and added an image showing a set of constraints that work without any constraints ambiguity message from Xcode. – Imanou Petit Aug 30 '14 at 14:03
  • Hi @POB, I came across another uitableviewcell, auto layout related issue. You seem to be well versed in auto layout. Could you please take a look at my question [here](http://stackoverflow.com/q/25947146/1077789)? Thank you. – Isuru Sep 20 '14 at 13:54
  • It took me an hour before I found your answer. I was using registerClass which worked in ObjC and that crashed my app. Thanks! – Fengson Feb 26 '15 at 16:16
  • 2
    @KirillKudaev it wasn't renamed in Swift 4 but in Swift 3: I've fixed your edit. – Cœur Oct 16 '18 at 01:10
  • What if the table is inside a custom view ? The delegates for the table are called before awake from xib and results into an error – nr5 Jan 05 '20 at 09:39
34

Here's my approach using Swift 2 and Xcode 7.3. This example will use a single ViewController to load two .xib files -- one for a UITableView and one for the UITableCellView.

enter image description here

For this example you can drop a UITableView right into an empty TableNib.xib file. Inside, set the file's owner to your ViewController class and use an outlet to reference the tableView.

enter image description here

and

enter image description here

Now, in your view controller, you can delegate the tableView as you normally would, like so

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

    ...

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        // Table view delegate
        self.tableView.delegate = self
        self.tableView.dataSource = self

        ...

To create your Custom cell, again, drop a Table View Cell object into an empty TableCellNib.xib file. This time, in the cell .xib file you don't have to specify an "owner" but you do need to specify a Custom Class and an identifier like "TableCellId"

enter image description here enter image description here

Create your subclass with whatever outlets you need like so

class TableCell: UITableViewCell {

    @IBOutlet weak var nameLabel: UILabel!

}

Finally... back in your View Controller, you can load and display the entire thing like so

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    // First load table nib
    let bundle = NSBundle(forClass: self.dynamicType)
    let tableNib = UINib(nibName: "TableNib", bundle: bundle)
    let tableNibView = tableNib.instantiateWithOwner(self, options: nil)[0] as! UIView

    // Then delegate the TableView
    self.tableView.delegate = self
    self.tableView.dataSource = self

    // Set resizable table bounds
    self.tableView.frame = self.view.bounds
    self.tableView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]

    // Register table cell class from nib
    let cellNib = UINib(nibName: "TableCellNib", bundle: bundle)
    self.tableView.registerNib(cellNib, forCellReuseIdentifier: self.tableCellId)

    // Display table with custom cells
    self.view.addSubview(tableNibView)

}

The code shows how you can simply load and display a nib file (the table), and second how to register a nib for cell use.

Hope this helps!!!

internet-nico
  • 1,637
  • 19
  • 17
  • 2
    can you explain what's the "tableCellId" in this line.... self.tableView.registerNib(cellNib, forCellReuseIdentifier: self.tableCellId).... because you have not defined whats that. and you cant manually define the identifier in xib .. no option is there to define it – PRADIP KUMAR Jul 27 '16 at 09:13
  • 1
    In the interface builder, when you create the tableCell, In the "attributes inspector" you define an identifier. The same identifier is what you use in your controller to reference the object. `let tableCellId = "myAwesomeCell"`. I added another image to help you. – internet-nico Oct 03 '16 at 21:56
22

Swift 4

Register Nib

override func viewDidLoad() {
    super.viewDidLoad()
    tblMissions.register(UINib(nibName: "MissionCell", bundle: nil), forCellReuseIdentifier: "MissionCell")
}

In TableView DataSource

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "MissionCell", for: indexPath) as? MissionCell else { return UITableViewCell() }
    return cell
}
Bruno Bieri
  • 9,724
  • 11
  • 63
  • 92
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
19

Detailed Solution with Screenshots

  1. Create an empty user interface file and name it MyCustomCell.xib.

enter image description here

  1. Add a UITableViewCell as the root of your xib file and any other visual components you want.

enter image description here

  1. Create a cocoa touch class file with class name MyCustomCell as a subclass of UITableViewCell.

enter image description here enter image description here

  1. Set the custom class and reuse identifier for your custom table view cell.

enter image description here enter image description here

  1. Open the assistant editor and ctrl+drag to create outlets for your visual components.

enter image description here

  1. Configure a UIViewController to use your custom cell.
class MyViewController: UIViewController {

    @IBOutlet weak var myTable: UITableView!

    override func viewDidLoad {
        super.viewDidLoad()

        let nib = UINib(nibName: "MyCustomCell", bundle: nil)
        myTable.register(nib, forCellReuseIdentifier: "MyCustomCell")
        myTable.dataSource = self
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "MyCustomCell") as? MyCustomCell {
            cell.myLabel.text = "Hello world."
            return cell
        }
        ...
    }
}

Derek Soike
  • 11,238
  • 3
  • 79
  • 74
  • You saved my day. I had.. copied `MyHeaderView.swift` for custom cell. The `.swift` for header view doesn't have `identifier` in `Table View Cell` in the `Attribute Inspector`. so... run time error occurred. – mazend Mar 08 '20 at 14:08
  • By the way.. why do we have declare same name for identifier in `.swift` and in `tableView?.register(blahblah, forCellReuseIdentifier: "myCell")`? I thought one of them is not necessary but.. I found that the both are essential. – mazend Mar 08 '20 at 14:12
  • um.. It maybe because.. `.xib` for custom cell can contain multiple `UITableViewCell` so.. `.xib` is not enough to.. find correct cell. – mazend Mar 08 '20 at 14:18
  • This solution works exactly the way I'm looking for, Thanks a lot. – thevikasnayak Feb 16 '22 at 06:03
6

swift 4.1.2

xib.

Create ImageCell2.swift

Step 1

import UIKit

class ImageCell2: UITableViewCell {

    @IBOutlet weak var imgBookLogo: UIImageView!
    @IBOutlet weak var lblTitle: UILabel!
    @IBOutlet weak var lblPublisher: UILabel!
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }

}

step 2 . According Viewcontroller class

  import UIKit

    class ImageListVC: UIViewController,UITableViewDataSource,UITableViewDelegate {
    @IBOutlet weak var tblMainVC: UITableView!

    var arrBook : [BookItem] = [BookItem]()

    override func viewDidLoad() {
        super.viewDidLoad()
         //Regester Cell
        self.tblMainVC.register(UINib.init(nibName: "ImageCell2", bundle: nil), forCellReuseIdentifier: "ImageCell2")
        // Response Call adn Disply Record
        APIManagerData._APIManagerInstance.getAPIBook { (itemInstance) in
            self.arrBook = itemInstance.arrItem!
            self.tblMainVC.reloadData()
        }
    }
    //MARK: DataSource & delegate
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.arrBook.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//    [enter image description here][2]
        let cell  = tableView.dequeueReusableCell(withIdentifier: "ImageCell2") as! ImageCell2
        cell.lblTitle.text = self.arrBook[indexPath.row].title
        cell.lblPublisher.text = self.arrBook[indexPath.row].publisher
        if let authors = self.arrBook[indexPath.row].author {
            for item in authors{
                print(" item \(item)")
            }
        }
        let  url  = self.arrBook[indexPath.row].imageURL
        if url == nil {
            cell.imgBookLogo.kf.setImage(with: URL.init(string: ""), placeholder: UIImage.init(named: "download.jpeg"))
        }
        else{
            cell.imgBookLogo.kf.setImage(with: URL(string: url!)!, placeholder: UIImage.init(named: "download.jpeg"))
        }
        return cell
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 90
    } 

}
kalpesh
  • 1,285
  • 1
  • 17
  • 30
Pravin Parmar
  • 149
  • 1
  • 6
5

You did not register your nib as below:

tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "CustomCell")
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Sujan
  • 147
  • 1
  • 3
4

Another method that may work for you (it's how I do it) is registering a class.

Assume you create a custom tableView like the following:

class UICustomTableViewCell: UITableViewCell {...}

You can then register this cell in whatever UITableViewController you will be displaying it in with "registerClass":

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.registerClass(UICustomTableViewCell.self, forCellReuseIdentifier: "UICustomTableViewCellIdentifier")
}

And you can call it as you would expect in the cell for row method:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("UICustomTableViewCellIdentifier", forIndexPath: indexPath) as! UICustomTableViewCell
    return cell
}
Ethan Kay
  • 657
  • 6
  • 24
3

For fix the "Overriding method... has incompatible type..." error I've changed the function declaration to

override func tableView(tableView: (UITableView!), 
                        cellForRowAtIndexPath indexPath: (NSIndexPath!)) 
    -> UITableViewCell {...}

(was -> UITableViewCell! -- with exclamation mark at the end)

tse
  • 5,769
  • 6
  • 38
  • 58
2

I had to make sure that when creating the outlet to specify that I was hooking to the cell, not the object's owner. When the menu appears to name it you have to select it in the 'object' dropdown menu. Of course you must declare the cell as your class too, not just 'TableViewCellClass'. Otherwise I would keep getting the class not key compliant.

buckleyJohnson
  • 459
  • 4
  • 12
2

This line add in TableView cell:

static var nib  : UINib{
    return UINib(nibName: identifier, bundle: nil)
}

static var identifier : String{
    return String(describing: self)    
}

    

And register in viewcontroller like

This line use in viewDidLoad

tableview.register(TopDealLikedTableViewCell.nib, forCellReuseIdentifier: TopDealLikedTableViewCell.identifier)

cell for row at indexpath

if let cell = tableView.dequeueReusableCell(withIdentifier:
    TopDealLikedTableViewCell.identifier) as? TopDealLikedTableViewCell{
    return cell
}

return UITableViewCell()
Bruno Bieri
  • 9,724
  • 11
  • 63
  • 92
jenish.x
  • 61
  • 6
1

Simple take a xib with class UITableViewCell. Set the UI as per reuirement and assign IBOutlet. Use it in cellForRowAt() of table view like this:

//MARK: - table method

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.arrayFruit.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    var cell:simpleTableViewCell? = tableView.dequeueReusableCell(withIdentifier:"simpleTableViewCell") as? simpleTableViewCell
    if cell == nil{
        tableView.register(UINib.init(nibName: "simpleTableViewCell", bundle: nil), forCellReuseIdentifier: "simpleTableViewCell")
        let arrNib:Array = Bundle.main.loadNibNamed("simpleTableViewCell",owner: self, options: nil)!
        cell = arrNib.first as? simpleTableViewCell
    }

    cell?.labelName.text = self.arrayFruit[indexPath.row]
    cell?.imageViewFruit.image = UIImage (named: "fruit_img")

    return cell!

}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
 return 100.0
}

enter image description here

100% working without any issue (Tested)

Mr.Javed Multani
  • 12,549
  • 4
  • 53
  • 52
0

Set on cell

static var identifier : String {
    return String(describing: self)   
}

static var nib : UINib {
    return UINib(nibName: identifier, bundle: nil)
}
Bruno Bieri
  • 9,724
  • 11
  • 63
  • 92
Hiren
  • 260
  • 3
  • 3