47

Hey all I am trying to create a custom UITableViewCell, but I see nothing on the simulator. Can you help me please.

I can see the label only if I var labUserName = UILabel(frame: CGRectMake(0.0, 0.0, 130, 30));

but it overlaps the cell. I don't understand, Auto Layout should know the preferred size/minimum size of each cell?

Thanks

import Foundation
import UIKit

class TableCellMessages: UITableViewCell {
    var imgUser     = UIImageView();
    var labUserName = UILabel();
    var labMessage  = UILabel();
    var labTime     = UILabel();


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

        imgUser.layer.cornerRadius = imgUser.frame.size.width / 2;
        imgUser.clipsToBounds = true;



        contentView.addSubview(imgUser)
        contentView.addSubview(labUserName)
        contentView.addSubview(labMessage)
        contentView.addSubview(labTime)


        //Set layout
        var viewsDict = Dictionary <String, UIView>()
        viewsDict["image"] = imgUser;
        viewsDict["username"] = labUserName;
        viewsDict["message"] = labMessage;
        viewsDict["time"] = labTime;
        //Image

        //contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[image(100)]-'", options: nil, metrics: nil, views: viewsDict));
        //contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[image(100)]-|", options: nil, metrics: nil, views: viewsDict));
         contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[username]-[message]-|", options: nil, metrics: nil, views: viewsDict));
         contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[username]-|", options: nil, metrics: nil, views: viewsDict));
          contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[message]-|", options: nil, metrics: nil, views: viewsDict));

    }

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

}
Eugene
  • 3,417
  • 5
  • 25
  • 49
shay te
  • 1,028
  • 3
  • 12
  • 23
  • If you're seeing overlapping images in your cells then the cell height is probably not being set properly. See: http://stackoverflow.com/questions/494562/setting-custom-uitableviewcells-height – Austen Chongpison Aug 20 '14 at 20:04
  • i see overlap only when i am using CGRectMake, but i don't want to use CGRectMake, i want to to be automatically, – shay te Aug 20 '14 at 21:13
  • i want the auto layout to control the sizing, this is the one thing i don't understand. – shay te Aug 20 '14 at 22:22
  • any help please, dose the labels inside should have their own size without me have to tell them what is the size ? – shay te Aug 21 '14 at 07:56
  • I know this thread is old, for me constraints worked better when "translatesAutoresizingMaskIntoConstraints = false" on all the UI elements like labels, views etc – anoop4real Dec 11 '19 at 13:39
  • I read a lot of SO articles about this. But this article was precise and clean: https://programmingwithswift.com/create-a-custom-uitableviewcell-with-swift/ – rustyMagnet Nov 10 '20 at 08:53

3 Answers3

67

Let's make a few assumptions:

You have an iOS8 project with a Storyboard that contains a single UITableViewController. Its tableView has a unique prototype UITableViewCell with custom style and identifier: "cell".

The UITableViewController will be linked to Class TableViewController, the cell will be linked to Class CustomTableViewCell.

You will then be able to set the following code (updated for Swift 2):

CustomTableViewCell.swift:

import UIKit

class CustomTableViewCell: UITableViewCell {

    let imgUser = UIImageView()
    let labUserName = UILabel()
    let labMessage = UILabel()
    let labTime = UILabel()

    override func awakeFromNib() {
        super.awakeFromNib()

        imgUser.backgroundColor = UIColor.blueColor()

        imgUser.translatesAutoresizingMaskIntoConstraints = false
        labUserName.translatesAutoresizingMaskIntoConstraints = false
        labMessage.translatesAutoresizingMaskIntoConstraints = false
        labTime.translatesAutoresizingMaskIntoConstraints = false

        contentView.addSubview(imgUser)
        contentView.addSubview(labUserName)
        contentView.addSubview(labMessage)
        contentView.addSubview(labTime)

        let viewsDict = [
            "image": imgUser,
            "username": labUserName,
            "message": labMessage,
            "labTime": labTime,
        ]

        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[image(10)]", options: [], metrics: nil, views: viewsDict))
        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[labTime]-|", options: [], metrics: nil, views: viewsDict))
        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[username]-[message]-|", options: [], metrics: nil, views: viewsDict))
        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[username]-[image(10)]-|", options: [], metrics: nil, views: viewsDict))
        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[message]-[labTime]-|", options: [], metrics: nil, views: viewsDict))
    }

}

TableViewController.swift:

import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //Auto-set the UITableViewCells height (requires iOS8+)
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 44
    }

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

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

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomTableViewCell

        cell.labUserName.text = "Name"
        cell.labMessage.text = "Message \(indexPath.row)"
        cell.labTime.text = NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: .ShortStyle, timeStyle: .ShortStyle)

        return cell
    }

}

You will expect a display like this (iPhone landscape): enter image description here

meerkat
  • 2,673
  • 5
  • 18
  • 33
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
  • it seem like this is executing awakefromnib before you give it the parameters like cell.labUerName.text = "Name" – Esqarrouth Nov 03 '14 at 08:44
  • 1
    this code is first calling awakefromnib than passing variables into the cell class. because of this i am getting a crash, what should be done? – Esqarrouth Dec 26 '14 at 14:50
  • 6
    To remove the dependency on Interface Builder for registering the *forCellReuseIdentifier*, add the following line to your *UITableViewController* in *loadView()* or *ViewDidLoad()*: `tableView.register(CustomTableViewCell.classForCoder(), forCellReuseIdentifier: "cell")`. – Jamie Birch Apr 20 '17 at 12:01
46

This is the update for swift 3 of the answer Imanou Petit.

CustomTableViewCell.swift:

import Foundation
import UIKit

class CustomTableViewCell: UITableViewCell {

    let imgUser = UIImageView()
    let labUerName = UILabel()
    let labMessage = UILabel()
    let labTime = UILabel()

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

        imgUser.backgroundColor = UIColor.blue

        imgUser.translatesAutoresizingMaskIntoConstraints = false
        labUerName.translatesAutoresizingMaskIntoConstraints = false
        labMessage.translatesAutoresizingMaskIntoConstraints = false
        labTime.translatesAutoresizingMaskIntoConstraints = false

        contentView.addSubview(imgUser)
        contentView.addSubview(labUerName)
        contentView.addSubview(labMessage)
        contentView.addSubview(labTime)

        let viewsDict = [
            "image" : imgUser,
            "username" : labUerName,
            "message" : labMessage,
            "labTime" : labTime,
            ] as [String : Any]

        contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[image(10)]", options: [], metrics: nil, views: viewsDict))
        contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[labTime]-|", options: [], metrics: nil, views: viewsDict))
        contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[username]-[message]-|", options: [], metrics: nil, views: viewsDict))
        contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[username]-[image(10)]-|", options: [], metrics: nil, views: viewsDict))
        contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[message]-[labTime]-|", options: [], metrics: nil, views: viewsDict))
    }

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

}

Settigns.swift:

import Foundation
import UIKit


class Settings: UIViewController, UITableViewDelegate, UITableViewDataSource {

    private var myTableView: UITableView!

    private let sections: NSArray = ["fruit", "vegitable"]    //Profile    network    audio Codecs
    private let fruit: NSArray = ["apple", "orange", "banana", "strawberry", "lemon"]
    private let vegitable: NSArray = ["carrots", "avocado", "potato", "onion"]



    override func viewDidLoad() {
        super.viewDidLoad()

        // get width and height of View
        let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
        let navigationBarHeight: CGFloat = self.navigationController!.navigationBar.frame.size.height
        let displayWidth: CGFloat = self.view.frame.width
        let displayHeight: CGFloat = self.view.frame.height

        myTableView = UITableView(frame: CGRect(x: 0, y: barHeight+navigationBarHeight, width: displayWidth, height: displayHeight - (barHeight+navigationBarHeight)))
        myTableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "cell")         // register cell name

        myTableView.dataSource = self
        myTableView.delegate = self

        //Auto-set the UITableViewCells height (requires iOS8+)
        myTableView.rowHeight = UITableViewAutomaticDimension
        myTableView.estimatedRowHeight = 44

        self.view.addSubview(myTableView)
    }

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


    // return the number of sections
    func numberOfSections(in tableView: UITableView) -> Int{
        return sections.count
    }



    // return the title of sections
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return sections[section] as? String
    }


    // called when the cell is selected.
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        print("Num: \(indexPath.row)")
        if indexPath.section == 0 {
            print("Value: \(fruit[indexPath.row])")
        } else if indexPath.section == 1 {
            print("Value: \(vegitable[indexPath.row])")
        }
    }

    // return the number of cells each section.
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0 {
            return fruit.count
        } else if section == 1 {
            return vegitable.count
        } else {
            return 0
        }
    }

    // return cells
    public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell

        if indexPath.section == 0 {
            cell.labUerName.text = "\(fruit[indexPath.row])"
            cell.labMessage.text = "Message \(indexPath.row)"
            cell.labTime.text = DateFormatter.localizedString(from: NSDate() as Date, dateStyle: .short, timeStyle: .short)
        } else if indexPath.section == 1 {
            cell.labUerName.text = "\(vegitable[indexPath.row])"
            cell.labMessage.text = "Message \(indexPath.row)"
            cell.labTime.text = DateFormatter.localizedString(from: NSDate() as Date, dateStyle: .short, timeStyle: .short)
        }



        return cell
    }

}

Screen Shot

Tiago Mendes
  • 4,572
  • 1
  • 29
  • 35
  • Just wondering... why did you decide to use `init(style:)` instead of `awakeFromNib()` which Imanou Petit used? Is there a difference? – Jonathan Cabrera Jul 20 '18 at 21:10
  • 7
    Imanou Petit was assuming that the user was using Interface Builder. When I used his answer, I made the code totally independent of any storyboard/xib = nib, so in my app the awakeFromNib() never is called. By the way I also had to add this line: myTableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "cell") – Tiago Mendes Jul 20 '18 at 22:41
  • Hey @TiagoMendes quick question, I created a custom cell using the storyboard and I am trying to change its background colour in the cellForRowAtIndexPath method via an outlet, but it doesn't seem to be working. Properties like .isHidden work fine this way. Is this the correct way of doing this? Am I missing something? – CristianMoisei Mar 23 '19 at 16:36
  • 1
    This was the fix for me in Swift5 still – Tofu Warrior Sep 24 '20 at 15:31
21

In Swift 5.

The custom UITableViewCell:

import UIKit

class CourseCell: UITableViewCell {

    let courseName = UILabel()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        // Set any attributes of your UI components here.
        courseName.translatesAutoresizingMaskIntoConstraints = false
        courseName.font = UIFont.systemFont(ofSize: 20)
        
        // Add the UI components
        contentView.addSubview(courseName)
        
        NSLayoutConstraint.activate([
            courseName.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            courseName.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20),
            courseName.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            courseName.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

The UITableViewController:

import UIKit

class CourseTableViewController: UITableViewController {

    private var data: [Int] = [1]
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // You must register the cell with a reuse identifier
        tableView.register(CourseCell.self, forCellReuseIdentifier: "courseCell")
        // Change the row height if you want
        tableView.rowHeight = 150
        // This will remove any empty cells that are below your data filled cells
        tableView.tableFooterView = UIView()
    }

    // MARK: - Table view data source

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

        return 1
    }

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


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "courseCell", for: indexPath) as! CourseCell
        cell.courseName.text = "Course name"

        return cell
    }
 
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
}
andrewlundy
  • 1,073
  • 15
  • 22