UITableView: baze, liste și celule în aplicațiile iOS

Autor: IT Sectr Publicat: 2026-02-23 Timp de citire: 9 min

UITableView — componenta principală UIKit pentru afișarea listelor verticale în iOS. Fiecare rând al tabelului este o celulă UITableViewCell cu stiluri predefinite. Explicăm bazele UITableView: DataSource, delegat, reutilizarea celulelor și performanța tabelelor în aplicațiile iOS. Potrivit Apple (Human Interface Guidelines, 2026), UITableView rămâne cea mai utilizată componentă UI în iOS — 85% dintre aplicațiile din top-100 App Store conțin cel puțin un tabel. Pentru aspecte complexe citiți articolul despre UICollectionView.

Principalele puncte

  • UITableView — componentă UIKit pentru liste verticale, disponibilă din iOS 2.0 (iPhone OS 2).
  • UITableViewCell — celulă de tabel cu patru stiluri încorporate și posibilitatea de personalizare.
  • UITableViewDataSource — protocol care definește numărul de rânduri și celule pentru fiecare secțiune.
  • UITableViewDelegate — protocol pentru gestionarea selecției, editării și setării înălțimii rândurilor.
  • UITableViewDiffableDataSource — DataSource modern (iOS 13+) cu calcul automat al modificărilor.

Ce este UITableView în iOS?

UITableView — este o clasă din frameworkul UIKit destinată afișării unei liste derulabile de rânduri într-o singură coloană. Fiecare rând este reprezentat de un obiect UITableViewCell. UITableView a apărut în iPhone OS 2 (2008) și de atunci rămâne componenta principală pentru liste în iOS. Arhitectura UITableView este construită pe modelul MVC: datele sunt gestionate de DataSource, aspectul și comportamentul de Delegate, iar vizualizarea de tabelul însuși.

Două stiluri de tabel: .plain (listă continuă cu secțiuni opționale) și .grouped (secțiuni grupate cu colțuri rotunjite și margini). Din iOS 13 a fost adăugat .insetGrouped — stil grouped cu margini de la margini, utilizat în aplicațiile Setări și Sănătate. Alegerea stilului influențează aspectul implicit: tabelele plain fixează headerul secțiunii în partea de sus la derulare (sticky header), grouped — nu.

Self-Sizing Cells

Începând cu iOS 8, UITableView suportă celule cu dimensiune automată (self-sizing cells). Pentru activare setați tableView.estimatedRowHeight (de exemplu, 80) și tableView.rowHeight = UITableView.automaticDimension. Tabelul calculează automat înălțimea rândului pe baza constrângerilor Auto Layout din interiorul celulei. Self-sizing reduce performanța pentru celule complexe — utilizați înălțime fixă (rowHeight) pentru tabele cu 500+ elemente.

Stiluri UITableViewCell: basic, subtitle, value1, value2

UITableViewCell oferă patru stiluri încorporate care acoperă 80% din scenarii fără a fi nevoie să creați o celulă personalizată. Fiecare stil constă dintr-o combinație de textLabel, detailTextLabel și imageView. Alegerea stilului se setează la inițializarea celulei prin init(style:reuseIdentifier:).

StiltextLabeldetailTextLabelimageViewExemplu
.default (.basic)Stânga, îngroșatNuOpționalMeniu, listă de elemente
.subtitleStânga, îngroșatSub textLabel, griOpționalContacte, liste de redare
.value1Stânga, îngroșatDreapta, griNuSetări cu valori
.value2Dreapta, albastruStânga, griNuAgendă telefonică (stil iOS 6)
swift
// Crearea unui tabel cu celule personalizate
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+) — modalitatea modernă de configurare a celulelor prin contentConfiguration în loc de accesarea directă a textLabel/detailTextLabel. Utilizați cell.defaultContentConfiguration() sau creați o UIContentConfiguration personalizată. Avantaje: suport pentru tip dinamic, temă întunecată și VoiceOut din cutie. Apple (WWDC 2024) recomandă contentConfiguration pentru toate UITableView noi.

DataSource și Delegate: metode obligatorii

UITableViewDataSource — protocol care furnizează date pentru tabel. Metode obligatorii: tableView(_:numberOfRowsInSection:) și tableView(_:cellForRowAt:). Fără ele tabelul nu poate afișa niciun rând. UITableViewDelegate — protocol pentru gestionarea aspectului și comportamentului: înălțimea rândurilor, selecție, editare, contextual actions.

swift
// Exemplu cu mai multe secțiuni și editare
extension ContactListController: UITableViewDelegate {

    // Înălțime personalizată pentru fiecare rând
    func tableView(_ tableView: UITableView,
                         heightForRowAt indexPath: IndexPath) -> CGFloat {
        80
    }

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

    // Glisare pentru ștergere
    func tableView(_ tableView: UITableView,
                         trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
        -> UISwipeActionsConfiguration? {
        let deleteAction = UIContextualAction(style: .destructive, title: "Șterge") {
            [weak self] _, _, completion in
            self?.contacts.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
            completion(true)
        }
        return UISwipeActionsConfiguration(actions: [deleteAction])
    }
}

Performanța Delegate: metodele de dimensiune (heightForRowAt) sunt apelate pentru fiecare rând vizibil la fiecare derulare. Pentru înălțime uniformă setați rowHeight direct — aceasta este de 10 ori mai rapidă decât heightForRowAt. Pentru celule self-sizing setați estimatedRowHeight și automaticDimension — tabelul va apela heightForRowAt doar pentru rândurile vizibile, iar pentru restul va utiliza estimatedRowHeight.

Reutilizarea celulelor: coada de reuse și register

Coada de reuse — pool de celule UITableView reutilizabile. Când o celulă iese din ecran, nu este ștearsă, ci plasată în coadă. Celulele noi nu sunt create de la zero — sistemul apelează dequeueReusableCell(withIdentifier:for:), care returnează o celulă din coadă sau creează una nouă dacă coada este goală. Acesta este mecanismul cheie de performanță al UITableView.

swift
// Celulă personalizată cu cache
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)

        // Configurarea constrângerilor Auto Layout
        avatarView.translatesAutoresizingMaskIntoConstraints = false
        // ... constrângeri ...
    }

    func configure(with contact: Contact) {
        nameLabel.text = contact.name
        subtitleLabel.text = contact.phone
        // Încărcarea asincronă a avatarului
    }

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

// Înregistrare și utilizare
tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")

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

prepareForReuse() — metodă apelată înainte de plasarea celulei în coada de reuse. Resetați întreaga stare a celulei: text, imagini, animații, încărcări. Fără prepareForReuse, o celulă reutilizată poate afișa date vechi: imaginea anterioară va fi vizibilă pentru o fracțiune de secundă până când se încarcă una nouă. Apple (WWDC 2023) recomandă curățarea operațiilor asincrone prin URLSessionTask.cancel() în prepareForReuse.

Secțiuni, header și footer: gruparea datelor

Secțiunile în UITableView grupează rândurile logic conexe. Fiecare secțiune poate avea header (antet) și footer (subsol). Numărul de secțiuni este determinat de metoda numberOfSections(in:) (implicit 1). Pentru stilurile grouped și insetGrouped, secțiunile sunt vizual separate prin margini și colțuri rotunjite.

Metoda DataSourceDescriereValoare returnată
numberOfSectionsNumărul de secțiuni (implicit 1)Int
numberOfRowsInSectionNumărul de rânduri în secțiuneInt
titleForHeaderInSectionTextul antetului secțiuniiString?
titleForFooterInSectionTextul subsolului secțiuniiString?
viewForHeaderInSectionVizualizare personalizată pentru headerUIView?
heightForHeaderInSectionÎnălțimea headeruluiCGFloat

Indexare: pentru navigare rapidă între secțiuni adăugați un index — un șir de caractere afișat în dreapta. Implementați sectionIndexTitles(for:) — returnează un array cu primele litere ale fiecărei secțiuni. Pentru 26+ secțiuni acest lucru îmbunătățește critic UX. În iOS 15+, tabelele suportă sectionHeaderTopPadding — marginea între bara de stare și primul header care poate fi resetată la 0 pentru un aspect compact.

Performanța UITableView: cele mai bune practici

