UITableView: dasar, daftar dan sel di aplikasi iOS

Penulis: IT Sectr Diterbitkan: 2026-02-23 Waktu membaca: 9 mnt

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 — komponen UIKit untuk daftar vertikal, tersedia sejak iOS 2.0 (iPhone OS 2).
  • UITableViewCell — sel tabel dengan empat gaya bawaan dan kemampuan kustomisasi.
  • UITableViewDataSource — protokol yang menentukan jumlah baris dan sel untuk setiap bagian.
  • UITableViewDelegate — protokol untuk menangani pemilihan, pengeditan, dan pengaturan tinggi baris.
  • UITableViewDiffableDataSource — DataSource modern (iOS 13+) dengan perhitungan perubahan otomatis.

Apa itu UITableView di iOS?

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.

Self-Sizing Cells

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.

Gaya UITableViewCell: basic, subtitle, value1, value2

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:).

GayatextLabeldetailTextLabelimageViewContoh
.default (.basic)Kiri, tebalTidak adaOpsionalMenu, daftar item
.subtitleKiri, tebalDi bawah textLabel, abu-abuOpsionalKontak, daftar putar
.value1Kiri, tebalKanan, abu-abuTidak adaPengaturan dengan nilai
.value2Kanan, biruKiri, abu-abuTidak adaBuku telepon (gaya iOS 6)
swift
// 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.

DataSource dan Delegate: metode wajib

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.

swift
// 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.

Penggunaan ulang sel: antrian reuse dan register

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.

swift
// 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, header dan footer: pengelompokan data

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 DataSourceDeskripsiNilai kembali
numberOfSectionsJumlah bagian (default 1)Int
numberOfRowsInSectionJumlah baris dalam bagianInt
titleForHeaderInSectionTeks judul bagianString?
titleForFooterInSectionTeks catatan kaki bagianString?
viewForHeaderInSectionTampilan kustom untuk headerUIView?
heightForHeaderInSectionTinggi headerCGFloat

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: praktik terbaik

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.

  • Flat-height — gunakan rowHeight konstan daripada heightForRowAt untuk semua sel. Perbedaan: 60 FPS vs 30 FPS untuk tabel dengan 500 baris.
  • Prefetching — implementasikan UITableViewDataSourcePrefetching untuk memuat data secara asinkron (gambar, API) sebelum sel muncul di layar.
  • Lebih sedikit subview — setiap subview di contentView meningkatkan waktu render. Gunakan draw() untuk grafik sederhana (pemisah, ikon) daripada UIImageView.
  • Operasi berat di latar belakang — lakukan pemformatan tanggal, lokalisasi, kalkulasi di model, bukan di cellForRowAt. Gunakan NSCache untuk hasil.
  • DiffableDataSource — ganti reloadData() dengan apply(snapshot) — animasi terjadi secara gratis, tabel tidak dimuat ulang sepenuhnya.
swift
// 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

UITableView tidak bisa digulir — apa yang harus dilakukan?

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.

Bagaimana cara memperbarui UITableView tanpa berkedip?

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.

Apa perbedaan antara UITableView dan UICollectionView?

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.

Bagaimana cara menghapus baris kosong di bawah konten di UITableView?

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.

Bagaimana cara membuat UITableView dengan tinggi konstan?

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

  • UITableView — komponen utama UIKit untuk daftar vertikal dengan arsitektur DataSource dan Delegate.
  • UITableViewCell — 4 gaya bawaan (.default, .subtitle, .value1, .value2) dan kustom melalui contentConfiguration.
  • Antrian reuse — kumpulan sel yang dapat digunakan kembali; register + dequeueReusableCell + prepareForReuse wajib.
  • DiffableDataSource (iOS 13+) — DataSource yang aman tipe dengan perhitungan perubahan otomatis dan animasi.
  • Kinerja: rowHeight tetap, prefetching, lebih sedikit subview, operasi berat di latar belakang.
  • 85% aplikasi iOS top-100 App Store menggunakan UITableView untuk menampilkan konten.
  • Untuk grid, korsel, dan tata letak kompleks, gunakan UICollectionView daripada UITableView.

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.

Diskusikan proyek

Baca juga