UITableView: basi, elenchi e celle nelle app iOS

Autore: IT Sectr Pubblicato: 2026-02-23 Tempo di lettura: 9 min

UITableView — il componente principale di UIKit per visualizzare elenchi verticali in iOS. Ogni riga della tabella è una cella UITableViewCell con stili predefiniti. Spieghiamo le basi di UITableView: DataSource, delegato, riutilizzo delle celle e prestazioni delle tabelle nelle applicazioni iOS. Secondo Apple (Human Interface Guidelines, 2026), UITableView rimane il componente UI più utilizzato in iOS — 85% delle applicazioni nella top 100 dell'App Store contengono almeno una tabella. Per layout complessi, leggete l'articolo su UICollectionView.

Punti chiave

  • UITableView — un componente UIKit per elenchi verticali, disponibile da iOS 2.0 (iPhone OS 2).
  • UITableViewCell — una cella di tabella con quattro stili integrati e opzioni di personalizzazione.
  • UITableViewDataSource — un protocollo che definisce il numero di righe e celle per ogni sezione.
  • UITableViewDelegate — un protocollo per gestire selezione, modifica e configurazione dell'altezza delle righe.
  • UITableViewDiffableDataSource — un DataSource moderno (iOS 13+) con calcolo automatico delle modifiche.

Cos'è UITableView in iOS?

UITableView è una classe del framework UIKit progettata per visualizzare un elenco scorrevole di righe in una singola colonna. Ogni riga è rappresentata da un oggetto UITableViewCell. UITableView è apparsa per la prima volta in iPhone OS 2 (2008) e da allora rimane il componente principale per gli elenchi in iOS. L'architettura di UITableView è basata sul pattern MVC: i dati sono gestiti dal DataSource, l'aspetto e il comportamento dal Delegato, e la presentazione dalla tabella stessa.

Due stili di tabella: .plain (elenco continuo con sezioni opzionali) e .grouped (sezioni raggruppate con angoli arrotondati e margini). iOS 13 ha aggiunto .insetGrouped — uno stile raggruppato con margini ai bordi, utilizzato nelle app Impostazioni e Salute. La scelta dello stile influisce sull'aspetto predefinito: le tabelle plain fissano le intestazioni delle sezioni in alto durante lo scorrimento (intestazione appiccicosa), quelle grouped no.

Celle a dimensionamento automatico

A partire da iOS 8, UITableView supporta le celle a dimensionamento automatico (self-sizing cells). Per attivare, impostate tableView.estimatedRowHeight (ad esempio, 80) e tableView.rowHeight = UITableView.automaticDimension. La tabella calcola automaticamente l'altezza della riga in base ai vincoli di Auto Layout all'interno della cella. L'auto-dimensionamento riduce le prestazioni per celle complesse — utilizzate un'altezza fissa (rowHeight) per tabelle con più di 500 elementi.

Stili UITableViewCell: Basic, Subtitle, Value1, Value2

UITableViewCell fornisce quattro stili integrati che coprono l'80% degli scenari senza bisogno di creare una cella personalizzata. Ogni stile consiste in una combinazione di textLabel, detailTextLabel e imageView. Lo stile viene scelto durante l'inizializzazione della cella tramite init(style:reuseIdentifier:).

StiletextLabeldetailTextLabelimageViewEsempio
.default (.basic)Sinistra, grassettoNessunoOpzionaleMenu, elenco di elementi
.subtitleSinistra, grassettoSotto textLabel, grigioOpzionaleContatti, playlist
.value1Sinistra, grassettoDestra, grigioNessunoImpostazioni con valori
.value2Destra, bluSinistra, grigioNessunoRubrica telefonica (stile iOS 6)
swift
// Creazione di una tabella con celle personalizzate
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+) — un modo moderno per configurare le celle tramite contentConfiguration invece di accedere direttamente a textLabel/detailTextLabel. Utilizzate cell.defaultContentConfiguration() o create una UIContentConfiguration personalizzata. Vantaggi: supporto integrato per Dynamic Type, modalità scura e VoiceOver. Apple (WWDC 2024) raccomanda contentConfiguration per tutte le nuove UITableView.

DataSource e Delegate: metodi obbligatori

UITableViewDataSource — un protocollo che fornisce i dati alla tabella. Metodi obbligatori: tableView(_:numberOfRowsInSection:) e tableView(_:cellForRowAt:). Senza di essi, la tabella non può visualizzare nessuna riga. UITableViewDelegate — un protocollo per gestire aspetto e comportamento: altezza delle righe, selezione, modifica, azioni contestuali.

swift
// Esempio con più sezioni e modifica
extension ContactListController: UITableViewDelegate {

    // Altezza personalizzata per ogni riga
    func tableView(_ tableView: UITableView,
                         heightForRowAt indexPath: IndexPath) -> CGFloat {
        80
    }

    // Gestione della selezione
    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: "Elimina") {
            [weak self] _, _, completion in
            self?.contacts.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
            completion(true)
        }
        return UISwipeActionsConfiguration(actions: [deleteAction])
    }
}

Prestazioni del Delegato: i metodi di dimensione (heightForRowAt) vengono chiamati per ogni riga visibile ad ogni scorrimento. Per un'altezza uniforme, impostate direttamente rowHeight — è 10 volte più veloce di heightForRowAt. Per le celle a dimensionamento automatico, impostate estimatedRowHeight e automaticDimension — la tabella chiama heightForRowAt solo per le righe visibili e utilizza estimatedRowHeight per le rimanenti.

Riutilizzo delle celle: coda di riutilizzo e registrazione

Coda di riutilizzo — un pool di celle UITableView riutilizzabili. Quando una cella esce dallo schermo, non viene eliminata ma inserita nella coda. Le nuove celle non vengono create da zero — il sistema chiama dequeueReusableCell(withIdentifier:for:), che restituisce una cella dalla coda o ne crea una nuova se la coda è vuota. Questo è il meccanismo chiave delle prestazioni di UITableView.

swift
// Cella personalizzata con 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)

        // Configurazione dei vincoli Auto Layout
        avatarView.translatesAutoresizingMaskIntoConstraints = false
        // ... constraints ...
    }

    func configure(with contact: Contact) {
        nameLabel.text = contact.name
        subtitleLabel.text = contact.phone
        // Caricamento asincrono dell'avatar
    }

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

// Registrazione e utilizzo
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() — un metodo chiamato prima di inserire una cella nella coda di riutilizzo. Reimpostate tutto lo stato della cella: testo, immagini, animazioni, operazioni di caricamento. Senza prepareForReuse, una cella riutilizzata potrebbe mostrare dati vecchi: l'immagine precedente sarà visibile per una frazione di secondo fino al caricamento di quella nuova. Apple (WWDC 2023) raccomanda di annullare le operazioni asincrone tramite URLSessionTask.cancel() in prepareForReuse.

Sezioni, intestazione e piè di pagina: raggruppamento dati

Le sezioni in UITableView raggruppano righe logicamente correlate. Ogni sezione può avere un'intestazione (header) e un piè di pagina (footer). Il numero di sezioni è determinato dal metodo numberOfSections(in:) (il valore predefinito è 1). Per gli stili grouped e insetGrouped, le sezioni sono visivamente separate da margini e angoli arrotondati.

Metodo DataSourceDescrizioneValore restituito
numberOfSectionsNumero di sezioni (predefinito 1)Int
numberOfRowsInSectionNumero di righe in una sezioneInt
titleForHeaderInSectionTesto del titolo dell'intestazione della sezioneString?
titleForFooterInSectionTesto del titolo del piè di sezioneString?
viewForHeaderInSectionVista personalizzata per l'intestazioneUIView?
heightForHeaderInSectionAltezza dell'intestazioneCGFloat

Indicizzazione: per una navigazione rapida tra le sezioni, aggiungete un titolo di indice — un array di stringhe visualizzato sul lato destro. Implementate sectionIndexTitles(for:) — restituisce un array delle prime lettere di ogni sezione. Per 26+ sezioni, questo migliora criticamente l'UX. In iOS 15+, le tabelle supportano sectionHeaderTopPadding — il padding tra la barra di stato e la prima intestazione, che può essere reimpostato a 0 per un layout compatto.

Prestazioni di UITableView: migliori pratiche

Le prestazioni di UITableView sono fondamentali per le applicazioni con elenchi lunghi. Problemi comuni: scorrimento lento (jank) dovuto a operazioni pesanti in cellForRowAt, chiamate frequenti a heightForRowAt, mancanza di prefetching. Apple (WWDC 2025) evidenzia cinque pratiche chiave per tabelle con più di 1000 elementi.

  • Altezza fissa — utilizzate rowHeight costante invece di heightForRowAt per tutte le celle. Differenza: 60 FPS contro 30 FPS per una tabella di 500 righe.
  • Prefetching — implementate UITableViewDataSourcePrefetching per il caricamento asincrono dei dati (immagini, API) prima che la cella appaia sullo schermo.
  • Meno sottoviste — ogni sottovista in contentView aumenta il tempo di rendering. Utilizzate draw() per grafiche semplici (separatori, icone) invece di UIImageView.
  • Operazioni pesanti in background — formattazione delle date, localizzazione, calcoli dovrebbero essere eseguiti nel modello, non in cellForRowAt. Utilizzate NSCache per i risultati.
  • DiffableDataSource — sostituite reloadData() con apply(snapshot) — l'animazione avviene gratuitamente, la tabella non viene ricaricata completamente.
swift
// Ottimizzazione con 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)
        }
    }
}

// Utilizzo di DiffableDataSource per aggiornamenti fluidi
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)
    }
}

Risultato pratico: l'applicazione di queste ottimizzazioni aumenta gli FPS di scorrimento da 30 a 60 su dispositivi iPhone 12 e successivi (secondo i dati dei test Apple, 2025). Nei progetti IT Sectr, utilizziamo DiffableDataSource per tutte le nuove tabelle e aggiungiamo prefetching per tabelle con immagini (avatar, anteprime, icone di app).

Domande frequenti

UITableView non scorre — cosa fare?

Verificate: (1) contentSize > frame.size — la tabella deve avere più contenuto della sua altezza; (2) la tabella non è all'interno di un altro UIScrollView che intercetta i gesti; (3) isScrollEnabled = true; (4) numberOfRowsInSection restituisce il numero corretto. Un problema tipico è una tabella con heightForRowAt = UITableView.automaticDimension senza estimatedRowHeight, che causa un calcolo infinito dell'altezza.

Come aggiornare UITableView senza sfarfallio?

Utilizzate performBatchUpdates o DiffableDataSource invece di reloadData(). reloadData() causa un ricaricamento completo senza animazione, creando sfarfallio visivo. Per aggiornamenti mirati utilizzate: reloadRows(at:with:), insertRows, deleteRows. Tutte le modifiche all'interno di performBatchUpdates vengono animate simultaneamente. DiffableDataSource lo fa automaticamente senza chiamate manuali.

Qual è la differenza tra UITableView e UICollectionView?

UITableView è un elenco verticale a colonna singola con stili di cella predefiniti e modifica integrata. UICollectionView è un layout flessibile (griglia, elenco, carosello, cascata) con posizionamento arbitrario degli elementi. Scegliete UITableView per elenchi semplici (impostazioni, contatti, messaggi). Scegliete UICollectionView per gallerie, cataloghi, bacheche e qualsiasi layout non lineare.

Come rimuovere le righe vuote sotto il contenuto in UITableView?

Impostate tableView.tableFooterView = UIView(frame: .zero). Per impostazione predefinita, UITableView mostra righe vuote (separatori) sotto l'ultimo elemento fino al bordo inferiore. Impostare una UIView vuota come tableFooterView le rimuove. Per lo stile grouped non è necessario — la tabella mostra solo le righe effettive con angoli arrotondati nell'ultima sezione.

Come creare una UITableView con altezza costante?

Impostate tableView.rowHeight = 80 e tableView.estimatedRowHeight = 0 o omettete estimatedRowHeight. Con altezza fissa, UITableView non chiama heightForRowAt, accelerando il rendering di 5–10 volte per elenchi di oltre 100 elementi. Per celle con altezza personalizzata (contenuto diverso), utilizzate l'auto-dimensionamento: estimatedRowHeight + UITableView.automaticDimension.

Riepilogo

  • UITableView — il componente UIKit principale per elenchi verticali con architettura DataSource e Delegate.
  • UITableViewCell — 4 stili integrati (.default, .subtitle, .value1, .value2) e personalizzati tramite contentConfiguration.
  • Coda di riutilizzo — un pool di celle riutilizzabili; register + dequeueReusableCell + prepareForReuse sono obbligatori.
  • DiffableDataSource (iOS 13+) — un DataSource type-safe con calcolo automatico delle modifiche e animazione.
  • Prestazioni: rowHeight fissa, prefetching, meno sottoviste, operazioni pesanti in background.
  • L'85% delle app iOS nella top 100 dell'App Store utilizza UITableView per visualizzare contenuti.
  • Per griglie, caroselli e layout complessi, utilizzate UICollectionView invece di UITableView.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche