121

I want to use a UITableview with different custom tableViewCells. My 3 cells are as such:

  • Cell1: should have an image and a label.
  • Cell2: should have two labels.
  • Cell3: should have a dayPicker.

I don't want to code a tag for the cells. How can I manage this in Swift. Do I have to code my own class for every cell? Can I use one tableviewController? How can I populate data in different cells?

I would like to generate a tableView, like a contact app of an iOS device.

mfaani
  • 33,269
  • 19
  • 164
  • 293
Easyglider
  • 1,215
  • 2
  • 9
  • 7

7 Answers7

278

Let me start with answering your questions first.

Do I have to code an own class for every cell?=> Yes, I believe so. At least, I would do that way.

Can I use one tableviewController?=> Yes, you can. However, you can also have a table view inside your View Controller.

How can I populate data in different cells? => Depending on the conditions, you can populate data in different cells. For example, let's assume that you want your first two rows to be like the first type of cells. So, you just create/reuse first type of cells and set it's data. It will be more clear, when I show you the screen shots, I guess.

Let me give you an example with a TableView inside a ViewController. Once you understand the main concept, then you can try and modify anyway you want.

Step 1: Create 3 Custom TableViewCells. I named it, FirstCustomTableViewCell, SecondCustomTableViewCell, ThirdCustomTableViewCell. You should use more meaningful names.

enter image description here

Step 2: Go the Main.storyboard and drag and drop a TableView inside your View Controller. Now, select the table view and go to the identity inspector. Set the "Prototype Cells" to 3. Here, you just told your TableView that you may have 3 different kinds of cells.

enter image description here

Step 3: Now, select the 1st cell in your TableView and in the identity inspector, put "FirstCustomTableViewCell" in the Custom class field and then set the identifier as "firstCustomCell" in the attribute inspector.

enter image description here

enter image description here

Do the same for all others- Set their Custom Classes as "SecondCustomTableViewCell" and "ThirdCustomTableViewCell" respectively. Also set the identifiers as secondCustomCell and thirdCustomCell consecutively.

Step 4: Edit the Custom Cell Classes and add outlets according to your need. I edited it based on your question.

P.S: You need to put the outlets under the class definition.

So, In the FirstCustomTableViewCell.swift, under the

class FirstCustomTableViewCell: UITableViewCell {

you would put your label and image view outlets.

 @IBOutlet weak var myImageView: UIImageView!
 @IBOutlet weak var myLabel: UILabel!

and in the SecondCustomTableViewCell.swift, add the two labels like-

import UIKit

class SecondCustomTableViewCell: UITableViewCell {

    @IBOutlet weak var myLabel_1: UILabel!
    @IBOutlet weak var myLabel_2: UILabel!

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

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

and the ThirdCustomTableViewCell.swift should look like-

import UIKit

class ThirdCustomTableViewCell: UITableViewCell {

    @IBOutlet weak var dayPicker: UIDatePicker!

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

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

Step 5: In your ViewController, create an Outlet for your TableView and set the connection from storyboard. Also, you need to add the UITableViewDelegate and UITableViewDataSource in the class definition as the protocol list. So, your class definition should look like-

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

After that attach the UITableViewDelegate and UITableViewDatasource of your table view to your controller. At This point your viewController.swift should look like-

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

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

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

P.S: If you were to use a TableViewController rather than a TableView inside a ViewController, you could have skipped this step.

Step 6: Drag and drop the image views and labels in your cell according to the Cell class. and then provide connection to their outlets from storyboard.

Step 7: Now, write the UITableViewDatasource's required methods in the view controller.

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

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

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

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        if indexPath.row == 0 {
            let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "firstCustomCell")
            //set the data here
            return cell
        }
        else if indexPath.row == 1 {
            let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "secondCustomCell")
            //set the data here
            return cell
        }
        else {
            let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "thirdCustomCell")
            //set the data here
            return cell
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
Natasha
  • 6,651
  • 3
  • 36
  • 58
  • 1
    I get the message: "Could not cast value of type 'UITableViewCell' (0x1dfbfdc) to 'Projekt.TitelTableViewCell' (0x89568)" in the func cellForRowAtIndexPath. I am using an UITableViewController and I have a class "TitelTableViewCell" for my custom cell "TitelZelle", which I have set in the identity inspector of the "TitelZelle" Cell. What do I have to do? – Easyglider Jun 12 '15 at 08:09
  • I have casted my cell as! TitelTableViewCell to populate my custom UI-elements in this cell. – Easyglider Jun 12 '15 at 08:15
  • 8
    try this=> let cell = tableView.dequeueReusableCellWithIdentifier("TitelZelle", forIndexPath: indexPath) as TitelTableViewCell – Natasha Jun 12 '15 at 08:40
  • I took three prototypes cells as default tableviewcell. and set the cell as tableView.dequeueReusableCellWithIdentifier according to indexpath.row. but the return statement is inside the if condition. what should be the return statement for all.it shows error like no return statement – PRADIP KUMAR Jul 19 '16 at 13:21
  • 2
    @PradipKumar, what is the problem? What is your error? Btw, you should ask a question if you have any query. The comment supposed to help understand or clarify this regarding the particular problem, author has posted. If you have a problem, you should go to ask question and start your own post. – Natasha Jul 19 '16 at 13:33
  • Actually i followed your answer. only difference is that i didnot take the customcell. Instead i used the default one .And the error came thats why I comment here – PRADIP KUMAR Jul 19 '16 at 13:43
  • There are couple of things that should be considered as logical error. Inside the 1st if clause, you coded-"if let name = sub.subcategory![indexPath.row]["name"]". This statement is not conditional & it will always return true. A conditional statement should say- "==" not "=". If you are getting a no return type error then after the if clauses, you could say "return nil;: but I would suggest, following the if, else if, else process. So, just change the 2nd "if" to "else if" and for the last "if", assume that is a default case, and rather than if, put it inside an else clause. – Natasha Jul 19 '16 at 15:08
  • @PradipKumar, see my updated answer. I updated the cellForRowAtIndexPath method. This approach may help you get rid of the error. – Natasha Jul 19 '16 at 15:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/117729/discussion-between-pradip-kumar-and-natasha). – PRADIP KUMAR Jul 19 '16 at 15:23
  • thank you I would add do viewDidLoad of ViewController: tableView.dataSource = self tableView.delegate = self – k1m190r Aug 07 '16 at 19:25
  • I have tried your solution but the cells are not getting loaded. I have followed each and every step. I don't see any errors though. I only see "Prototype table cells must have reuse identifiers" this warning – Dee Sep 15 '16 at 14:47
  • I have posted a query. Here is the link: http://stackoverflow.com/questions/39515763/how-to-show-2-customized-cells-in-the-uitableview – Dee Sep 15 '16 at 16:12
  • @Natasha nice explanation.but my array is dynamic.so how to put dynamic array in numberofrowsinsection method according to indexpath? – Krutarth Patel Oct 12 '16 at 06:00
  • Let's say your array is named as list. So, firstly in your "numberOfRowsInSection", you would say "return [list count]". That will give you the number of rows you need and then in your "cellForRowAtIndexPath", after creating a cell, put whatever value, you want to put in your cell based on your array value. – Natasha Oct 13 '16 at 06:58
  • can i do like if indexpath.row == 0 than return 1 in numberofrowsinsection?i ask becuase in in my tableview there are 7 cell.and data is different in each cell – Krutarth Patel Oct 13 '16 at 07:22
  • 1
    numberOfRowsInSection helps the tableview to determine how many row, we want. So, you should only provide clause that will help the method to determine the number of rows. indexpath.row == 0 meaning that you already have a row but at that time, your table doesn't know if it has a row or not. So, you should do like that. – Natasha Oct 13 '16 at 10:31
  • What if you had to tell which cells to display, and which not to? I.e lets say some JSON data comes back from a network call, and you have to have an N number of cells, all different types. Where in the code would you build in the logic to differentiate between the cell types? GREAT WRITE UP btw :) – Cmag Oct 15 '16 at 03:09
  • ok this is a large question to answer @Cmag. Create a question and provide me the link here. I will try to answer than. Also in your question, try to give as much visible hints as possible. – Natasha Oct 15 '16 at 09:44
  • @Natasha gladly! Thank you!!! http://stackoverflow.com/questions/40061465/xcode8-swift3-multiple-uitableviews-with-custom-cells-with – Cmag Oct 15 '16 at 16:24
  • AWESOME answer. Step 2 was really eye opening for me. How can I do this **programmatically**? The differences I can think of are: 1. I would have to make my classes in a separate class just as you, but add its outlets programmatically? 2. register ALL 3 tableViewCells to the tableView programmatically? <-- this is identical to your step2 right? 3. The rest is almost the same. Am I correct? ... The answer is more than complete but it would great if you can add the programmatic approach as well – mfaani Jan 10 '17 at 15:59
  • hello @Natasha I have a query that I had placed a table view with three sections and in this three sections first one is a dynamic array count and second one is static which I placed a button and in the third one I had placed an another table view in the custom cell is it the correct way to implement in this second table view have a api like two arrays in which I need to display as shown in image here https://i.stack.imgur.com/8AFZR.png and the api for this is http://www.json-generator.com/api/json/get/bVgbyVQGmq?indent=2 – Vamsi S Aug 23 '17 at 05:30
  • @Vamsi, as far as I can tel, if you specify the the row count in respect to the different table's properly, there is nothing wrong in doing so. However, I would suggest to have different tableview id for you different table, that way it will be easy for you to specify the row count. – Natasha Aug 24 '17 at 07:43
  • @Natasha I had implemented after running app the third custom cell having table view was not loading first time later when I scroll the table view to down then it was loading how to avoid this ? – Vamsi S Aug 24 '17 at 07:46
  • If I have 2 tableViews, one with two custom cells and another with just one custom cell. What can I write in the numberOfRowsInSection function and in the cellForRowAtIndexPath? – Ghiggz Pikkoro Jan 26 '18 at 16:12
  • You don't need step 5 if you are using UITableViewController instead of a UIViewController. – Deepak Thakur Aug 08 '20 at 15:24
