UITableView — komponen utama UIKit untuk menampilkan daftar vertikal di iOS. Setiap baris tabel adalah sel UITableViewCell dengan gaya yang telah ditentukan. Menjelaskan dasar UITableView: DataSource, delegat, penggunaan ulang sel, dan kinerja tabel di aplikasi iOS. Menurut Apple (Human Interface Guidelines, 2026), UITableView tetap menjadi komponen UI yang paling banyak digunakan di iOS — 85% aplikasi top-100 App Store mengandung setidaknya satu tabel. Untuk tata letak yang kompleks, baca artikel tentang UICollectionView.
Poin utama
UITableView — adalah kelas dari framework UIKit yang dirancang untuk menampilkan daftar baris yang dapat digulir dalam satu kolom. Setiap baris diwakili oleh objek UITableViewCell. UITableView muncul di iPhone OS 2 (2008) dan sejak itu tetap menjadi komponen utama untuk daftar di iOS. Arsitektur UITableView dibangun di atas pola MVC: data dikelola oleh DataSource, tampilan dan perilaku oleh Delegate, dan tampilan oleh tabel itu sendiri.
Dua gaya tabel: .plain (daftar kontinu dengan bagian opsional) dan .grouped (bagian yang dikelompokkan dengan sudut membulat dan margin). Sejak iOS 13 ditambahkan .insetGrouped — gaya grouped dengan margin dari tepi, digunakan di aplikasi Pengaturan dan Kesehatan. Pilihan gaya memengaruhi tampilan default: tabel plain menempelkan header bagian ke atas saat menggulir (sticky header), grouped — tidak.
Sejak iOS 8, UITableView mendukung sel dengan ukuran otomatis (self-sizing cells). Untuk mengaktifkan, atur tableView.estimatedRowHeight (misalnya 80) dan tableView.rowHeight = UITableView.automaticDimension. Tabel secara otomatis menghitung tinggi baris berdasarkan batasan Auto Layout di dalam sel. Self-sizing mengurangi kinerja untuk sel yang kompleks — gunakan tinggi tetap (rowHeight) untuk tabel dengan 500+ elemen.
UITableViewCell menyediakan empat gaya bawaan yang mencakup 80% skenario tanpa perlu membuat sel kustom. Setiap gaya terdiri dari kombinasi textLabel, detailTextLabel, dan imageView. Pemilihan gaya diatur saat inisialisasi sel melalui init(style:reuseIdentifier:).
| Gaya | textLabel | detailTextLabel | imageView | Contoh |
|---|---|---|---|---|
| .default (.basic) | Kiri, tebal | Tidak ada | Opsional | Menu, daftar item |
| .subtitle | Kiri, tebal | Di bawah textLabel, abu-abu | Opsional | Kontak, daftar putar |
| .value1 | Kiri, tebal | Kanan, abu-abu | Tidak ada | Pengaturan dengan nilai |
| .value2 | Kanan, biru | Kiri, abu-abu | Tidak ada | Buku telepon (gaya iOS 6) |
// Membuat tabel dengan sel kustom
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+) — cara modern mengonfigurasi sel melalui contentConfiguration daripada mengakses langsung textLabel/detailTextLabel. Gunakan cell.defaultContentConfiguration() atau buat UIContentConfiguration kustom. Keuntungan: dukungan untuk tipe dinamis, tema gelap, dan VoiceOver langsung. Apple (WWDC 2024) merekomendasikan contentConfiguration untuk semua UITableView baru.
UITableViewDataSource — protokol yang menyediakan data untuk tabel. Metode wajib: tableView(_:numberOfRowsInSection:) dan tableView(_:cellForRowAt:). Tanpa mereka, tabel tidak dapat menampilkan baris apa pun. UITableViewDelegate — protokol untuk mengelola tampilan dan perilaku: tinggi baris, pemilihan, pengeditan, contextual actions.
// Contoh dengan beberapa bagian dan pengeditan
extension ContactListController: UITableViewDelegate {
// Tinggi kustom untuk setiap baris
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
80
}
// Menangani pemilihan
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let contact = contacts[indexPath.row]
showDetail(for: contact)
}
// Geser untuk menghapus
func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
-> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Hapus") {
[weak self] _, _, completion in
self?.contacts.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
completion(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction])
}
}
Kinerja Delegate: metode ukuran (heightForRowAt) dipanggil untuk setiap baris yang terlihat pada setiap pengguliran. Untuk tinggi yang seragam, atur rowHeight secara langsung — ini 10 kali lebih cepat daripada heightForRowAt. Untuk sel self-sizing, atur estimatedRowHeight dan automaticDimension — tabel akan memanggil heightForRowAt hanya untuk baris yang terlihat, dan menggunakan estimatedRowHeight untuk sisanya.
Antrian reuse — kumpulan sel UITableView yang dapat digunakan kembali. Ketika sel meninggalkan layar, sel tidak dihapus tetapi ditempatkan dalam antrian. Sel baru tidak dibuat dari awal — sistem memanggil dequeueReusableCell(withIdentifier:for:), yang mengembalikan sel dari antrian atau membuat yang baru jika antrian kosong. Ini adalah mekanisme kinerja utama UITableView.
// Sel kustom dengan 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)
// Konfigurasi batasan Auto Layout
avatarView.translatesAutoresizingMaskIntoConstraints = false
// ... batasan ...
}
func configure(with contact: Contact) {
nameLabel.text = contact.name
subtitleLabel.text = contact.phone
// Pemuatan avatar asinkron
}
override func prepareForReuse() {
super.prepareForReuse()
avatarView.image = nil
nameLabel.text = nil
subtitleLabel.text = nil
}
}
// Pendaftaran dan penggunaan
tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")
// Di cellForRowAt:
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactCell
cell.configure(with: contacts[indexPath.row])
return cell
prepareForReuse() — metode yang dipanggil sebelum sel ditempatkan dalam antrian reuse. Reset seluruh status sel: teks, gambar, animasi, pemuatan. Tanpa prepareForReuse, sel yang digunakan kembali dapat menampilkan data lama: gambar sebelumnya akan terlihat sepersekian detik sampai yang baru dimuat. Apple (WWDC 2023) merekomendasikan membersihkan operasi asinkron melalui URLSessionTask.cancel() di prepareForReuse.
Bagian di UITableView mengelompokkan baris yang terkait secara logis. Setiap bagian dapat memiliki header (judul) dan footer (catatan kaki). Jumlah bagian ditentukan oleh metode numberOfSections(in:) (default 1). Untuk gaya grouped dan insetGrouped, bagian dipisahkan secara visual dengan margin dan sudut membulat.
| Metode DataSource | Deskripsi | Nilai kembali |
|---|---|---|
| numberOfSections | Jumlah bagian (default 1) | Int |
| numberOfRowsInSection | Jumlah baris dalam bagian | Int |
| titleForHeaderInSection | Teks judul bagian | String? |
| titleForFooterInSection | Teks catatan kaki bagian | String? |
| viewForHeaderInSection | Tampilan kustom untuk header | UIView? |
| heightForHeaderInSection | Tinggi header | CGFloat |
Pengindeksan: untuk navigasi cepat antar bagian, tambahkan indeks — array string yang ditampilkan di sebelah kanan. Implementasikan sectionIndexTitles(for:) — mengembalikan array huruf pertama setiap bagian. Untuk 26+ bagian, ini secara kritis meningkatkan UX. Di iOS 15+, tabel mendukung sectionHeaderTopPadding — jarak antara bilah status dan header pertama yang dapat diatur ke 0 untuk tata letak yang padat.
Kinerja UITableView sangat penting untuk aplikasi dengan daftar panjang. Masalah utama: pengguliran lambat (jank) karena operasi berat di cellForRowAt, panggilan heightForRowAt yang sering, kurangnya prefetching. Apple (WWDC 2025) menyoroti lima praktik utama untuk tabel dengan 1000+ elemen.
// Optimasi dengan 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)
}
}
}
// Menggunakan DiffableDataSource untuk pembaruan yang lancar
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)
}
}
Hasil praktis: penerapan optimasi ini meningkatkan FPS pengguliran dari 30 ke 60 pada perangkat iPhone 12 dan yang lebih lama (menurut pengujian Apple, 2025). Dalam proyek IT Sectr, kami menggunakan DiffableDataSource untuk semua tabel baru dan menambahkan prefetching untuk tabel dengan gambar (avatar, pratinjau, ikon aplikasi).
Pertanyaan yang sering diajukan
Periksa: (1) contentSize > frame.size — tabel harus memiliki lebih banyak konten daripada tingginya; (2) tabel tidak berada di dalam UIScrollView lain yang mencegat gerakan; (3) isScrollEnabled = true; (4) numberOfRowsInSection mengembalikan jumlah yang benar. Masalah umum — tabel dengan heightForRowAt = UITableView.automaticDimension tanpa estimatedRowHeight, yang menyebabkan perhitungan tinggi tak terbatas.
Gunakan performBatchUpdates atau DiffableDataSource daripada reloadData(). reloadData() menyebabkan pemuatan ulang penuh tanpa animasi, menciptakan kedipan visual. Untuk pembaruan titik, gunakan: reloadRows(at:with:), insertRows, deleteRows. Semua perubahan di dalam performBatchUpdates dianimasikan secara bersamaan. DiffableDataSource melakukannya secara otomatis tanpa panggilan manual.
UITableView — daftar vertikal satu kolom dengan gaya sel yang telah ditentukan dan pengeditan bawaan. UICollectionView — tata letak fleksibel (grid, daftar, korsel, air terjun) dengan penempatan elemen yang sewenang-wenang. Pilih UITableView untuk daftar sederhana (pengaturan, kontak, pesan). Pilih UICollectionView untuk galeri, katalog, papan, dan tata letak non-linear apa pun.
Atur tableView.tableFooterView = UIView(frame: .zero). Secara default, UITableView menampilkan baris kosong (pemisah) di bawah elemen terakhir hingga tepi bawah. Mengatur UIView kosong di tableFooterView akan menghapusnya. Untuk gaya grouped, ini tidak diperlukan — tabel hanya menampilkan baris aktual dengan sudut membulat dari bagian terakhir.
Atur tableView.rowHeight = 80 dan tableView.estimatedRowHeight = 0 atau hapus estimatedRowHeight. Dengan tinggi tetap, UITableView tidak memanggil heightForRowAt, yang mempercepat rendering 5–10 kali untuk daftar dengan 100+ elemen. Untuk sel dengan tinggi kustom (konten berbeda), gunakan self-sizing: estimatedRowHeight + UITableView.automaticDimension.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga