Practical examples of UITableView implementation in Swift for modern iOS apps

If you build iOS apps, you live with tables. Messages, email, settings screens, search results — they all lean on UITableView or its SwiftUI cousins. That’s why strong, real-world examples of UITableView implementation in Swift are worth studying instead of copy‑pasting the same tired boilerplate. In this guide, we’ll walk through several practical examples of UITableView implementation examples in Swift, from the classic static list to diffable data sources and async data loading. Rather than theory, we’ll focus on how you would actually wire these patterns into a 2024‑era iOS project: modern Swift, iOS 15+ APIs, diffable data, and clean architecture. These examples include simple prototypes you can drop into a playground as well as production‑ready patterns you’d use in a real app. Along the way, we’ll highlight best practices, common mistakes, and where UITableView still makes sense even as SwiftUI and UICollectionView keep getting more attention.
Written by
Jamie
Published

Before getting clever, you need a clean, minimal baseline. This is the smallest practical example of UITableView implementation in Swift that still respects modern patterns.

import UIKit

final class SimpleListViewController: UIViewController {
    private let tableView = UITableView(frame: .zero, style: .plain)
    private let items = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Fruits"
        view.backgroundColor = .systemBackground

        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")

        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

extension SimpleListViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        items.count
    }

    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        var config = cell.defaultContentConfiguration()
        config.text = items[indexPath.row]
        cell.contentConfiguration = config
        return cell
    }
}

extension SimpleListViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        print("Selected: \(items[indexPath.row])")
    }
}

This is the reference point. Every other example of UITableView implementation in Swift builds on this pattern: set up the table, adopt UITableViewDataSource and UITableViewDelegate, and keep your configuration inside cellForRowAt.


Real examples: grouped sections, headers, and footers

Most real apps don’t show a flat list. Settings, profile screens, and form‑like layouts use grouped sections. These examples of UITableView implementation in Swift show how to structure data and customize headers and footers.

struct SettingsSection {
    let title: String
    let items: [String]
}

final class SettingsViewController: UIViewController {
    private let tableView = UITableView(frame: .zero, style: .insetGrouped)

    private let sections: [SettingsSection] = [
        .init(title: "Account", items: ["Profile", "Password", "Two-Factor Auth"]),
        .init(title: "Notifications", items: ["Push", "Email", "SMS"]),
        .init(title: "About", items: ["Terms of Service", "Privacy Policy"])
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Settings"
        view.backgroundColor = .systemBackground

        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")

        view.addSubview(tableView)
        tableView.frame = view.bounds
        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }
}

extension SettingsViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int { sections.count }

    func tableView(_ tableView: UITableView,
                   numberOfRowsInSection section: Int) -> Int {
        sections[section].items.count
    }

    func tableView(_ tableView: UITableView,
                   titleForHeaderInSection section: Int) -> String? {
        sections[section].title
    }

    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        var config = cell.defaultContentConfiguration()
        config.text = sections[indexPath.section].items[indexPath.row]
        cell.accessoryType = .disclosureIndicator
        cell.contentConfiguration = config
        return cell
    }
}

This is one of the best examples of how to map your model to sections cleanly: a struct for each section, no magic numbers, and titles coming from data instead of hard‑coded switches.


Custom cell example of UITableView implementation in Swift

The moment you need avatars, subtitles, or custom layouts, stock cells stop cutting it. Here’s a custom cell example of UITableView implementation in Swift using Auto Layout and modern configuration.

final class UserCell: UITableViewCell {
    private let avatarImageView = UIImageView()
    private let nameLabel = UILabel()
    private 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() {
        avatarImageView.layer.cornerRadius = 20
        avatarImageView.clipsToBounds = true
        avatarImageView.backgroundColor = .secondarySystemBackground

        nameLabel.font = .preferredFont(forTextStyle: .headline)
        subtitleLabel.font = .preferredFont(forTextStyle: .subheadline)
        subtitleLabel.textColor = .secondaryLabel

        let stack = UIStackView(arrangedSubviews: [nameLabel, subtitleLabel])
        stack.axis = .vertical
        stack.spacing = 2

        contentView.addSubview(avatarImageView)
        contentView.addSubview(stack)

        avatarImageView.translatesAutoresizingMaskIntoConstraints = false
        stack.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            avatarImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            avatarImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            avatarImageView.widthAnchor.constraint(equalToConstant: 40),
            avatarImageView.heightAnchor.constraint(equalToConstant: 40),

            stack.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 12),
            stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
            stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8)
        ])
    }

    func configure(name: String, subtitle: String) {
        nameLabel.text = name
        subtitleLabel.text = subtitle
    }
}

struct User {
    let name: String
    let role: String
}

final class UsersViewController: UIViewController {
    private let tableView = UITableView()
    private let users: [User] = [
        .init(name: "Alice", role: "Admin"),
        .init(name: "Bob", role: "Editor"),
        .init(name: "Carol", role: "Viewer")
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Team"
        view.backgroundColor = .systemBackground

        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(UserCell.self, forCellReuseIdentifier: "UserCell")

        view.addSubview(tableView)
        tableView.frame = view.bounds
        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }
}

extension UsersViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        users.count
    }

    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(
            withIdentifier: "UserCell",
            for: indexPath
        ) as? UserCell else {
            return UITableViewCell()
        }
        let user = users[indexPath.row]
        cell.configure(name: user.name, subtitle: user.role)
        return cell
    }
}

Among the best examples of UITableView implementation in Swift, this pattern scales nicely when you later add async image loading, skeleton states, or accessibility tweaks.


Editable rows: swipe to delete and insert

Users expect to manage lists: deleting emails, removing items from a cart, or reordering favorites. These real examples of UITableView implementation in Swift show how to support editing without turning your controller into spaghetti.

final class EditableListViewController: UIViewController {
    private let tableView = UITableView()
    private var items = ["Milk", "Eggs", "Bread", "Coffee"]

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Groceries"
        navigationItem.rightBarButtonItem = editButtonItem

        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")

        view.addSubview(tableView)
        tableView.frame = view.bounds
        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }

    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        tableView.setEditing(editing, animated: animated)
    }
}

extension EditableListViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        items.count
    }

    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        var config = cell.defaultContentConfiguration()
        config.text = items[indexPath.row]
        cell.contentConfiguration = config
        return cell
    }

    func tableView(_ tableView: UITableView,
                   canEditRowAt indexPath: IndexPath) -> Bool {
        true
    }

    func tableView(_ tableView: UITableView,
                   commit editingStyle: UITableViewCell.EditingStyle,
                   forRowAt indexPath: IndexPath) {
        switch editingStyle {
        case .delete:
            items.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
        case .insert:
            items.insert("New Item", at: indexPath.row)
            tableView.insertRows(at: [indexPath], with: .automatic)
        default:
            break
        }
    }
}

extension EditableListViewController: UITableViewDelegate {}

If you’re building a to‑do list or habit tracker, this is one of the best examples to start from: editing is fully integrated with your data model, not just the UI.


Diffable data source: modern examples of UITableView implementation in Swift

Starting in iOS 13, Apple introduced diffable data sources, and by 2024 this should be your default for any non‑trivial list. These examples of UITableView implementation examples in Swift with diffable data reduce bugs around index paths and batch updates.

final class DiffableListViewController: UIViewController {
    enum Section { case main }

    struct Item: Hashable {
        let id = UUID()
        let title: String
    }

    private let tableView = UITableView()

    private lazy var dataSource = UITableViewDiffableDataSource<Section, Item>(
        tableView: tableView
    ) { tableView, indexPath, itemIdentifier in
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        var config = cell.defaultContentConfiguration()
        config.text = itemIdentifier.title
        cell.contentConfiguration = config
        return cell
    }

    private var items: [Item] = [
        .init(title: "Inbox"),
        .init(title: "Today"),
        .init(title: "Upcoming")
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Diffable"
        view.backgroundColor = .systemBackground

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        view.addSubview(tableView)
        tableView.frame = view.bounds
        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        applySnapshot(animatingDifferences: false)
    }

    private func applySnapshot(animatingDifferences: Bool = true) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items, toSection: .main)
        dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
    }

    func addItem(title: String) {
        items.append(.init(title: title))
        applySnapshot()
    }
}

For lists that change often — think chats, notifications, or live data — this example of UITableView implementation in Swift with a diffable data source is significantly easier to maintain than manual insertRows and deleteRows calls.

For background reading, Apple’s own documentation on diffable data sources is still worth a look: https://developer.apple.com/documentation/uikit/uitableviewdiffabledatasource


Async data loading: networking and pagination

In 2024 and 2025, almost every table you build talks to an API. Here’s how you might structure real examples of UITableView implementation in Swift that fetch data asynchronously using async/await.

struct Post: Decodable, Hashable {
    let id: Int
    let title: String
}

final class PostsViewController: UIViewController {
    enum Section { case main }

    private let tableView = UITableView()
    private lazy var dataSource = UITableViewDiffableDataSource<Section, Post>(
        tableView: tableView
    ) { tableView, indexPath, post in
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        var config = cell.defaultContentConfiguration()
        config.text = post.title
        cell.contentConfiguration = config
        return cell
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Posts"
        view.backgroundColor = .systemBackground

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        view.addSubview(tableView)
        tableView.frame = view.bounds
        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        Task { await loadPosts() }
    }

    private func apply(posts: [Post]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Post>()
        snapshot.appendSections([.main])
        snapshot.appendItems(posts)
        dataSource.apply(snapshot, animatingDifferences: true)
    }

    private func loadPosts() async {
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return }

        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            let posts = try JSONDecoder().decode([Post].self, from: data)
            await MainActor.run { self.apply(posts: posts) }
        } catch {
            print("Failed to load posts: \(error)")
        }
    }
}

This example of UITableView implementation in Swift is closer to production reality: network calls, error handling, and UI updates marshaled back to the main actor.

If you’re thinking about performance and responsiveness, Apple’s Human Interface Guidelines are still the north star for list design and perceived speed: https://developer.apple.com/design/human-interface-guidelines/ios/overview/themes


Accessibility and dynamic type in table views

UITableView can easily become an accessibility nightmare if you ignore Dynamic Type and VoiceOver. Modern examples of UITableView implementation in Swift should always respect content size categories and provide clear labels.

Key practices you can adopt in all the examples include:

  • Use preferredFont(forTextStyle:) for labels inside cells so text scales with user settings.
  • Avoid hard‑coded cell heights; let Auto Layout drive row height with tableView.rowHeight = UITableView.automaticDimension and a reasonable estimatedRowHeight.
  • Provide accessibilityLabel and accessibilityHint for custom cells where the visual layout doesn’t map 1:1 to text.

If you want to go deeper into accessible list design, the W3C Web Accessibility Initiative has guidelines that translate well to mobile list views: https://www.w3.org/WAI/fundamentals/accessibility-intro/

When you review examples of UITableView implementation examples in Swift, ask yourself: will this still work for someone using the largest text size or VoiceOver? If not, fix it before you ship.


UITableView vs SwiftUI List in 2024–2025

You might wonder why you should care about more examples of UITableView implementation in Swift when SwiftUI exists. In 2024 and 2025, teams typically land on a hybrid approach:

  • UIKit (UITableView) for legacy screens, highly customized layouts, or when you need mature APIs like diffable data sources and fine‑grained control.
  • SwiftUI List for new features where you can accept some constraints in exchange for faster iteration.

The good news: the patterns you learn from these examples include clean separation of data, cell configuration, and navigation. Those ideas transfer directly to SwiftUI’s List and ForEach.

For long‑lived apps in regulated spaces (finance, health, education), UIKit is still heavily used because of its stability and long track record. If you’re in those industries, studying practical examples of UITableView implementation in Swift is still very much worth your time.


FAQ: common questions about UITableView examples

Q: Where can I find more real examples of UITableView implementation in Swift?
Apple’s sample code on the Developer site is still the best starting point, especially projects that demonstrate diffable data sources and list layouts: https://developer.apple.com/sample-code. From there, GitHub is full of open‑source apps that show another example of production‑grade table view setups.

Q: What is a good example of migrating from a legacy UITableView to a diffable data source?
A practical migration path is to keep your existing UITableView and delegate methods for layout, but replace DataSource methods with UITableViewDiffableDataSource. Start by defining a section enum and an item type that conforms to Hashable, then gradually replace calls to reloadData() with snapshot updates. One of the best examples is Apple’s own diffable sample projects, which show step‑by‑step adoption.

Q: Do these examples include support for pagination and infinite scrolling?
The async posts example is a starting point. To add pagination, watch for willDisplay on the last row, trigger another network request, append new items to your array, and re‑apply the diffable snapshot. Many production apps follow this pattern with a loading indicator cell at the bottom.

Q: Are there examples of UITableView implementation in Swift that work well with MVVM or Clean Architecture?
Yes. The key idea is to move data fetching and transformation into a view model, then expose a simple array or snapshot description to the view controller. Your table view code becomes mostly about binding: when the view model publishes a new snapshot, you apply it. The diffable examples of UITableView implementation in Swift in this article adapt very well to MVVM.

Q: When should I choose UICollectionView instead of UITableView for lists?
If your layout is strictly one column, vertically scrolling, and mostly text, UITableView is still fine. Once you need grid layouts, complex compositional sections, or horizontally scrolling sub‑sections, UICollectionView is a better fit. The mental model you learn from these table view examples carries over to collection views pretty naturally.

Explore More Swift Code Snippets

Discover more examples and insights in this category.

View All Swift Code Snippets