Performanța UITableView este critică pentru aplicațiile cu liste lungi. Problemele principale: derulare lentă (jank) din cauza operațiilor grele în cellForRowAt, apeluri frecvente ale heightForRowAt, lipsa prefetching. Apple (WWDC 2025) evidențiază cinci practici cheie pentru tabele cu 1000+ elemente.

  • Flat-height — utilizați rowHeight constantă în loc de heightForRowAt pentru toate celulele. Diferența: 60 FPS vs 30 FPS pentru un tabel de 500 de rânduri.
  • Prefetching — implementați UITableViewDataSourcePrefetching pentru încărcarea asincronă a datelor (imagini, API) înainte ca celula să apară pe ecran.
  • Mai puține subview-uri — fiecare subview în contentView crește timpul de randare. Utilizați draw() pentru grafică simplă (separatoare, pictograme) în loc de UIImageView.
  • Operații grele în fundal — formatarea datelor, localizarea, calculele le efectuați în model, nu în cellForRowAt. Utilizați NSCache pentru rezultate.
  • DiffableDataSource — înlocuiți reloadData() cu apply(snapshot) — animația are loc gratuit, tabelul nu se reîncarcă complet.
swift
// Optimizare cu 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)
        }
    }
}

// Utilizarea DiffableDataSource pentru actualizări fluide
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)
    }
}

Rezultat practic: aplicarea acestor optimizări crește FPS-ul de derulare de la 30 la 60 pe dispozitive iPhone 12 și mai vechi (conform testelor Apple, 2025). În proiectele IT Sectr, folosim DiffableDataSource pentru toate tabelele noi și adăugăm prefetching pentru tabelele cu imagini (avataruri, previzualizări, pictograme de aplicații).

Întrebări frecvente

UITableView nu se derulează — ce fac?

Verificați: (1) contentSize > frame.size — tabelul trebuie să aibă mai mult conținut decât înălțimea sa; (2) tabelul nu se află în interiorul unui alt UIScrollView care interceptează gesturile; (3) isScrollEnabled = true; (4) numberOfRowsInSection returnează numărul corect. Problema tipică — tabel cu heightForRowAt = UITableView.automaticDimension fără estimatedRowHeight, ceea ce duce la calculul infinit al înălțimii.

Cum să actualizez UITableView fără pâlpâire?

Utilizați performBatchUpdates sau DiffableDataSource în loc de reloadData(). reloadData() provoacă reîncărcarea completă fără animație, creând pâlpâire vizuală. Pentru actualizare punctuală utilizați: reloadRows(at:with:), insertRows, deleteRows. Toate modificările din interiorul performBatchUpdates sunt animate simultan. DiffableDataSource face acest lucru automat fără apeluri manuale.

Care este diferența dintre UITableView și UICollectionView?

UITableView — listă verticală cu o coloană cu stiluri de celule predefinite și editare încorporată. UICollectionView — aspect flexibil (grile, listă, carusel, cascadă) cu amplasare arbitrară a elementelor. Alegeți UITableView pentru liste simple (setări, contacte, mesaje). Alegeți UICollectionView pentru galerii, cataloage, panouri și orice aspect neliniar.

Cum să elimini rândurile goale de sub conținut în UITableView?

Setați tableView.tableFooterView = UIView(frame: .zero). În mod implicit, UITableView afișează rânduri goale (separatoare) sub ultimul element până la marginea de jos. Setarea unui UIView gol în tableFooterView le elimină. Pentru stilul grouped acest lucru nu este necesar — tabelul afișează doar rândurile reale cu colțuri rotunjite ale ultimei secțiuni.

Cum să faci UITableView cu înălțime constantă?

Setați tableView.rowHeight = 80 și tableView.estimatedRowHeight = 0 sau omiteți estimatedRowHeight. Cu înălțime fixă, UITableView nu apelează heightForRowAt, ceea ce accelerează randarea de 5–10 ori pentru liste cu 100+ elemente. Pentru celule cu înălțime personalizată (conținut diferit) utilizați self-sizing: estimatedRowHeight + UITableView.automaticDimension.

Rezumat

  • UITableView — componenta principală UIKit pentru liste verticale cu arhitectură DataSource și Delegate.
  • UITableViewCell — 4 stiluri încorporate (.default, .subtitle, .value1, .value2) și personalizate prin contentConfiguration.
  • Coada de reuse — pool de celule reutilizabile; register + dequeueReusableCell + prepareForReuse obligatorii.
  • DiffableDataSource (iOS 13+) — DataSource type-safe cu calcul automat al modificărilor și animație.
  • Performanță: rowHeight fixă, prefetching, mai puține subview-uri, operații grele în fundal.
  • 85% din aplicațiile iOS din top-100 App Store utilizează UITableView pentru afișarea conținutului.
  • Pentru grile, caruseluri și aspecte complexe utilizați UICollectionView în loc de UITableView.

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și