41

Swift 3.0 + update with minimum code

Basic concept: Create a table view with dynamic cell prototypes. Assign identifier and create custom table view cell class for each cell prototype. Initiate and show custom cells in table view's delegate method.

1. Create cells on storyboard

Drag a tableView to your view controller, add prototype cells to it, and then drop UI element to your table view cells, add constraint properly if needed.

enter image description here

2. Create custom UITableViewCell classes

Add the following code to your project. I am putting it right above the view controller class.

class FirstTableCell: UITableViewCell {
}

class SecondTableCell: UITableViewCell {
}

class ThirdTableCell: UITableViewCell {   
}

3. Assign custom class and identifier to cell prototypes

For each of the cell prototypes in storyboard, assign the custom class created from step 2, and then enter an unique identifier.

enter image description here

4. Connect UI elements to swift code

Control drag the table view and connect to the view controller class. Control drag the UI elements that get added to cell prototypes on step 1, and connect to the corresponding table view cell class.

enter image description here

5. Add code to view controller and control the table view

Make your view controller conform to table view delegate

class YourViewController: UIViewController, UITableViewDataSource, UITableViewDelegate

In viewDidLoad, set up table view's delegate and data source.

override func viewDidLoad() {
    super.viewDidLoad()

    self.tableView.dataSource = self
    self.tableView.delegate = self

}

Finally, add two delegate methods to control your table view, as per minimum requirement.

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 3
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if indexPath.row == 0 {
        let cell = tableView.dequeueReusableCell(withIdentifier: "firstTableCell") as! FirstTableCell
        // Set up cell.label
        return cell
    } else if indexPath.row == 1 {
        let cell = tableView.dequeueReusableCell(withIdentifier: "secondTableCell") as! SecondTableCell
        // Set up cell.button
        return cell
    } else {
        let cell = tableView.dequeueReusableCell(withIdentifier: "thirdTableCell") as! ThirdTableCell
        // Set up cell.textField
        return cell
    }
}

6. Give it a try :)

enter image description here

Fangming
  • 24,551
  • 6
  • 100
  • 90
  • 1
    Fantastic! I love your animations. – iphaaw Mar 11 '18 at 17:51
  • Could you please tell me, Is It possible to put a header for each cell ? if so, how? – Pruthvi Hariharan May 08 '18 at 17:32
  • @PruthviHariharan if you want to have a “header” for every or most of your cells, it’s not a good idea to implement a header cell or section headers, which needs more code to manage and will affect performance. My suggestion is to put a view on top of your prototype cell and make it look like a “header” – Fangming May 08 '18 at 17:45
  • @FangmingNing Could you please explain with an small example. Thank you – Pruthvi Hariharan May 08 '18 at 17:55
  • You don't need step 5 if you are using UITableViewController instead of a UIViewController. – Deepak Thakur Aug 08 '20 at 15:25
4

I recommend to use this simple and easy to use library, I made for Table and Collection views. You can add as many types of cells as you want and achieve more clean ViewControllers without boilerplate code.

https://github.com/deniskakacka/DKDataSources

For UI on first picture, all your code in ViewController is this:

lazy var dataSource = DKTableDataSource<CellType>(
    models: [
        DisclosureCellModel(title: "Disclosure 1", action: .action1),
        TextFieldCellModel(title: "TextField 1", placeholder: "Placeholder 1"),
        SwitchCellModel(title: "Switch 1", isOn: true),
        BannerCellModel(imageName: "placeholder"),
        SwitchCellModel(title: "Switch 2", isOn: false),
        BannerCellModel(imageName: "placeholder"),
        DisclosureCellModel(title: "Disclosure 2", action: .action2),
        TextFieldCellModel(title: "TextField 2", placeholder: "Placeholder 2"),
        BannerCellModel(imageName: "placeholder")
    ]
)

// in `viewDidLoad`
dataSource.registerCells(for: tableView)
tableView.dataSource = dataSource

Denis Kakačka
  • 697
  • 1
  • 8
  • 21
4

Swift 5

  1. Create 3 Custom TableViewCells. I named it, FirstTableViewCell, SecondTableViewCell, ThirdTableViewCell

enter image description here

  1. Add All 3 Custom Cell Classes and add outlets according to your need. I have added in below code.

    class FirstTableViewCell: UITableViewCell {

    @IBOutlet weak var myImageView: UIImageView!
    @IBOutlet weak var myLabel: UILabel!
    
    static let cellIdentifier = "FirstTableViewCell"
    static let cellNib = UINib(nibName: "FirstTableViewCell", bundle: Bundle.main)
    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
    }
    

    }

3: In your ViewController, create an Outlet for your TableView. Also, you need to add the UITableViewDelegate and UITableViewDataSource in the class definition.

@IBOutlet weak var tableView: UITableView!  {
        didSet {
            tableView.delegate = self
            tableView.dataSource = self
            tableView.register(FirstTableViewCell.cellNib, forCellReuseIdentifier: FirstTableViewCell.cellIdentifier)
            tableView.register(SecondTableViewCell.cellNib, forCellReuseIdentifier: SecondTableViewCell.cellIdentifier)
            tableView.register(ThirdTableViewCell.cellNib, forCellReuseIdentifier: ThirdTableViewCell.cellIdentifier)
        }
    }

4.Now, write the UITableViewDatasource's required methods in the view controller.

    extension ViewController: UITableViewDelegate,UITableViewDataSource  {
                func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                    return 3
                }
                func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                    if indexPath.row == 0 {
                        guard let cell = tableView.dequeueReusableCell(withIdentifier: FirstTableViewCell.cellIdentifier, for: indexPath) as? FirstTableViewCell  else { return UITableViewCell() }
                        return cell
                    }else if indexPath.row == 1 {
                        guard let cell = tableView.dequeueReusableCell(withIdentifier: SecondTableViewCell.cellIdentifier, for: indexPath) as? SecondTableViewCell  else { return UITableViewCell() }
                        return cell
                    }else  {
                        guard let cell = tableView.dequeueReusableCell(withIdentifier: ThirdTableViewCell.cellIdentifier, for: indexPath) as? ThirdTableViewCell  else { return UITableViewCell() }
                        return cell
                    }
                }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 
            return 50 //According requirement
        }
   }

Your code will look like below(View Controller Code)

class ViewController: UIViewController {
            
            @IBOutlet weak var tableView: UITableView!  {
                didSet {
                    tableView.delegate = self
                    tableView.dataSource = self
                    tableView.register(FirstTableViewCell.cellNib, forCellReuseIdentifier: FirstTableViewCell.cellIdentifier)
                    tableView.register(SecondTableViewCell.cellNib, forCellReuseIdentifier: SecondTableViewCell.cellIdentifier)
                    tableView.register(ThirdTableViewCell.cellNib, forCellReuseIdentifier: ThirdTableViewCell.cellIdentifier)
                }
            }
            override func viewDidLoad() {
                super.viewDidLoad()
                // Do any additional setup after loading the view.
            }
        
        
        }
        
        extension ViewController: UITableViewDelegate,UITableViewDataSource  {
            func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                return 3
            }
            func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                if indexPath.row == 0 {
                    guard let cell = tableView.dequeueReusableCell(withIdentifier: FirstTableViewCell.cellIdentifier, for: indexPath) as? FirstTableViewCell  else { return UITableViewCell() }
                    return cell
                }else if indexPath.row == 1 {
                    guard let cell = tableView.dequeueReusableCell(withIdentifier: SecondTableViewCell.cellIdentifier, for: indexPath) as? SecondTableViewCell  else { return UITableViewCell() }
                    return cell
                }else  {
                    guard let cell = tableView.dequeueReusableCell(withIdentifier: ThirdTableViewCell.cellIdentifier, for: indexPath) as? ThirdTableViewCell  else { return UITableViewCell() }
                    return cell
                }
            }
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 
    return 50 //According requirement
}
}
Priyank Patel
  • 791
  • 8
  • 6
0

The above answers are the best answers, but there are TONS of reasons to get this issue. Here is another potential solution for anyone with this problem:

My problem was that I was segueing to the ViewController class and not the storyboard view. So my reference to the storyboard cell was meaningless, since the storyboard wasn't being used.

I was doing this:

let viewControllerB = SubViewController()
viewControllerB.passedData = diseases[indexPath.row].name
navigationController?.pushViewController(viewControllerB, animated: true)

And I needed to do something like this:

let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "SubViewStoryboardController") as! SubViewController
nextViewController.passedData = diseases[indexPath.row].name
self.present(nextViewController, animated:true, completion:nil)

Hope this helps someone.

Coltuxumab
  • 615
  • 5
  • 16
0

If you're using custom XIBs as TableView Cells then follow the below code

 //Write in viewDidLoad()

    let nib = UINib(nibName: "PrinterTVC", bundle: nil)
    tableView.register(nib, forCellReuseIdentifier: "CELL1")
    
    let nib1 = UINib(nibName: "SelectAndEditTVC", bundle: nil)
    tableView.register(nib1, forCellReuseIdentifier: "CELL2")
thevikasnayak
  • 559
  • 5
  • 13
-22

UITableViewController is inheriting UIViewController that already has UITableviewDataSource & UITableviewDelegate mapped on itself.

You might subclass UITableViewController or use a TableView inside your ViewController. After that you must implement required methods(cellForRowAtIndexPath and numberOfRowsInSection) which are declared in the UITableviewDataSource.

Also in storyboard, you need to create cell prototypes with unique Id.

There are basic types of cell, with (title, subtitle for instance) - you can use them too if you don't need special configuration.

So, for picker, yes, you need to create your own custom cell. Create necessary custom UITableViewCell class holding date picker and make sure to use delegate to send back the desired result back to your ViewController.

milo526
  • 5,012
  • 5
  • 41
  • 60