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
isEditingtotrueto enable editing mode - Customize selection behavior using
allowsSelectionandallowsMultipleSelection - 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.