Skip to main content

Working with Tables and Lists

1. Creating UITableView

Property Value Description
UITableView.Style plain, grouped Determines the visual style of the table view
UITableView.DataSource UITableViewDataSource Protocol that provides data for the table view
UITableView.Delegate UITableViewDelegate Protocol that handles user interactions and layout

Example: Basic UITableView Setup

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
    }
}

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

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

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Selected row \(indexPath.row)")
    }
}

2. Implementing UITableViewDataSource

Method Description Return Type
numberOfSections(in:) Number of sections in the table view Int
numberOfRows(inSection:) Number of rows in a specific section Int
cellForRowAt: Provides the cell for a specific row UITableViewCell
viewForHeaderInSection: Provides the header view for a section UIView?
viewForFooterInSection: Provides the footer view for a section UIView?
canEditRowAt: Indicates if a row can be edited Bool
canMoveRowAt: Indicates if a row can be moved Bool

Example: Implementing UITableViewDataSource

class MyTableViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    let data = ["Item 1", "Item 2", "Item 3"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
    }
}

extension MyTableViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = data[indexPath.row]
        return cell
    }
}

3. Implementing UITableViewDelegate

Method Description Notes
tableView(_:didSelectRowAt:) Called when a row is selected Use to handle row selection logic
tableView(_:heightForRowAt:) Specifies the height of a row Return a CGFloat value for custom height
tableView(_:cellForRowAt:) Provides the cell for a specific row Essential for customizing cell content
tableView(_:viewForHeaderInSection:) Provides the view for a section header Return a UIView or nil for default
tableView(_:viewForFooterInSection:) Provides the view for a section footer Return a UIView or nil for default

Example: Implementing tableView(_:didSelectRowAt:)

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print("Selected row at section: \(indexPath.section), row: \(indexPath.row)")
}

Example: Implementing tableView(_:cellForRowAt:)

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

4. Registering Custom Cells

Method Description Usage
register(_:forCellReuseIdentifier:) Registers a custom cell class for reuse tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
register(_:forCellReuseIdentifier:) Registers a custom cell from nib file tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "CustomCell")

Example: Registering Custom Cell Class

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!

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

Example: Registering Custom Cell from Nib

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let nib = UINib(nibName: "CustomCell", bundle: nil)
        tableView.register(nib, forCellReuseIdentifier: "CustomCell")
    }
}

5. Dequeuing Reusable Cells

Method Parameters Description
dequeueReusableCell(withIdentifier:for:) identifier: String, indexPath: IndexPath Dequeues a reusable cell with the specified identifier and index path. This is the preferred method for dequeuing cells in a table view.
dequeueReusableCell(withIdentifier:) identifier: String Dequeues a reusable cell with the specified identifier. This method is less preferred as it does not provide an index path.

Example: Dequeuing a Reusable Cell

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath)
    
    // Configure the cell with data
    cell.textLabel?.text = data[indexPath.row].title
    
    return cell
}
Note: The identifier must match the one set in the storyboard or XIB file for the cell.
Warning: If no reusable cell is available, this method will create a new one. Ensure that the identifier matches to avoid unexpected behavior.

6. Implementing Cell Selection

Property Value Description
allowsSelection true Enables or disables selection in the table view
allowsMultipleSelection false Allows multiple cells to be selected simultaneously
allowsSelectionDuringEditing false Enables selection while the table is in editing mode

Example: Selecting a Cell

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print("Selected row at section: \(indexPath.section), row: \(indexPath.row)")
}

Example: Deselecting a Cell

override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
    print("Deselected row at section: \(indexPath.section), row: \(indexPath.row)")
}
Note: To enable selection, ensure allowsSelection is set to true.
Warning: When using allowsMultipleSelection, make sure to handle selection state correctly to prevent UI inconsistencies.

7. Using Section Headers and Footers

Property Description Notes
sectionIndex Index of the section in the table view Used to identify which section a header or footer belongs to
headerView Custom view to display as the section header Can be any UIView subclass
footerView Custom view to display as the section footer Can be any UIView subclass

Example: Adding Section Headers and Footers

import UIKit

class TableViewViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    let sections = [
        ["Item 1", "Item 2"],
        ["Item 3", "Item 4"]
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
    }
}

