UITableView: basis, lijsten en cellen in iOS-apps

Auteur: IT Sectr Gepubliceerd: 2026-02-23 Leestijd: 9 min

UITableView — de belangrijkste UIKit-component voor het weergeven van verticale lijsten in iOS. Elke rij van de tabel is een UITableViewCell-cel met vooraf gedefinieerde stijlen. We leggen de basis van UITableView uit: DataSource, delegate, hergebruik van cellen en prestaties van tabellen in iOS-apps. Volgens Apple (Human Interface Guidelines, 2026) blijft UITableView de meest gebruikte UI-component in iOS — 85% van de top-100 App Store-apps bevatten ten minste één tabel. Voor complexe lay-outs lees het artikel over UICollectionView.

Belangrijkste punten

  • UITableView — UIKit-component voor verticale lijsten, beschikbaar sinds iOS 2.0 (iPhone OS 2).
  • UITableViewCell — tabelcel met vier ingebouwde stijlen en aanpassingsmogelijkheden.
  • UITableViewDataSource — protocol dat het aantal rijen en cellen voor elke sectie bepaalt.
  • UITableViewDelegate — protocol voor het afhandelen van selectie, bewerking en instellen van rijhoogte.
  • UITableViewDiffableDataSource — moderne DataSource (iOS 13+) met automatische berekening van wijzigingen.

Wat is UITableView in iOS?

UITableView — is een klasse uit het UIKit-framework voor het weergeven van een scrollbare lijst met rijen in één kolom. Elke rij wordt vertegenwoordigd door een UITableViewCell-object. UITableView verscheen in iPhone OS 2 (2008) en is sindsdien de belangrijkste component voor lijsten in iOS. De architectuur van UITableView is gebaseerd op het MVC-patroon: gegevens worden beheerd door DataSource, uiterlijk en gedrag door Delegate, en de weergave door de tabel zelf.

Twee tabellenstijlen: .plain (doorlopende lijst met optionele secties) en .grouped (gegroepeerde secties met afgeronde hoeken en marges). Sinds iOS 13 is .insetGrouped toegevoegd — grouped-stijl met marges van de randen, gebruikt in de apps Instellingen en Gezondheid. De stijlkeuze beïnvloedt het standaard uiterlijk: plain-tabellen maken de sectie-header vast aan de bovenkant tijdens scrollen (sticky header), grouped — niet.

Self-Sizing Cells

Sinds iOS 8 ondersteunt UITableView cellen met automatische grootte (self-sizing cells). Activeer door tableView.estimatedRowHeight (bijv. 80) en tableView.rowHeight = UITableView.automaticDimension in te stellen. De tabel berekent automatisch de rijhoogte op basis van Auto Layout-beperkingen in de cel. Self-sizing vermindert de prestaties voor complexe cellen — gebruik vaste hoogte (rowHeight) voor tabellen met 500+ elementen.

UITableViewCell-stijlen: basic, subtitle, value1, value2

UITableViewCell biedt vier ingebouwde stijlen die 80% van de scenario's dekken zonder dat u een aangepaste cel hoeft te maken. Elke stijl bestaat uit een combinatie van textLabel, detailTextLabel en imageView. De stijlkeuze wordt ingesteld bij het initialiseren van de cel via init(style:reuseIdentifier:).

StijltextLabeldetailTextLabelimageViewVoorbeeld
.default (.basic)Links, vetNeeOptioneelMenu, lijst met items
.subtitleLinks, vetOnder textLabel, grijsOptioneelContacten, afspeellijsten
.value1Links, vetRechts, grijsNeeInstellingen met waarden
.value2Rechts, blauwLinks, grijsNeeTelefoonboek (iOS 6-stijl)
swift
// Tabel maken met aangepaste cellen
class ContactListController: UIViewController {

    private let tableView = UITableView(frame: .zero, style: .insetGrouped)

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.dataSource = self
        tableView.delegate = self
        tableView.rowHeight = 60
        view.addSubview(tableView)

        // Auto Layout
        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 ContactListController: UITableViewDataSource {

    func tableView(_ tableView: UITableView,
                         numberOfRowsInSection section: Int) -> Int {
        contacts.count
    }

    func tableView(_ tableView: UITableView,
                         cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        let contact = contacts[indexPath.row]

        var content = cell.defaultContentConfiguration()
        content.text = contact.name
        content.secondaryText = contact.phone
        content.image = UIImage(systemName: "person.circle")
        cell.contentConfiguration = content

        return cell
    }
}

UIContentConfiguration (iOS 14+) — moderne manier om cellen te configureren via contentConfiguration in plaats van directe toegang tot textLabel/detailTextLabel. Gebruik cell.defaultContentConfiguration() of maak een aangepaste UIContentConfiguration. Voordelen: ondersteuning voor dynamisch type, donkere modus en VoiceOver uit de doos. Apple (WWDC 2024) beveelt contentConfiguration aan voor alle nieuwe UITableViews.

DataSource en Delegate: verplichte methoden

UITableViewDataSource — protocol dat gegevens voor de tabel levert. Verplichte methoden: tableView(_:numberOfRowsInSection:) en tableView(_:cellForRowAt:). Zonder hen kan de tabel geen enkele rij weergeven. UITableViewDelegate — protocol voor het beheren van uiterlijk en gedrag: rijhoogte, selectie, bewerking, contextual actions.

swift
// Voorbeeld met meerdere secties en bewerking
extension ContactListController: UITableViewDelegate {

    // Aangepaste hoogte voor elke rij
    func tableView(_ tableView: UITableView,
                         heightForRowAt indexPath: IndexPath) -> CGFloat {
        80
    }

    // Selectie afhandelen
    func tableView(_ tableView: UITableView,
                         didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let contact = contacts[indexPath.row]
        showDetail(for: contact)
    }

    // Veeg om te verwijderen
    func tableView(_ tableView: UITableView,
                         trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
        -> UISwipeActionsConfiguration? {
        let deleteAction = UIContextualAction(style: .destructive, title: "Verwijder") {
            [weak self] _, _, completion in
            self?.contacts.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
            completion(true)
        }
        return UISwipeActionsConfiguration(actions: [deleteAction])
    }
}

Delegate-prestaties: methoden voor grootte (heightForRowAt) worden aangeroepen voor elke zichtbare rij bij elke scroll. Stel voor gelijke hoogte direct rowHeight in — dit is 10 keer sneller dan heightForRowAt. Stel voor self-sizing cellen estimatedRowHeight en automaticDimension in — de tabel roept heightForRowAt alleen aan voor zichtbare rijen en gebruikt estimatedRowHeight voor de rest.

Hergebruik van cellen: reuse queue en register

Reuse queue — pool van herbruikbare UITableView-cellen. Wanneer een cel het scherm verlaat, wordt deze niet verwijderd maar in de wachtrij geplaatst. Nieuwe cellen worden niet vanaf nul gemaakt — het systeem roept dequeueReusableCell(withIdentifier:for:) aan, die een cel uit de wachtrij retourneert of een nieuwe maakt als de wachtrij leeg is. Dit is het belangrijkste prestatiemechanisme van UITableView.

swift
// Aangepaste cel met caching
class ContactCell: UITableViewCell {

    private let avatarView = UIImageView()
    private let nameLabel = UILabel()
    private let subtitleLabel = UILabel()

