Custom UITableViewCell programmatically using Swift

91,620

Solution 1

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

Solution 2

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

Solution 3

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)
    }
}
Share:
91,620

Related videos on Youtube

shay te
Author by

shay te

Updated on September 01, 2020

Comments

  • shay te
    shay te over 3 years

    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")
        }
    
    }
    
    • Austen Chongpison
      Austen Chongpison over 9 years
      If you're seeing overlapping images in your cells then the cell height is probably not being set properly. See: stackoverflow.com/questions/494562/…
    • shay te
      shay te over 9 years
      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
      shay te over 9 years
      i want the auto layout to control the sizing, this is the one thing i don't understand.
    • shay te
      shay te over 9 years
      any help please, dose the labels inside should have their own size without me have to tell them what is the size ?
    • anoop4real
      anoop4real over 4 years
      I know this thread is old, for me constraints worked better when "translatesAutoresizingMaskIntoConstraints = false" on all the UI elements like labels, views etc
    • rustyMagnet
      rustyMagnet over 3 years
      I read a lot of SO articles about this. But this article was precise and clean: programmingwithswift.com/…
  • Esqarrouth
    Esqarrouth over 9 years
    it seem like this is executing awakefromnib before you give it the parameters like cell.labUerName.text = "Name"
  • Esqarrouth
    Esqarrouth over 9 years
    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?
  • Jamie Birch
    Jamie Birch almost 7 years
    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").
  • Jonathan Cabrera
    Jonathan Cabrera over 5 years
    Just wondering... why did you decide to use init(style:) instead of awakeFromNib() which Imanou Petit used? Is there a difference?
  • Tiago Mendes
    Tiago Mendes over 5 years
    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")
  • CristianMoisei
    CristianMoisei about 5 years
    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?
  • Tofu Warrior
    Tofu Warrior over 3 years
    This was the fix for me in Swift5 still