Create UITableView programmatically in Swift

IosSwiftUitableview

Ios Problem Overview


I try to implement UITableView programmatically without use of xib or Storyboards. This is my code:

ViewController.swift

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let table: UITableViewController = MyTableViewController()
        let tableView: UITableView = UITableView()
        tableView.frame = CGRect(x: 10, y: 10, width: 100, height: 500)
        tableView.dataSource = table
        tableView.delegate = table
        
        self.view.addSubview(tableView)
    }
}

MyTableViewController.swift

import UIKit

class MyTableViewController: UITableViewController {

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        NSLog("sections")
        return 2
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        NSLog("rows")
        return 3
    }
    
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        NSLog("get cell")
        let cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "Cell")
        cell.textLabel!.text = "foo"
        return cell
    }  
}

But when I run app, all I get is empty table. In log I see a few lines of sections and rows, but no get cell. How can I fix this code to get table with 6 lines of foo text?

Ios Solutions


Solution 1 - Ios

Note: As you mentioned you just started programming in Swift. I created a tableView programmatically. Copy and paste below code into your viewController and run the project...

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    private let myArray: NSArray = ["First","Second","Third"]
    private var myTableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
        let displayWidth: CGFloat = self.view.frame.width
        let displayHeight: CGFloat = self.view.frame.height
        
        myTableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight))
        myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
        myTableView.dataSource = self
        myTableView.delegate = self
        self.view.addSubview(myTableView)
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Num: \(indexPath.row)")
        print("Value: \(myArray[indexPath.row])")
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath)
        cell.textLabel!.text = "\(myArray[indexPath.row])"
        return cell
    }
}

Output:

enter image description here

Solution 2 - Ios

Updated for Swift 3

Option 1:

import UIKit
//
// MARK :- TableViewController
//
class TableViewController: UITableViewController {
    
    private let headerId = "headerId"
    private let footerId = "footerId"
    private let cellId = "cellId"
    
    //
    // MARK :- HEADER
    //
    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        
        return 150
    }
    
    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        
        let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerId) as! CustomTableViewHeader
        return header
    }
    
    //
    // MARK :- FOOTER
    //
    override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        
        return 150
    }
    
    override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        
        let footer = tableView.dequeueReusableHeaderFooterView(withIdentifier: footerId) as! CustomTableViewFooter
        return footer
    }
    
    //
    // MARK :- CELL
    //
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return 1
    }
    
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        
        return 150
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! CustomTableCell
        return cell
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = "TableView Demo"
        view.backgroundColor = .white
        setupTableView()
    }
    
    func setupTableView() {
        
        tableView.backgroundColor = .lightGray
        tableView.register(CustomTableViewHeader.self, forHeaderFooterViewReuseIdentifier: headerId)
        tableView.register(CustomTableViewFooter.self, forHeaderFooterViewReuseIdentifier: footerId)
        tableView.register(CustomTableCell.self, forCellReuseIdentifier: cellId)
    }
}

//
// MARK :- HEADER
//
class CustomTableViewHeader: UITableViewHeaderFooterView {
    
    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)
        
        contentView.backgroundColor = .orange
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

//
// MARK :- FOOTER
//
class CustomTableViewFooter: UITableViewHeaderFooterView {
    
    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)
        
        contentView.backgroundColor = .green
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

//
// MARK :- CELL
//
class CustomTableCell: UITableViewCell {
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        contentView.backgroundColor = .white
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Option 2: replace above Option 1 TableViewController with this class

import UIKit
//
// MARK :- ViewController
//
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    private let headerId = "headerId"
    private let footerId = "footerId"
    private let cellId = "cellId"
    
    lazy var tableView: UITableView = {
        
        let tv = UITableView(frame: .zero, style: .plain)
        tv.translatesAutoresizingMaskIntoConstraints = false
        tv.backgroundColor = .lightGray
        tv.delegate = self
        tv.dataSource = self
        tv.register(CustomTableViewHeader.self, forHeaderFooterViewReuseIdentifier: self.headerId)
        tv.register(CustomTableViewFooter.self, forHeaderFooterViewReuseIdentifier: self.footerId)
        tv.register(CustomTableCell.self, forCellReuseIdentifier: self.cellId)
        return tv
    }()
    
    //
    // MARK :- HEADER
    //
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        
        return 150
    }
    
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        
        let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerId) as! CustomTableViewHeader
        return header
    }
    
    //
    // MARK :- FOOTER
    //
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        
        return 150
    }
    
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        
        let footer = tableView.dequeueReusableHeaderFooterView(withIdentifier: footerId) as! CustomTableViewFooter
        return footer
    }
    
    //
    // MARK :- CELL
    //
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return 1
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        
        return 150
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! CustomTableCell
        return cell
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = "TableView Demo"
        view.backgroundColor = .white
        view.addSubview(tableView)
        setupAutoLayout()
    }
    
    func setupAutoLayout() {
        
        tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    }
}

enter image description here

Solution 3 - Ios

Swift 4 compatible

Instead of adding a UITableView to your UIViewController, you should consider creating a UITableViewController and avoid setting up delegates:

class YourTVC: TableViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    // setup custom cells if you use them
    tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "yourCell")
  }
  
  // MARK: tableView
  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 3 // set to value needed
  }
  
  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "yourCell", for: indexPath) as! CustomTableViewCell
    cell.textLabel?.text = "Cell at row \(indexPath.row)"
    return cell
  }

}

Solution 4 - Ios

It makes no sense that you are using a UITableViewController as the data source and delegate for your view controller's table view. Your own view controller should be the table view's data source and delegate.

Since you seem to want a view controller with a table view that doesn't take up the entire view, move every thing to your view controller as follows:

ViewController.swift:

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableView: UITableView = UITableView()
        tableView.frame = CGRect(x: 10, y: 10, width: 100, height: 500)
        tableView.dataSource = self
        tableView.delegate = self

        self.view.addSubview(tableView)
    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        NSLog("sections")
        return 2
    }

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

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        NSLog("get cell")
        let cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "Cell")
        cell.textLabel!.text = "foo"
        return cell
    }  
}

Solution 5 - Ios

You don't need to make a separate class for UITableView. Just in your ViewController class implement protocols of UITableViewDelegate and UITableViewDataSource and then implement delegate methods. I think your code should be like

class ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource {

    override func viewDidLoad() {
        super.viewDidLoad()

        let table: UITableViewController = MyTableViewController()
        let tableView: UITableView = UITableView()
        tableView.frame = CGRect(x: 10, y: 10, width: 100, height: 500)
        tableView.dataSource = table
        tableView.delegate = table

        self.view.addSubview(tableView)
    }
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        NSLog("sections")
        return 2
    }

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

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        NSLog("get cell")
        let cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "Cell")
        cell.textLabel!.text = "foo"
        return cell
    } 
}

Tell us more info or show logs if you still face issue.

Solution 6 - Ios

I had a similar issue in that the data would not populate for my programmatic UITableView. This was because I was using a delegate/dataSource without a strong reference. Once I kept a reference to it (I had one class implementing both UITableViewDataSource and UITableViewDelegate), the data was populated.

Solution 7 - Ios

import UIKit

class ViewController: UITableViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    
    tableView.register(TableCell.self, forCellReuseIdentifier: "cell")
}

override func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 10
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableCell
    cell.nameLabel.text = "TableViewCell programtically"
    cell.nameLabel.textAlignment = .center
    cell.nameLabel.textColor  = .white
    return cell
}

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

Solution 8 - Ios

Simple solution

import UIKit

class CustomTableViewController: UICollectionViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    }

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

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath)
        cell.textLabel!.text = "\(indexPath.row)"
        return cell
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionOndraView Question on Stackoverflow
Solution 1 - IosJoeView Answer on Stackoverflow
Solution 2 - IosiAjView Answer on Stackoverflow
Solution 3 - IosbudiDinoView Answer on Stackoverflow
Solution 4 - IosrmaddyView Answer on Stackoverflow
Solution 5 - Iossaad_nadView Answer on Stackoverflow
Solution 6 - IossalamiView Answer on Stackoverflow
Solution 7 - IossrisivarakrishnavaraprasadView Answer on Stackoverflow
Solution 8 - IosNikView Answer on Stackoverflow