extension TableViewViewController: UITableViewDelegate, UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sections[section].count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = sections[indexPath.section][indexPath.row]
        return cell
    }
    
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = UIView()
        headerView.backgroundColor = UIColor.lightGray
        
        let label = UILabel()
        label.text = "Section \(section + 1)"
        label.frame = CGRect(x: 10, y: 0, width: 200, height: 30)
        headerView.addSubview(label)
        
        return headerView
    }
    
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let footerView = UIView()
        footerView.backgroundColor = UIColor.darkGray
        
        let label = UILabel()
        label.text = "Footer for Section \(section + 1)"
        label.frame = CGRect(x: 10, y: 0, width: 200, height: 20)
        footerView.addSubview(label)
        
        return footerView
    }
}
Note: Section headers and footers can be customized with any UIView, allowing for rich content like images or complex layouts.

8. Implementing Editing Mode

Property Description Notes
isEditing Boolean flag to enable or disable editing mode Defaults to false
allowsSelection Controls whether cells can be selected in editing mode Defaults to true
allowsMultipleSelection Enables multiple cell selection in editing mode Defaults to false

Example: Enabling Editing Mode

class MyTableViewController: UITableViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.isEditing = true
    }
}

Example: Customizing Editing Behavior

class MyTableViewController: UITableViewController {
    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        self.tableView.allowsSelection = editing
        self.tableView.allowsMultipleSelection = editing
    }
}
Note: Editing mode affects the entire table view. Use UITableView's setEditing(_:animated:) method to toggle editing state.

Editing Mode Workflow

  • Set isEditing to true to enable editing mode
  • Customize selection behavior using allowsSelection and allowsMultipleSelection
  • Implement custom editing behavior in setEditing(_:animated:)

9. Reordering Table Rows

Method Description Notes
moveRow(at: IndexPath, to: IndexPath) Moves a row from one index path to another Must be called on the main thread
insertRows(at: [IndexPath], with:RowAnimation) Inserts new rows at specified index paths Use for dynamic data updates
deleteRows(at: [IndexPath], with:RowAnimation) Deletes rows at specified index paths Must match existing data model

Example: Reordering Table Rows

func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    // Move data in your data model
    let movedItem = items.remove(at: sourceIndexPath.row)
    items.insert(movedItem, at: destinationIndexPath.row)
    
    // Optionally animate the change
    tableView.reloadRows(at: [sourceIndexPath, destinationIndexPath], with: .fade)
}
Note: Ensure your data model is updated before calling table view methods to maintain consistency.

10. Using Self-Sizing Cells

Property Value Description
estimatedRowHeight UITableViewAutomaticDimension Estimated height for rows when using self-sizing cells
rowHeight UITableViewAutomaticDimension Enables automatic row height calculation
preferredMaxLayoutWidth view.bounds.width Preferred max width for layout calculations

Example: Configuring Self-Sizing Cells

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = 44.0
    }
}

Example: Cell Configuration with Auto Layout

class CustomTableViewCell: UITableViewCell {
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var subtitleLabel: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        titleLabel.numberOfLines = 0
        subtitleLabel.numberOfLines = 0
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        titleLabel.preferredMaxLayoutWidth = titleLabel.bounds.width
        subtitleLabel.preferredMaxLayoutWidth = subtitleLabel.bounds.width
    }
}
Note: Ensure that your cell's content views have proper Auto Layout constraints to support self-sizing behavior.

11. Implementing Pull-to-Refresh

Example: Basic Pull-to-Refresh Implementation

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupPullToRefresh()
    }
    
    func setupPullToRefresh() {
        let refreshControl = UIRefreshControl()
        refreshControl.addTarget(self, action: #selector(refreshData), for: .valueChanged)
        tableView.refreshControl = refreshControl
    }
    
    @objc func refreshData() {
        // Simulate data fetch
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.tableView.refreshControl?.endRefreshing()
        }
    }
}
Property Value Description
refreshControl UIRefreshControl? The refresh control associated with the table view
addTarget(_:action:for:) Void Adds a target-action pair for a specified control event
endRefreshing() Void Ends the refreshing state of the control
Note: The refresh control appears automatically when the user pulls down on the table view. You must call endRefreshing() to stop the refresh animation.

Was this article helpful?