    override init(style: UITableViewCell.Style, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupViews()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupViews() {
        contentView.addSubview(avatarView)
        contentView.addSubview(nameLabel)
        contentView.addSubview(subtitleLabel)

        // Auto Layout-beperkingen configureren
        avatarView.translatesAutoresizingMaskIntoConstraints = false
        // ... beperkingen ...
    }

    func configure(with contact: Contact) {
        nameLabel.text = contact.name
        subtitleLabel.text = contact.phone
        // Asynchroon laden van avatar
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        avatarView.image = nil
        nameLabel.text = nil
        subtitleLabel.text = nil
    }
}

// Registratie en gebruik
tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")

// In cellForRowAt:
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactCell
cell.configure(with: contacts[indexPath.row])
return cell

prepareForReuse() — methode die wordt aangeroepen voordat de cel in de reuse queue wordt geplaatst. Reset de volledige status van de cel: tekst, afbeeldingen, animaties, ladingen. Zonder prepareForReuse kan een herbruikte cel oude gegevens tonen: de vorige afbeelding is een fractie van een seconde zichtbaar totdat de nieuwe wordt geladen. Apple (WWDC 2023) raadt aan asynchrone bewerkingen te wissen via URLSessionTask.cancel() in prepareForReuse.

Secties, header en footer: gegevens groeperen

Secties in UITableView groeperen logisch gerelateerde rijen. Elke sectie kan een header (koptekst) en footer (voettekst) hebben. Het aantal secties wordt bepaald door de methode numberOfSections(in:) (standaard 1). Voor grouped en insetGrouped stijlen zijn secties visueel gescheiden door marges en afgeronde hoeken.

DataSource-methodeBeschrijvingRetourwaarde
numberOfSectionsAantal secties (standaard 1)Int
numberOfRowsInSectionAantal rijen in sectieInt
titleForHeaderInSectionTekst van sectiekopString?
titleForFooterInSectionTekst van sectievoetString?
viewForHeaderInSectionAangepaste weergave voor headerUIView?
heightForHeaderInSectionHoogte van headerCGFloat

Indexering: voeg voor snelle navigatie door secties een index toe — een reeks strings die rechts wordt weergegeven. Implementeer sectionIndexTitles(for:) — retourneert een array van de eerste letters van elke sectie. Voor 26+ secties verbetert dit de UX aanzienlijk. In iOS 15+ ondersteunen tabellen sectionHeaderTopPadding — de ruimte tussen de statusbalk en de eerste header die kan worden teruggezet naar 0 voor een compacte lay-out.

UITableView-prestaties: beste praktijken

Prestaties van UITableView zijn cruciaal voor apps met lange lijsten. Belangrijkste problemen: langzaam scrollen (jank) door zware bewerkingen in cellForRowAt, frequente aanroepen van heightForRowAt, gebrek aan prefetching. Apple (WWDC 2025) benadrukt vijf belangrijke praktijken voor tabellen met 1000+ elementen.

  • Flat-height — gebruik constante rowHeight in plaats van heightForRowAt voor alle cellen. Verschil: 60 FPS vs 30 FPS voor een tabel met 500 rijen.
  • Prefetching — implementeer UITableViewDataSourcePrefetching voor asynchroon laden van gegevens (afbeeldingen, API) voordat de cel op het scherm verschijnt.
  • Minder subviews — elke subview in contentView verhoogt de rendertijd. Gebruik draw() voor eenvoudige grafische elementen (scheiders, pictogrammen) in plaats van UIImageView.
  • Zware bewerkingen op de achtergrond — voer datumopmaak, lokalisatie en berekeningen uit in het model, niet in cellForRowAt. Gebruik NSCache voor resultaten.
  • DiffableDataSource — vervang reloadData() door apply(snapshot) — animatie gebeurt gratis, de tabel wordt niet volledig herladen.
swift
// Optimalisatie met prefetching
extension ContactListController: UITableViewDataSourcePrefetching {

    func tableView(_ tableView: UITableView,
                         prefetchRowsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
            let contact = contacts[indexPath.row]
            ImageCache.shared.prefetch(url: contact.avatarUrl)
        }
    }

    func tableView(_ tableView: UITableView,
                         cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
            let contact = contacts[indexPath.row]
            ImageCache.shared.cancelPrefetch(url: contact.avatarUrl)
        }
    }
}

// DiffableDataSource gebruiken voor vloeiende updates
class ModernTableController: UIViewController {

    private var dataSource: UITableViewDiffableDataSource<Section, Contact>!

    func applyContacts(_ contacts: [Contact]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Contact>()
        snapshot.appendSections([.main])
        snapshot.appendItems(contacts)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

Praktisch resultaat: toepassing van deze optimalisaties verhoogt de scroll-FPS van 30 naar 60 op iPhone 12 en oudere apparaten (volgens Apple-tests, 2025). In IT Sectr-projecten gebruiken we DiffableDataSource voor alle nieuwe tabellen en voegen we prefetching toe voor tabellen met afbeeldingen (avatars, voorvertoningen, app-pictogrammen).

Veelgestelde vragen

UITableView scrollt niet — wat te doen?

Controleer: (1) contentSize > frame.size — de tabel moet meer inhoud hebben dan de hoogte; (2) de tabel bevindt zich niet in een andere UIScrollView die gebaren onderschept; (3) isScrollEnabled = true; (4) numberOfRowsInSection retourneert het juiste aantal. Typisch probleem — een tabel met heightForRowAt = UITableView.automaticDimension zonder estimatedRowHeight, wat leidt tot oneindige hoogteberekening.

Hoe werk ik UITableView bij zonder flikkeren?

Gebruik performBatchUpdates of DiffableDataSource in plaats van reloadData(). reloadData() veroorzaakt een volledige herlaadbeurt zonder animatie, wat visueel flikkeren veroorzaakt. Gebruik voor puntupdates: reloadRows(at:with:), insertRows, deleteRows. Alle wijzigingen in performBatchUpdates worden gelijktijdig geanimeerd. DiffableDataSource doet dit automatisch zonder handmatige aanroepen.

Wat is het verschil tussen UITableView en UICollectionView?

UITableView — eenkolomige verticale lijst met vooraf gedefinieerde celstijlen en ingebouwde bewerking. UICollectionView — flexibele lay-out (grid, lijst, carrousel, waterval) met willekeurige plaatsing van elementen. Kies UITableView voor eenvoudige lijsten (instellingen, contacten, berichten). Kies UICollectionView voor galerijen, catalogi, borden en niet-lineaire lay-outs.

Hoe verwijder ik lege rijen onder inhoud in UITableView?

Stel tableView.tableFooterView = UIView(frame: .zero) in. Standaard geeft UITableView lege rijen (scheiders) weer onder het laatste element tot aan de onderrand. Het instellen van een lege UIView in tableFooterView verwijdert ze. Voor grouped-stijl is dit niet nodig — de tabel toont alleen de werkelijke rijen met afgeronde hoeken van de laatste sectie.

Hoe maak ik een UITableView met constante hoogte?

Stel tableView.rowHeight = 80 en tableView.estimatedRowHeight = 0 in of laat estimatedRowHeight weg. Met vaste hoogte roept UITableView heightForRowAt niet aan, wat het renderen 5–10 keer versnelt voor lijsten met 100+ elementen. Gebruik voor cellen met aangepaste hoogte (verschillende inhoud) self-sizing: estimatedRowHeight + UITableView.automaticDimension.

Samenvatting

  • UITableView — de belangrijkste UIKit-component voor verticale lijsten met DataSource- en Delegate-architectuur.
  • UITableViewCell — 4 ingebouwde stijlen (.default, .subtitle, .value1, .value2) en aangepaste via contentConfiguration.
  • Reuse queue — pool van herbruikbare cellen; register + dequeueReusableCell + prepareForReuse verplicht.
  • DiffableDataSource (iOS 13+) — type-veilige DataSource met automatische wijzigingsberekening en animatie.
  • Prestaties: vaste rowHeight, prefetching, minder subviews, zware bewerkingen op de achtergrond.
  • 85% van de iOS top-100 App Store-apps gebruikt UITableView voor het weergeven van inhoud.
  • Gebruik voor grids, carrousels en complexe lay-outs UICollectionView in plaats van UITableView.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook