UITableView and Custom UITableViewCells in UIKit
UITableView
is a UIKit class that displays a scrollable list of data in a single-column format, widely used for lists, settings, or feeds in iOS apps. As a subclass of UIScrollView
, it relies on data source and delegate protocols to manage content and interactions. UITableViewCell
represents individual rows, and custom cells enable tailored layouts. This document covers the key properties, methods, and usage of UITableView
, including custom UITableViewCells
, with sections on SwiftUI integration, editing, swipe actions, diffable data sources, multiple sections, UITableViewController
, and pull-to-refresh, along with examples and best practices.
Overview of UITableView
UITableView
organizes data into sections and rows, supporting scrolling, selection, editing, and animations. It uses UITableViewDataSource
to provide content and UITableViewDelegate
to handle user interactions. Reusable cells improve performance, and custom cells allow flexible designs.
Creating a UITableView
You can create a UITableView
programmatically or via Interface Builder.
Programmatic Example:
import UIKit
let tableView = UITableView()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
view.addSubview(tableView)
Key Properties of UITableView
Property | Type | Description |
---|---|---|
dataSource | UITableViewDataSource? | Provides data (e.g., number of rows, cells). |
delegate | UITableViewDelegate? | Handles interactions and cell customization. |
rowHeight | CGFloat | Default row height (use .automaticDimension for dynamic sizing). |
estimatedRowHeight | CGFloat | Estimated height for dynamic sizing. |
separatorStyle | UITableViewCell.SeparatorStyle | Separator style (e.g., .singleLine , .none ). |
allowsSelection | Bool | Enables/disables row selection. |
isEditing | Bool | Toggles editing mode. |
refreshControl | UIRefreshControl? | Pull-to-refresh control. |
Example: Configuring Properties:
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 44
tableView.separatorStyle = .singleLine
tableView.allowsSelection = true
Key Methods of UITableView
Method | Description |
---|---|
register(_:forCellReuseIdentifier:) | Registers a cell class or nib for reuse. |
dequeueReusableCell(withIdentifier:for:) | Dequeues a reusable cell. |
reloadData() | Reloads all table data. |
insertRows(at:with:) | Inserts rows with animation. |
deleteRows(at:with:) | Deletes rows with animation. |
selectRow(at:animated:scrollPosition:) | Selects a row programmatically. |
UITableViewDataSource Protocol
Method | Description |
---|---|
tableView(_:numberOfRowsInSection:) | Returns the number of rows in a section. |
tableView(_:cellForRowAt:) | Returns a configured cell for a row. |
numberOfSections(in:) | Returns the number of sections (optional). |
UITableViewDelegate Protocol
Method | Description |
---|---|
tableView(_:didSelectRowAt:) | Handles row selection. |
tableView(_:heightForRowAt:) | Returns custom row height. |
tableView(_:trailingSwipeActionsConfigurationForRowAt:) | Configures trailing swipe actions. |
Creating Custom UITableViewCells
Custom cells enable unique layouts with labels, images, or other views, created programmatically or via nibs/storyboards.
Programmatic Custom Cell
class CustomTableViewCell: UITableViewCell {
static let identifier = "CustomCell"
let titleLabel = UILabel()
let subtitleLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
titleLabel.font = .systemFont(ofSize: 16, weight: .bold)
subtitleLabel.font = .systemFont(ofSize: 14)
subtitleLabel.textColor = .systemGray
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.axis = .vertical
stackView.spacing = 4
contentView.addSubview(stackView)
stackView.snp.makeConstraints { make in
make.edges.equalTo(contentView).inset(16)
}
}
func configure(title: String, subtitle: String) {
titleLabel.text = title
subtitleLabel.text = subtitle
}
}
Register and Use:
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: CustomTableViewCell.identifier)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as! CustomTableViewCell
cell.configure(title: "Item \(indexPath.row)", subtitle: "Details")
return cell
}
Creating Custom Table Cells Using SwiftUI
SwiftUI views can be hosted in UITableViewCell
using UIHostingController
for hybrid UIKit/SwiftUI apps.
SwiftUI Cell View
import SwiftUI
struct CustomCellView: View {
let title: String
let subtitle: String
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.system(size: 16, weight: .bold))
Text(subtitle)
.font(.system(size: 14))
.foregroundColor(.gray)
}
.padding(16)
}
}
Hosting in UITableViewCell
class SwiftUICustomCell: UITableViewCell {
static let identifier = "SwiftUICell"
private var hostingController: UIHostingController<CustomCellView>?
func configure(title: String, subtitle: String) {
if hostingController == nil {
let contentView = CustomCellView(title: title, subtitle: subtitle)
hostingController = UIHostingController(rootView: contentView)
hostingController!.view.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(hostingController!.view)
hostingController!.view.snp.makeConstraints { make in
make.edges.equalTo(contentView)
}
} else {
hostingController!.rootView = CustomCellView(title: title, subtitle: subtitle)
}
}
override func prepareForReuse() {
super.prepareForReuse()
hostingController?.view.removeFromSuperview()
hostingController = nil
}
}
Register and Use:
tableView.register(SwiftUICustomCell.self, forCellReuseIdentifier: SwiftUICustomCell.identifier)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: SwiftUICustomCell.identifier, for: indexPath) as! SwiftUICustomCell
cell.configure(title: "SwiftUI Item \(indexPath.row)", subtitle: "SwiftUI Details")
return cell
}
Inserting and Removing Rows from a Table
UITableView
supports animated row insertion and deletion using insertRows
and deleteRows
.
Example:
var data = ["Item 1", "Item 2", "Item 3"]
func addRow() {
data.append("Item \(data.count + 1)")
let indexPath = IndexPath(row: data.count - 1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
func removeRow(at index: Int) {
data.remove(at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.deleteRows(at: [indexPath], with: .fade)
}
Batch Updates:
tableView.performBatchUpdates {
data.insert("New Item", at: 0)
tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .top)
if data.count > 1 {
data.remove(at: 1)
tableView.deleteRows(at: [IndexPath(row: 1, section: 0)], with: .fade)
}
}
Reloading Data in a Table and Selecting Rows of a Table
Reloading Data
Use reloadData()
to refresh the entire table or reloadRows(at:with:)
for specific rows.
Example:
func refreshData() {
data = ["Updated Item 1", "Updated Item 2"]
tableView.reloadData()
}
func updateRow(at index: Int) {
data[index] = "Updated Item \(index)"
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}
Selecting Rows
Handle selection via tableView(_:didSelectRowAt:)
and programmatically select rows with selectRow
.
Example:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Selected: \(data[indexPath.row])")
tableView.deselectRow(at: indexPath, animated: true)
}
func selectRowProgrammatically(at index: Int) {
let indexPath = IndexPath(row: index, section: 0)
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
}
Putting a UITableView Into Edit Mode
Enable editing for row deletion, reordering, or insertion.
Example:
// Enable editing
tableView.setEditing(true, animated: true)
// Toggle edit mode with a button
@objc func toggleEditMode() {
tableView.setEditing(!tableView.isEditing, animated: true)
}
// Allow deletion
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
data.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
// Allow reordering
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let item = data.remove(at: sourceIndexPath.row)
data.insert(item, at: destinationIndexPath.row)
}
Swipe Actions on UITableViewCells
Add leading or trailing swipe actions for quick interactions.
Example: Trailing Swipe Actions:
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { _, _, completion in
self.data.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
completion(true)
}
let favoriteAction = UIContextualAction(style: .normal, title: "Favorite") { _, _, completion in
print("Favorited: \(self.data[indexPath.row])")
completion(true)
}
favoriteAction.backgroundColor = .systemYellow
return UISwipeActionsConfiguration(actions: [deleteAction, favoriteAction])
}
Leading Swipe Actions:
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let markAction = UIContextualAction(style: .normal, title: "Mark") { _, _, completion in
print("Marked: \(self.data[indexPath.row])")
completion(true)
}
markAction.backgroundColor = .systemBlue
return UISwipeActionsConfiguration(actions: [markAction])
}
UITableViewDiffableDataSource
Introduced in iOS 13, UITableViewDiffableDataSource
simplifies data management with type-safe, animated updates.
Example:
enum Section { case main }
typealias DataSource = UITableViewDiffableDataSource<Section, String>
typealias Snapshot = NSDiffableDataSourceSnapshot<Section, String>
var dataSource: DataSource!
private func configureDataSource() {
dataSource = DataSource(tableView: tableView) { tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as! CustomTableViewCell
cell.configure(title: item, subtitle: "Details")
return cell
}
}
private func applySnapshot(data: [String], animatingDifferences: Bool = true) {
var snapshot = Snapshot()
snapshot.appendSections([.main])
snapshot.appendItems(data)
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
Support Multiple Sections In Tables Using a Diffable Data Source
Diffable data sources handle multiple sections with unique identifiers.
Example:
enum Section: Hashable {
case featured
case regular
}
struct Item: Hashable {
let id = UUID()
let title: String
}
typealias DataSource = UITableViewDiffableDataSource<Section, Item>
typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Item>
var dataSource: DataSource!
var featuredItems: [Item] = [Item(title: "Featured 1"), Item(title: "Featured 2")]
var regularItems: [Item] = [Item(title: "Regular 1"), Item(title: "Regular 2")]
private func configureDataSource() {
dataSource = DataSource(tableView: tableView) { tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as! CustomTableViewCell
cell.configure(title: item.title, subtitle: "Details")
return cell
}
// Configure headers
dataSource.supplementaryViewProvider = { tableView, kind, indexPath in
let header = tableView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "Header", for: indexPath) as! HeaderView
header.configure(with: indexPath.section == 0 ? "Featured" : "Regular")
return header
}
}
private func applySnapshot(animatingDifferences: Bool = true) {
var snapshot = Snapshot()
snapshot.appendSections([.featured, .regular])
snapshot.appendItems(featuredItems, toSection: .featured)
snapshot.appendItems(regularItems, toSection: .regular)
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
Header View:
class HeaderView: UITableViewHeaderFooterView {
static let identifier = "Header"
let label = UILabel()
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
label.font = .systemFont(ofSize: 18, weight: .bold)
contentView.addSubview(label)
label.snp.makeConstraints { make in
make.edges.equalTo(contentView).inset(8)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with text: String) {
label.text = text
}
}
Support Multiple Sections in a UITableView the Old Way
Before iOS 13, multiple sections were managed manually with UITableViewDataSource
methods.
Example:
var featuredItems = ["Featured 1", "Featured 2"]
var regularItems = ["Regular 1", "Regular 2"]
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? featuredItems.count : regularItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as! CustomTableViewCell
let item = indexPath.section == 0 ? featuredItems[indexPath.row] : regularItems[indexPath.row]
cell.configure(title: item, subtitle: "Details")
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return section == 0 ? "Featured" : "Regular"
}
UITableViewController
UITableViewController
is a UIViewController
subclass that manages a UITableView
, automatically setting itself as the table’s data source and delegate. It simplifies table view setup but is less flexible than embedding a UITableView
in a UIViewController
.
Example:
class TableViewController: UITableViewController {
var data = ["Item 1", "Item 2", "Item 3"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: CustomTableViewCell.identifier)
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 44
}
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: CustomTableViewCell.identifier, for: indexPath) as! CustomTableViewCell
cell.configure(title: data[indexPath.row], subtitle: "Details")
return cell
}
}
- Pros: Simplified setup, built-in table view management.
- Cons: Less flexible for complex layouts or multiple views.
- Use Case: Simple table-based apps.
Implementing Pull to Refresh in a UITableViewController and UITableView (Old and New Way)
Old Way (Pre-iOS 10 or Manual Implementation)
Use a UIRefreshControl
manually added to the table view.
Example in UIViewController with UITableView:
override func viewDidLoad() {
super.viewDidLoad()
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshData), for: .valueChanged)
tableView.refreshControl = refreshControl
}
@objc func refreshData() {
// Simulate network fetch
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.data = ["Refreshed Item 1", "Refreshed Item 2"]
self.tableView.reloadData()
self.tableView.refreshControl?.endRefreshing()
}
}
Example in UITableViewController:
override func viewDidLoad() {
super.viewDidLoad()
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(refreshData), for: .valueChanged)
}
@objc func refreshData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.data = ["Refreshed Item 1", "Refreshed Item 2"]
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
}
}
New Way (iOS 10+ with Diffable Data Source)
Use UIRefreshControl
with UITableViewDiffableDataSource
for animated updates.
Example in UIViewController with UITableView:
override func viewDidLoad() {
super.viewDidLoad()
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshData), for: .valueChanged)
tableView.refreshControl = refreshControl
configureDataSource()
applySnapshot(data: data)
}
@objc func refreshData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.data = ["Refreshed Item 1", "Refreshed Item 2"]
self.applySnapshot(data: self.data)
self.tableView.refreshControl?.endRefreshing()
}
}
Example in UITableViewController:
class TableViewController: UITableViewController {
var dataSource: DataSource!
var data = ["Item 1", "Item 2", "Item 3"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: CustomTableViewCell.identifier)
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(refreshData), for: .valueChanged)
configureDataSource()
applySnapshot(data: data)
}
private func configureDataSource() {
dataSource = DataSource(tableView: tableView) { tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as! CustomTableViewCell
cell.configure(title: item, subtitle: "Details")
return cell
}
}
private func applySnapshot(data: [String], animatingDifferences: Bool = true) {
var snapshot = Snapshot()
snapshot.appendSections([.main])
snapshot.appendItems(data)
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
@objc func refreshData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.data = ["Refreshed Item 1", "Refreshed Item 2"]
self.applySnapshot(data: self.data)
self.refreshControl?.endRefreshing()
}
}
}
Best Practices
Reuse Cells: Register and dequeue cells for performance.
Accessibility: Set
accessibilityLabel
andaccessibilityTraits
for cells.swiftcell.accessibilityLabel = "Item \(indexPath.row)" cell.accessibilityTraits = .button
Auto Layout: Use constraints (e.g., SnapKit) in custom cells.
Performance: Minimize complex cell layouts and use
estimatedRowHeight
.Testing: Verify selection, editing, swipe actions, and refresh across devices.
Diffable Data Source: Prefer
UITableViewDiffableDataSource
for modern apps.
Troubleshooting
- Cells Not Displaying: Check
dataSource
andcellForRowAt
implementation. - Reuse Issues: Ensure unique reuse identifiers.
- Animations Failing: Use
performBatchUpdates
orapply
correctly. - Refresh Not Working: Verify
refreshControl
target-action andendRefreshing()
call. - Accessibility Issues: Test with VoiceOver.
- Performance Issues: Profile with Instruments for heavy layouts.
Example: Complete UITableView Setup with Diffable Data Source and Pull-to-Refresh
import UIKit
import SnapKit
class CustomTableViewCell: UITableViewCell {
static let identifier = "CustomCell"
let titleLabel = UILabel()
let subtitleLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
titleLabel.font = .systemFont(ofSize: 16, weight: .bold)
subtitleLabel.font = .systemFont(ofSize: 14)
subtitleLabel.textColor = .systemGray
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.axis = .vertical
stackView.spacing = 4
contentView.addSubview(stackView)
stackView.snp.makeConstraints { make in
make.edges.equalTo(contentView).inset(16)
}
}
func configure(title: String, subtitle: String) {
titleLabel.text = title
subtitleLabel.text = subtitle
accessibilityLabel = "\(title), \(subtitle)"
}
}
class HeaderView: UITableViewHeaderFooterView {
static let identifier = "Header"
let label = UILabel()
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
label.font = .systemFont(ofSize: 18, weight: .bold)
contentView.addSubview(label)
label.snp.makeConstraints { make in
make.edges.equalTo(contentView).inset(8)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with text: String) {
label.text = text
}
}
enum Section: Hashable {
case featured
case regular
}
struct Item: Hashable {
let id = UUID()
let title: String
}
typealias DataSource = UITableViewDiffableDataSource<Section, Item>
typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Item>
class ViewController: UIViewController, UITableViewDelegate {
let tableView = UITableView()
var dataSource: DataSource!
var featuredItems = [Item(title: "Featured 1"), Item(title: "Featured 2")]
var regularItems = [Item(title: "Regular 1"), Item(title: "Regular 2")]
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
configureDataSource()
applySnapshot()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(toggleEditMode))
}
private func setupTableView() {
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: CustomTableViewCell.identifier)
tableView.register(HeaderView.self, forHeaderFooterViewReuseIdentifier: HeaderView.identifier)
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 44
tableView.delegate = self
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshData), for: .valueChanged)
tableView.refreshControl = refreshControl
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalTo(view.safeAreaLayoutGuide)
}
}
private func configureDataSource() {
dataSource = DataSource(tableView: tableView) { tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as! CustomTableViewCell
cell.configure(title: item.title, subtitle: "Details")
return cell
}
dataSource.supplementaryViewProvider = { tableView, kind, indexPath in
let header = tableView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: HeaderView.identifier, for: indexPath) as! HeaderView
header.configure(with: indexPath.section == 0 ? "Featured" : "Regular")
return header
}
}
private func applySnapshot(animatingDifferences: Bool = true) {
var snapshot = Snapshot()
snapshot.appendSections([.featured, .regular])
snapshot.appendItems(featuredItems, toSection: .featured)
snapshot.appendItems(regularItems, toSection: .regular)
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
@objc func refreshData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.featuredItems = [Item(title: "Refreshed Featured 1"), Item(title: "Refreshed Featured 2")]
self.regularItems = [Item(title: "Refreshed Regular 1"), Item(title: "Refreshed Regular 2")]
self.applySnapshot()
self.tableView.refreshControl?.endRefreshing()
}
}
@objc func toggleEditMode() {
tableView.setEditing(!tableView.isEditing, animated: true)
navigationItem.rightBarButtonItem?.title = tableView.isEditing ? "Done" : "Edit"
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = indexPath.section == 0 ? featuredItems[indexPath.row] : regularItems[indexPath.row]
print("Selected: \(item.title)")
tableView.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { _, _, completion in
if indexPath.section == 0 {
self.featuredItems.remove(at: indexPath.row)
} else {
self.regularItems.remove(at: indexPath.row)
}
self.applySnapshot()
completion(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction])
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if indexPath.section == 0 {
self.featuredItems.remove(at: indexPath.row)
} else {
self.regularItems.remove(at: indexPath.row)
}
self.applySnapshot()
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: HeaderView.identifier) as! HeaderView
header.configure(with: section == 0 ? "Featured" : "Regular")
return header
}
}
// App setup
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let navController = UINavigationController(rootViewController: ViewController())
window?.rootViewController = navController
window?.makeKeyAndVisible()
return true
}
}