Create UITableView programmatically in Swift - ios

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?

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:

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
}
}

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
}
}

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
}
}

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.

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.

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
}
}

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
}
}

Related

UITableView Header background color

There are questions and answers for how to change the background color of Header titles in UITableView. These work when a class directly inherits from UITableView. However, when I embed a UITableView as a child of a UIViewController, the same approach of changing the background color does not appear to work.
Can you please see the code below and let me know how I can do this?
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var table: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = "Test row"
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Section Title"
}
func numberOfSections(in tableView: UITableView) -> Int {
1
}
// One way to change the header color
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
view.backgroundColor = .green
}
// Another way
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let vw = UIView()
vw.backgroundColor = .red
return vw
}
override func viewDidLoad() {
super.viewDidLoad()
table = UITableView(frame: CGRect(x: view.frame.maxX/3, y: view.frame.maxY/3, width: view.frame.width/2, height: view.frame.height/2))
table.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
table.dataSource = self
view.addSubview(table)
}
}
tableView(_:viewForHeaderInSection:) is a delegate method check Docs , so you need to add
table.delegate = self
You'll need to conform to UITableViewDelegate for titleForHeaderInSection, willDisplayHeaderView, and viewForHeaderInSection.
You'll need to conform to UITableViewDataSource for numberOfRowsInSection, cellForRowAt, numberOfRowsInSection.
In your viewDidLoad():
table.delegate = self
table.dataSource = self

tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) not getting called in UITableViewController subclass

numberOfSection and numberOfRowsInSection of UITableViewController is getting called but cellForRow not. What could be the reason ? Below is the code for tableViewController.
class GlobalSearchTableViewController: UITableViewController {
/// MARK: Properties
/// Delegate
weak var delegate: GlobalSearchTableViewControllerDelegate?
private var state = GlobalSearchTableState(searchResults: [])
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedSectionFooterHeight = 60
tableView.sectionFooterHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 60
tableView.rowHeight = UITableView.automaticDimension
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell()
}
extension GlobalSearchTableViewController: GlobalSearchTablePresenter {
func present(state: GlobalSearchTableState) {
tableView.reloadData()
}
try to return cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell : UITableViewCell!
cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell.textLabel?.text = dataArray[indexPath.row]
return cell
}
Try adding tableView.delegate = self or tableView.dataSource = self.

Table View Header with Landscape iPhone X support

Well, I am trying to make the headers of UITableView compatible with iPhone X Landscape where I want background colors of Header should expand to edge.
I have seen the WWDC video to do that where they said to apply color on backgroundView but in my case it's always nil.
Here is my code and also link to demo of it.
extension ViewController:UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String.init(describing: HVTableViewCell.self)) as! HVTableViewCell
return cell
}
}
extension ViewController:UITableViewDelegate{
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60.0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 44.0
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: String.init(describing: HVTableViewHeaderFooterView.self)) as? HVTableViewHeaderFooterView{
if let backgroundView = headerView.backgroundView{
backgroundView.backgroundColor = UIColor.red
}else{
print("Failed")
}
return headerView
}else{
return nil
}
}
}
class ViewController: UIViewController {
#IBOutlet weak var tblReference: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tblReference.register(UINib.init(nibName: String.init(describing: HVTableViewCell.self), bundle: nil), forCellReuseIdentifier: String.init(describing: HVTableViewCell.self))
self.tblReference.register(UINib.init(nibName: String.init(describing: HVTableViewHeaderFooterView.self), bundle: nil), forHeaderFooterViewReuseIdentifier: String.init(describing: HVTableViewHeaderFooterView.self))
self.tblReference.delegate = self
self.tblReference.dataSource = self
self.tblReference.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here is the screenshot of the issue.
Thanks in advance.
I found another way to do it.
We can set the background color of Layer of HeaderView.
// HeaderView Class
self.layer.backgroundColor = UIColor.red.cgColor
Here is the link of working code.
Hope it helps to others as well.

Data loading issue in Table View in IOS

I have a table view controller in my view controller. When I give it static number of rows and cell for row index method name it shows nothing to me in the table view. I have also reload the table view but it isn't showing I don't know why is it so,
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell (style: .subtitle, reuseIdentifier: "cellID")
let message = messages[indexPath.row]
cell.textLabel?.text = message.text
return cell
}
Recheck if u have set
tableView.delegate = self
tableView.datasource = self
Also put a breakpoint on cellForRowAtIndexpath to check if code runs through the block.
Also Recheck the cellIdentifier (cellID) is correct or not.
class ViewController:UIViewController,UITableViewDataSource,UITableViewDelegate {
//Getting the reference variables of storyboard
#IBOutlet weak var tableView:UITableView!
var messages = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//Add some packages
messages.append("Super")
messages.append("Free ")
messages.append("Miscellaneous")
messages.append("All ")
tableView.datasource = self
tableView.delegate = self
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell (style: .subtitle, reuseIdentifier: "cellID")
let message = messages[indexPath.row]
cell.textLabel?.text = message.text
return cell
}
}
Use this . This one works for me
class yourViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var tableView:UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.datasource = self
tableView.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell=tableView.dequeueReusableCell(withIdentifier: "cellID",for : indexPath)
let message = messages[indexPath.row]
cell.textLabel?.text = message.text
return (cell)
}
}

Add space between cells in Swift 3

I want to add space between the cells that I have in my UITableView.
Here's my code. What I mean is cell then space then cell then space etc.
class TableViewController: UITableViewController {
var textArray = [String]()
var cellsArray = ["1","2","3"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
textArray = ["","",""]
self.tableView.estimatedSectionHeaderHeight = 80
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return textArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: (cellsArray[indexPath.row]), for: indexPath) as UITableViewCell
return cell
}
}
Use this for space between two cells
let testView: UIView = UIView(frame: CGRect(x:0, y:0, width:TableView1.frame.size.width , height:(cell?.contentView.frame.size.height)! - 5 ))
testView.backgroundColor = UIColor.blue
testView.alpha = 0.5
testView.isUserInteractionEnabled = true
testView.layer.cornerRadius = 5
testView.layer.masksToBounds = true
cell?.contentView.addSubview(testView)
cell?.contentView.sendSubview(toBack: testView)
Just simple approach if you like it ...
let tableViewCellIdentifier = "TableViewCell"
class ViewController: UITableViewController {
var cellsArray = ["1","2","3","4","5","6"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.separatorColor = UIColor.clear
tableView.register(UINib(nibName: tableViewCellIdentifier, bundle: nil), forCellReuseIdentifier: tableViewCellIdentifier)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellsArray.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: tableViewCellIdentifier) as! TableViewCell
cell.customCellLabel.text = cellsArray[indexPath.row]
return cell
}
}
Custom cell
class TableViewCell: UITableViewCell {
#IBOutlet weak var customCellLabel: UILabel!
#IBOutlet weak var innerView: UIView!
}
If you have a vertical scroll for your UITableViewController or UICollectionViewController you need to add this code:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}

Resources