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 — 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.
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 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:).
| Stijl | textLabel | detailTextLabel | imageView | Voorbeeld |
|---|---|---|---|---|
| .default (.basic) | Links, vet | Nee | Optioneel | Menu, lijst met items |
| .subtitle | Links, vet | Onder textLabel, grijs | Optioneel | Contacten, afspeellijsten |
| .value1 | Links, vet | Rechts, grijs | Nee | Instellingen met waarden |
| .value2 | Rechts, blauw | Links, grijs | Nee | Telefoonboek (iOS 6-stijl) |
// 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.
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.
// 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.
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.
// 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 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-methode | Beschrijving | Retourwaarde |
|---|---|---|
| numberOfSections | Aantal secties (standaard 1) | Int |
| numberOfRowsInSection | Aantal rijen in sectie | Int |
| titleForHeaderInSection | Tekst van sectiekop | String? |
| titleForFooterInSection | Tekst van sectievoet | String? |
| viewForHeaderInSection | Aangepaste weergave voor header | UIView? |
| heightForHeaderInSection | Hoogte van header | CGFloat |
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.
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.
// 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
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.
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.
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.
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.
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
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.
Lees ook