UITableView — die Hauptkomponente von UIKit zur Anzeige vertikaler Listen in iOS. Jede Tabellenzeile ist eine UITableViewCell mit vordefinierten Stilen. Wir erklären die Grundlagen von UITableView: DataSource, Delegat, Wiederverwendung von Zellen und Tabellenleistung in iOS-Anwendungen. Laut Apple (Human Interface Guidelines, 2026) bleibt UITableView die am häufigsten verwendete UI-Komponente in iOS — 85% der Apps in den Top 100 des App Store enthalten mindestens eine Tabelle. Für komplexe Layouts lesen Sie den Artikel über UICollectionView.
Wichtige Punkte
UITableView ist eine Klasse aus dem UIKit-Framework, die zur Anzeige einer scrollbaren Liste von Zeilen in einer einzigen Spalte entwickelt wurde. Jede Zeile wird durch ein UITableViewCell-Objekt dargestellt. UITableView erschien erstmals in iPhone OS 2 (2008) und ist seitdem die Hauptkomponente für Listen in iOS. Die Architektur von UITableView basiert auf dem MVC-Muster: Daten werden von der DataSource verwaltet, Aussehen und Verhalten vom Delegate und die Darstellung von der Tabelle selbst.
Zwei Tabellenstile: .plain (durchgehende Liste mit optionalen Abschnitten) und .grouped (gruppierte Abschnitte mit abgerundeten Ecken und Rändern). iOS 13 fügte .insetGrouped hinzu — einen gruppierten Stil mit Randabständen, der in den Apps Einstellungen und Gesundheit verwendet wird. Die Wahl des Stils beeinflusst das Standard-Erscheinungsbild: Plain-Tabellen fixieren Abschnittsüberschriften beim Scrollen oben (sticky header), grouped-Tabellen nicht.
Ab iOS 8 unterstützt UITableView selbstanpassende Zellen (self-sizing cells). Zum Aktivieren setzen Sie tableView.estimatedRowHeight (z. B. 80) und tableView.rowHeight = UITableView.automaticDimension. Die Tabelle berechnet die Zeilenhöhe automatisch basierend auf den Auto-Layout-Beschränkungen innerhalb der Zelle. Self-Sizing reduziert die Leistung bei komplexen Zellen — verwenden Sie eine feste Höhe (rowHeight) für Tabellen mit 500+ Elementen.
UITableViewCell bietet vier integrierte Stile, die 80% der Szenarien abdecken, ohne dass eine benutzerdefinierte Zelle erstellt werden muss. Jeder Stil besteht aus einer Kombination von textLabel, detailTextLabel und imageView. Der Stil wird beim Initialisieren der Zelle über init(style:reuseIdentifier:) ausgewählt.
| Stil | textLabel | detailTextLabel | imageView | Beispiel |
|---|---|---|---|---|
| .default (.basic) | Links, fett | Keiner | Optional | Menü, Liste von Elementen |
| .subtitle | Links, fett | Unter textLabel, grau | Optional | Kontakte, Wiedergabelisten |
| .value1 | Links, fett | Rechts, grau | Keiner | Einstellungen mit Werten |
| .value2 | Rechts, blau | Links, grau | Keiner | Telefonbuch (iOS 6-Stil) |
// Erstellen einer Tabelle mit benutzerdefinierten Zellen
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+) — eine moderne Möglichkeit, Zellen über contentConfiguration zu konfigurieren, anstatt direkt auf textLabel/detailTextLabel zuzugreifen. Verwenden Sie cell.defaultContentConfiguration() oder erstellen Sie eine benutzerdefinierte UIContentConfiguration. Vorteile: integrierte Unterstützung für Dynamic Type, Dunkelmodus und VoiceOver. Apple (WWDC 2024) empfiehlt contentConfiguration für alle neuen UITableViews.
UITableViewDataSource — ein Protokoll, das Daten für die Tabelle bereitstellt. Erforderliche Methoden: tableView(_:numberOfRowsInSection:) und tableView(_:cellForRowAt:). Ohne sie kann die Tabelle keine einzige Zeile anzeigen. UITableViewDelegate — ein Protokoll zur Verwaltung von Aussehen und Verhalten: Zeilenhöhe, Auswahl, Bearbeitung, kontextbezogene Aktionen.
// Beispiel mit mehreren Abschnitten und Bearbeitung
extension ContactListController: UITableViewDelegate {
// Benutzerdefinierte Höhe für jede Zeile
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
80
}
// Auswahlbehandlung
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let contact = contacts[indexPath.row]
showDetail(for: contact)
}
// Swipe-to-delete
func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
-> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Löschen") {
[weak self] _, _, completion in
self?.contacts.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
completion(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction])
}
}
Delegate-Leistung: Größenmethoden (heightForRowAt) werden bei jedem Scrollen für jede sichtbare Zeile aufgerufen. Für eine gleichmäßige Höhe setzen Sie direkt rowHeight — das ist 10-mal schneller als heightForRowAt. Für selbstanpassende Zellen setzen Sie estimatedRowHeight und automaticDimension — die Tabelle ruft heightForRowAt nur für sichtbare Zeilen auf und verwendet estimatedRowHeight für die restlichen.
Wiederverwendungs-Warteschlange — ein Pool wiederverwendbarer UITableView-Zellen. Wenn eine Zelle aus dem Bildschirm scrollt, wird sie nicht gelöscht, sondern in die Warteschlange gestellt. Neue Zellen werden nicht von Grund auf erstellt — das System ruft dequeueReusableCell(withIdentifier:for:) auf, das eine Zelle aus der Warteschlange zurückgibt oder eine neue erstellt, wenn die Warteschlange leer ist. Dies ist der wichtigste Leistungsmechanismus von UITableView.
// Benutzerdefinierte Zelle mit 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)
// Konfigurieren von Auto-Layout-Beschränkungen
avatarView.translatesAutoresizingMaskIntoConstraints = false
// ... constraints ...
}
func configure(with contact: Contact) {
nameLabel.text = contact.name
subtitleLabel.text = contact.phone
// Asynchrones Laden von Avataren
}
override func prepareForReuse() {
super.prepareForReuse()
avatarView.image = nil
nameLabel.text = nil
subtitleLabel.text = nil
}
}
// Registrierung und Verwendung
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() — eine Methode, die aufgerufen wird, bevor eine Zelle in die Wiederverwendungs-Warteschlange gestellt wird. Setzen Sie den gesamten Zellenzustand zurück: Text, Bilder, Animationen, Ladevorgänge. Ohne prepareForReuse kann eine wiederverwendete Zelle alte Daten anzeigen: Das vorherige Bild ist für einen Bruchteil einer Sekunde sichtbar, bis das neue geladen wird. Apple (WWDC 2023) empfiehlt, asynchrone Operationen über URLSessionTask.cancel() in prepareForReuse abzubrechen.
Abschnitte in UITableView gruppieren logisch zusammengehörige Zeilen. Jeder Abschnitt kann eine Kopfzeile (header) und eine Fußzeile (footer) haben. Die Anzahl der Abschnitte wird durch die Methode numberOfSections(in:) bestimmt (Standard ist 1). Bei grouped- und insetGrouped-Stilen sind die Abschnitte visuell durch Ränder und abgerundete Ecken getrennt.
| DataSource-Methode | Beschreibung | Rückgabewert |
|---|---|---|
| numberOfSections | Anzahl der Abschnitte (Standard 1) | Int |
| numberOfRowsInSection | Anzahl der Zeilen in einem Abschnitt | Int |
| titleForHeaderInSection | Text der Abschnittsüberschrift | String? |
| titleForFooterInSection | Text der Abschnittsfußzeile | String? |
| viewForHeaderInSection | Benutzerdefinierte Ansicht für Kopfzeile | UIView? |
| heightForHeaderInSection | Höhe der Kopfzeile | CGFloat |
Indizierung: Für die schnelle Navigation zwischen Abschnitten fügen Sie einen Index-Titel hinzu — ein Array von Zeichenfolgen, die auf der rechten Seite angezeigt werden. Implementieren Sie sectionIndexTitles(for:) — gibt ein Array der ersten Buchstaben jedes Abschnitts zurück. Bei 26+ Abschnitten verbessert dies die UX erheblich. Unter iOS 15+ unterstützen Tabellen sectionHeaderTopPadding — den Abstand zwischen der Statusleiste und der ersten Kopfzeile, der für ein dichtes Layout auf 0 zurückgesetzt werden kann.
Die Leistung von UITableView ist für Apps mit langen Listen entscheidend. Häufige Probleme: langsames Scrollen (Jank) aufgrund schwerer Operationen in cellForRowAt, häufige heightForRowAt-Aufrufe, fehlendes Prefetching. Apple (WWDC 2025) hebt fünf wichtige Praktiken für Tabellen mit 1000+ Elementen hervor.
// Optimierung mit 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)
}
}
}
// Verwendung von DiffableDataSource für flüssige Aktualisierungen
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)
}
}
Praktisches Ergebnis: Die Anwendung dieser Optimierungen erhöht die Scroll-FPS von 30 auf 60 auf iPhone 12 und neueren Geräten (laut Apple-Testdaten, 2025). In IT Sectr-Projekten verwenden wir DiffableDataSource für alle neuen Tabellen und fügen Prefetching für Tabellen mit Bildern hinzu (Avatare, Vorschauen, App-Symbole).
Häufig gestellte Fragen
Überprüfen Sie: (1) contentSize > frame.size — die Tabelle muss mehr Inhalt als ihre Höhe haben; (2) die Tabelle befindet sich nicht in einer anderen UIScrollView, die Gesten abfängt; (3) isScrollEnabled = true; (4) numberOfRowsInSection gibt die korrekte Anzahl zurück. Ein typisches Problem ist eine Tabelle mit heightForRowAt = UITableView.automaticDimension ohne estimatedRowHeight, was zu einer unendlichen Höhenberechnung führt.
Verwenden Sie performBatchUpdates oder DiffableDataSource anstelle von reloadData(). reloadData() verursacht ein vollständiges Neuladen ohne Animation, was visuelles Flackern erzeugt. Für gezielte Aktualisierungen verwenden Sie: reloadRows(at:with:), insertRows, deleteRows. Alle Änderungen innerhalb von performBatchUpdates werden gleichzeitig animiert. DiffableDataSource erledigt dies automatisch ohne manuelle Aufrufe.
UITableView ist eine einspaltige vertikale Liste mit vordefinierten Zellstilen und integrierter Bearbeitung. UICollectionView ist ein flexibles Layout (Raster, Liste, Karussell, Wasserfall) mit beliebiger Elementpositionierung. Wählen Sie UITableView für einfache Listen (Einstellungen, Kontakte, Nachrichten). Wählen Sie UICollectionView für Galerien, Kataloge, Boards und alle nichtlinearen Layouts.
Setzen Sie tableView.tableFooterView = UIView(frame: .zero). Standardmäßig zeigt UITableView leere Zeilen (Trennlinien) unterhalb des letzten Elements bis zum unteren Rand an. Das Setzen einer leeren UIView als tableFooterView entfernt sie. Für den grouped-Stil ist dies nicht erforderlich — die Tabelle zeigt nur die tatsächlichen Zeilen mit abgerundeten Ecken im letzten Abschnitt an.
Setzen Sie tableView.rowHeight = 80 und tableView.estimatedRowHeight = 0 oder lassen Sie estimatedRowHeight weg. Bei fester Höhe ruft UITableView heightForRowAt nicht auf, was das Rendern für Listen mit 100+ Elementen um das 5- bis 10-fache beschleunigt. Für Zellen mit benutzerdefinierter Höhe (unterschiedlicher Inhalt) verwenden Sie Self-Sizing: estimatedRowHeight + UITableView.automaticDimension.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch