UITableView: conceitos básicos, listas e células em aplicativos iOS

Autor: IT Sectr Publicado: 2026-02-23 Tempo de leitura: 9 min

UITableView — o principal componente do UIKit para exibir listas verticais em iOS. Cada linha da tabela é uma célula UITableViewCell com estilos predefinidos. Explicamos os fundamentos da UITableView: DataSource, delegate, reutilização de células e desempenho de tabelas em aplicações iOS. De acordo com a Apple (Human Interface Guidelines, 2026), a UITableView continua a ser o componente de UI mais usado em iOS — 85% das aplicações no top 100 da App Store contêm pelo menos uma tabela. Para layouts complexos, leia o artigo sobre UICollectionView.

Pontos principais

  • UITableView — um componente UIKit para listas verticais, disponível desde iOS 2.0 (iPhone OS 2).
  • UITableViewCell — uma célula de tabela com quatro estilos incorporados e opções de personalização.
  • UITableViewDataSource — um protocolo que define o número de linhas e células para cada secção.
  • UITableViewDelegate — um protocolo para lidar com seleção, edição e configuração de altura das linhas.
  • UITableViewDiffableDataSource — um DataSource moderno (iOS 13+) com cálculo automático de alterações.

O que é UITableView em iOS?

UITableView é uma classe do framework UIKit projetada para exibir uma lista rolável de linhas numa única coluna. Cada linha é representada por um objeto UITableViewCell. A UITableView apareceu pela primeira vez no iPhone OS 2 (2008) e desde então continua a ser o principal componente para listas em iOS. A arquitetura da UITableView é baseada no padrão MVC: os dados são geridos pelo DataSource, a aparência e o comportamento pelo Delegate, e a apresentação pela própria tabela.

Dois estilos de tabela: .plain (lista contínua com secções opcionais) e .grouped (secções agrupadas com cantos arredondados e margens). O iOS 13 adicionou .insetGrouped — um estilo agrupado com margens nas bordas, usado nas aplicações Definições e Saúde. A escolha do estilo afeta a aparência padrão: as tabelas plain fixam os cabeçalhos das secções no topo ao rolar (sticky header), as grouped não.

Células de tamanho automático

A partir do iOS 8, a UITableView suporta células de tamanho automático (self-sizing cells). Para ativar, defina tableView.estimatedRowHeight (por exemplo, 80) e tableView.rowHeight = UITableView.automaticDimension. A tabela calcula automaticamente a altura da linha com base nas restrições de Auto Layout dentro da célula. O auto-dimensionamento reduz o desempenho para células complexas — use altura fixa (rowHeight) para tabelas com mais de 500 elementos.

Estilos de UITableViewCell: Basic, Subtitle, Value1, Value2

UITableViewCell fornece quatro estilos incorporados que cobrem 80% dos cenários sem necessidade de criar uma célula personalizada. Cada estilo consiste numa combinação de textLabel, detailTextLabel e imageView. O estilo é escolhido ao inicializar a célula através de init(style:reuseIdentifier:).

EstilotextLabeldetailTextLabelimageViewExemplo
.default (.basic)Esquerda, negritoNenhumOpcionalMenu, lista de itens
.subtitleEsquerda, negritoAbaixo de textLabel, cinzentoOpcionalContactos, listas de reprodução
.value1Esquerda, negritoDireita, cinzentoNenhumDefinições com valores
.value2Direita, azulEsquerda, cinzentoNenhumLista telefónica (estilo iOS 6)
swift
// Criação de uma tabela com células personalizadas
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+) — uma forma moderna de configurar células através de contentConfiguration em vez de aceder diretamente a textLabel/detailTextLabel. Use cell.defaultContentConfiguration() ou crie uma UIContentConfiguration personalizada. Vantagens: suporte incorporado para Dynamic Type, modo escuro e VoiceOver. A Apple (WWDC 2024) recomenda contentConfiguration para todas as novas UITableView.

DataSource e Delegate: métodos obrigatórios

UITableViewDataSource — um protocolo que fornece dados para a tabela. Métodos obrigatórios: tableView(_:numberOfRowsInSection:) e tableView(_:cellForRowAt:). Sem eles, a tabela não pode exibir nenhuma linha. UITableViewDelegate — um protocolo para gerir aparência e comportamento: altura das linhas, seleção, edição, ações contextuais.

swift
// Exemplo com várias secções e edição
extension ContactListController: UITableViewDelegate {

    // Altura personalizada para cada linha
    func tableView(_ tableView: UITableView,
                         heightForRowAt indexPath: IndexPath) -> CGFloat {
        80
    }

    // Manipulação de seleção
    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: "Eliminar") {
            [weak self] _, _, completion in
            self?.contacts.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
            completion(true)
        }
        return UISwipeActionsConfiguration(actions: [deleteAction])
    }
}

Desempenho do Delegate: os métodos de tamanho (heightForRowAt) são chamados para cada linha visível em cada rolagem. Para altura uniforme, defina rowHeight diretamente — isto é 10 vezes mais rápido que heightForRowAt. Para células de tamanho automático, defina estimatedRowHeight e automaticDimension — a tabela chama heightForRowAt apenas para as linhas visíveis e usa estimatedRowHeight para as restantes.

Reutilização de células: fila de reutilização e registo

Fila de reutilização — um conjunto de células UITableView reutilizáveis. Quando uma célula sai do ecrã, não é eliminada mas sim colocada na fila. Novas células não são criadas do zero — o sistema chama dequeueReusableCell(withIdentifier:for:), que devolve uma célula da fila ou cria uma nova se a fila estiver vazia. Este é o mecanismo chave de desempenho da UITableView.

swift
// Célula personalizada com 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)

        // Configuração de restrições de Auto Layout
        avatarView.translatesAutoresizingMaskIntoConstraints = false
        // ... constraints ...
    }

    func configure(with contact: Contact) {
        nameLabel.text = contact.name
        subtitleLabel.text = contact.phone
        // Carregamento assíncrono de avatar
    }

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

// Registo e utilização
tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")

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

prepareForReuse() — um método chamado antes de colocar uma célula na fila de reutilização. Redefina todo o estado da célula: texto, imagens, animações, operações de carregamento. Sem prepareForReuse, uma célula reutilizada pode mostrar dados antigos: a imagem anterior ficará visível por uma fração de segundo até que a nova seja carregada. A Apple (WWDC 2023) recomenda cancelar operações assíncronas através de URLSessionTask.cancel() em prepareForReuse.

Secções, cabeçalho e rodapé: agrupamento de dados

Secções na UITableView agrupam linhas logicamente relacionadas. Cada secção pode ter um cabeçalho (header) e um rodapé (footer). O número de secções é determinado pelo método numberOfSections(in:) (o padrão é 1). Para os estilos grouped e insetGrouped, as secções são visualmente separadas por margens e cantos arredondados.

Método do DataSourceDescriçãoValor de retorno
numberOfSectionsNúmero de secções (padrão 1)Int
numberOfRowsInSectionNúmero de linhas numa secçãoInt
titleForHeaderInSectionTexto do título do cabeçalho da secçãoString?
titleForFooterInSectionTexto do título do rodapé da secçãoString?
viewForHeaderInSectionVista personalizada para o cabeçalhoUIView?
heightForHeaderInSectionAltura do cabeçalhoCGFloat

Indexação: para navegação rápida entre secções, adicione um título de índice — uma matriz de strings exibidas no lado direito. Implemente sectionIndexTitles(for:) — devolve uma matriz das primeiras letras de cada secção. Para 26+ secções, isto melhora criticamente a experiência do utilizador. No iOS 15+, as tabelas suportam sectionHeaderTopPadding — o padding entre a barra de estado e o primeiro cabeçalho, que pode ser redefinido para 0 para um layout compacto.

Desempenho da UITableView: melhores práticas

O desempenho da UITableView é crítico para aplicações com listas longas. Problemas comuns: rolagem lenta (jank) devido a operações pesadas em cellForRowAt, chamadas frequentes a heightForRowAt, falta de prefetching. A Apple (WWDC 2025) destaca cinco práticas chave para tabelas com mais de 1000 elementos.

  • Altura fixa — use rowHeight constante em vez de heightForRowAt para todas as células. Diferença: 60 FPS vs 30 FPS para uma tabela de 500 linhas.
  • Prefetching — implemente UITableViewDataSourcePrefetching para carregamento assíncrono de dados (imagens, API) antes de a célula aparecer no ecrã.
  • Menos subvistas — cada subvista em contentView aumenta o tempo de renderização. Use draw() para gráficos simples (separadores, ícones) em vez de UIImageView.
  • Operações pesadas em segundo plano — formatação de datas, localização, cálculos devem ser feitos no modelo, não em cellForRowAt. Use NSCache para os resultados.
  • DiffableDataSource — substitua reloadData() por apply(snapshot) — a animação ocorre sem custo, a tabela não é recarregada completamente.
swift
// Otimização com 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)
        }
    }
}

// Uso de DiffableDataSource para atualizações suaves
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)
    }
}

Resultado prático: a aplicação destas otimizações aumenta os FPS de rolagem de 30 para 60 em dispositivos iPhone 12 e posteriores (de acordo com dados de testes da Apple, 2025). Nos projetos da IT Sectr, usamos DiffableDataSource para todas as novas tabelas e adicionamos prefetching para tabelas com imagens (avatars, pré-visualizações, ícones de aplicações).

Perguntas frequentes

A UITableView não está a rolar — o que fazer?

Verifique: (1) contentSize > frame.size — a tabela deve ter mais conteúdo que a sua altura; (2) a tabela não está dentro de outra UIScrollView que interceta gestos; (3) isScrollEnabled = true; (4) numberOfRowsInSection devolve o número correto. Um problema típico é uma tabela com heightForRowAt = UITableView.automaticDimension sem estimatedRowHeight, o que causa cálculo infinito de altura.

Como atualizar a UITableView sem cintilação?

Use performBatchUpdates ou DiffableDataSource em vez de reloadData(). reloadData() causa uma recarga completa sem animação, criando cintilação visual. Para atualizações direcionadas use: reloadRows(at:with:), insertRows, deleteRows. Todas as alterações dentro de performBatchUpdates são animadas simultaneamente. O DiffableDataSource faz isto automaticamente sem chamadas manuais.

Qual é a diferença entre UITableView e UICollectionView?

UITableView é uma lista vertical de coluna única com estilos de célula predefinidos e edição incorporada. UICollectionView é um layout flexível (grelha, lista, carrossel, cascata) com posicionamento arbitrário de elementos. Escolha UITableView para listas simples (definições, contactos, mensagens). Escolha UICollectionView para galerias, catálogos, quadros e qualquer layout não linear.

Como remover linhas vazias abaixo do conteúdo na UITableView?

Defina tableView.tableFooterView = UIView(frame: .zero). Por padrão, a UITableView exibe linhas vazias (separadores) abaixo do último item até à borda inferior. Definir uma UIView vazia como tableFooterView remove-as. Para o estilo grouped isto não é necessário — a tabela exibe apenas as linhas reais com cantos arredondados na última secção.

Como fazer UITableView com altura constante?

Defina tableView.rowHeight = 80 e tableView.estimatedRowHeight = 0 ou omita estimatedRowHeight. Com altura fixa, a UITableView não chama heightForRowAt, o que acelera a renderização em 5–10 vezes para listas de mais de 100 itens. Para células com altura personalizada (conteúdo diferente), use auto-dimensionamento: estimatedRowHeight + UITableView.automaticDimension.

Resumo

  • UITableView — o principal componente UIKit para listas verticais com arquitetura DataSource e Delegate.
  • UITableViewCell — 4 estilos incorporados (.default, .subtitle, .value1, .value2) e personalizados via contentConfiguration.
  • Fila de reutilização — um conjunto de células reutilizáveis; register + dequeueReusableCell + prepareForReuse são obrigatórios.
  • DiffableDataSource (iOS 13+) — um DataSource type-safe com cálculo automático de alterações e animação.
  • Desempenho: rowHeight fixa, prefetching, menos subvistas, operações pesadas em segundo plano.
  • 85% das aplicações iOS no top 100 da App Store usam UITableView para exibir conteúdo.
  • Para grelhas, carrosséis e layouts complexos, use UICollectionView em vez de UITableView.

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também