UITableView: conceptos básicos, listas y celdas en aplicaciones iOS

Autor: IT Sectr Publicado: 2026-02-23 Tiempo de lectura: 9 min

UITableView — el componente principal de UIKit para mostrar listas verticales en iOS. Cada fila de la tabla es una celda UITableViewCell con estilos predefinidos. Explicamos los fundamentos de UITableView: DataSource, delegado, reutilización de celdas y rendimiento de tablas en aplicaciones iOS. Según Apple (Human Interface Guidelines, 2026), UITableView sigue siendo el componente de UI más utilizado en iOS — 85% de las aplicaciones del top 100 de App Store contienen al menos una tabla. Para diseños complejos, lea el artículo sobre UICollectionView.

Puntos clave

  • UITableView — un componente de UIKit para listas verticales, disponible desde iOS 2.0 (iPhone OS 2).
  • UITableViewCell — una celda de tabla con cuatro estilos integrados y opciones de personalización.
  • UITableViewDataSource — un protocolo que define el número de filas y celdas para cada sección.
  • UITableViewDelegate — un protocolo para manejar selección, edición y configuración de altura de filas.
  • UITableViewDiffableDataSource — un DataSource moderno (iOS 13+) con cálculo automático de cambios.

¿Qué es UITableView en iOS?

UITableView es una clase del framework UIKit diseñada para mostrar una lista desplazable de filas en una sola columna. Cada fila está representada por un objeto UITableViewCell. UITableView apareció por primera vez en iPhone OS 2 (2008) y desde entonces sigue siendo el componente principal para listas en iOS. La arquitectura de UITableView se basa en el patrón MVC: los datos son gestionados por el DataSource, la apariencia y el comportamiento por el Delegate, y la presentación por la propia tabla.

Dos estilos de tabla: .plain (lista continua con secciones opcionales) y .grouped (secciones agrupadas con esquinas redondeadas y márgenes). iOS 13 añadió .insetGrouped — un estilo agrupado con márgenes en los bordes, utilizado en las aplicaciones Ajustes y Salud. La elección del estilo afecta la apariencia predeterminada: las tablas plain fijan los encabezados de sección en la parte superior al hacer scroll (sticky header), las grouped no.

Self-Sizing Cells

A partir de iOS 8, UITableView admite celdas de tamaño automático (self-sizing cells). Para activarlo, configure tableView.estimatedRowHeight (por ejemplo, 80) y tableView.rowHeight = UITableView.automaticDimension. La tabla calcula automáticamente la altura de la fila basándose en las restricciones de Auto Layout dentro de la celda. El auto-dimensionado reduce el rendimiento para celdas complejas — use altura fija (rowHeight) para tablas con más de 500 elementos.

Estilos de UITableViewCell: Basic, Subtitle, Value1, Value2

UITableViewCell proporciona cuatro estilos integrados que cubren el 80% de los escenarios sin necesidad de crear una celda personalizada. Cada estilo consiste en una combinación de textLabel, detailTextLabel e imageView. El estilo se elige al inicializar la celda mediante init(style:reuseIdentifier:).

EstilotextLabeldetailTextLabelimageViewEjemplo
.default (.basic)Izquierda, negritaNingunoOpcionalMenú, lista de elementos
.subtitleIzquierda, negritaDebajo de textLabel, grisOpcionalContactos, listas de reproducción
.value1Izquierda, negritaDerecha, grisNingunoAjustes con valores
.value2Derecha, azulIzquierda, grisNingunoAgenda telefónica (estilo iOS 6)
swift
// Creación de una tabla con celdas 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+) — una forma moderna de configurar celdas mediante contentConfiguration en lugar de acceder directamente a textLabel/detailTextLabel. Use cell.defaultContentConfiguration() o cree una UIContentConfiguration personalizada. Ventajas: soporte integrado para Dynamic Type, modo oscuro y VoiceOver. Apple (WWDC 2024) recomienda contentConfiguration para todos los UITableView nuevos.

DataSource y Delegate: métodos obligatorios

UITableViewDataSource — un protocolo que proporciona datos para la tabla. Métodos obligatorios: tableView(_:numberOfRowsInSection:) y tableView(_:cellForRowAt:). Sin ellos, la tabla no puede mostrar ninguna fila. UITableViewDelegate — un protocolo para gestionar apariencia y comportamiento: altura de filas, selección, edición, acciones contextuales.

swift
// Ejemplo con múltiples secciones y edición
extension ContactListController: UITableViewDelegate {

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

    // Manejo de selección
    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])
    }
}

Rendimiento del Delegate: los métodos de tamaño (heightForRowAt) se llaman para cada fila visible en cada desplazamiento. Para altura uniforme, establezca rowHeight directamente — esto es 10 veces más rápido que heightForRowAt. Para celdas de tamaño automático, configure estimatedRowHeight y automaticDimension — la tabla llama a heightForRowAt solo para las filas visibles y usa estimatedRowHeight para el resto.

Reutilización de celdas: cola de reutilización y registro

Cola de reutilización — un grupo de celdas UITableView reutilizables. Cuando una celda sale de la pantalla, no se elimina sino que se coloca en la cola. Las nuevas celdas no se crean desde cero — el sistema llama a dequeueReusableCell(withIdentifier:for:), que devuelve una celda de la cola o crea una nueva si la cola está vacía. Este es el mecanismo clave de rendimiento de UITableView.

swift
// Celda personalizada con caché
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)

        // Configuración de restricciones de Auto Layout
        avatarView.translatesAutoresizingMaskIntoConstraints = false
        // ... constraints ...
    }

    func configure(with contact: Contact) {
        nameLabel.text = contact.name
        subtitleLabel.text = contact.phone
        // Carga asíncrona de avatar
    }

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

// Registro y uso
tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")

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

prepareForReuse() — un método llamado antes de colocar una celda en la cola de reutilización. Restablezca todo el estado de la celda: texto, imágenes, animaciones, operaciones de carga. Sin prepareForReuse, una celda reutilizada puede mostrar datos antiguos: la imagen anterior será visible durante una fracción de segundo hasta que se cargue la nueva. Apple (WWDC 2023) recomienda cancelar operaciones asíncronas mediante URLSessionTask.cancel() en prepareForReuse.

Secciones, header y footer: agrupación de datos

Secciones en UITableView agrupan filas lógicamente relacionadas. Cada sección puede tener un header (encabezado) y un footer (pie). El número de secciones se determina mediante el método numberOfSections(in:) (el valor predeterminado es 1). Para los estilos grouped e insetGrouped, las secciones están visualmente separadas por márgenes y esquinas redondeadas.

Método de DataSourceDescripciónValor de retorno
numberOfSectionsNúmero de secciones (predeterminado 1)Int
numberOfRowsInSectionNúmero de filas en una secciónInt
titleForHeaderInSectionTexto del título del encabezado de secciónString?
titleForFooterInSectionTexto del título del pie de secciónString?
viewForHeaderInSectionVista personalizada para el encabezadoUIView?
heightForHeaderInSectionAltura del encabezadoCGFloat

Indexación: para una navegación rápida entre secciones, añada un título de índice — un array de cadenas mostradas en el lado derecho. Implemente sectionIndexTitles(for:) — devuelve un array con las primeras letras de cada sección. Para 26+ secciones, esto mejora críticamente la experiencia de usuario. En iOS 15+, las tablas admiten sectionHeaderTopPadding — el padding entre la barra de estado y el primer encabezado, que se puede restablecer a 0 para un diseño compacto.

Rendimiento de UITableView: mejores prácticas

El rendimiento de UITableView es crítico para aplicaciones con listas largas. Problemas comunes: desplazamiento lento (jank) debido a operaciones pesadas en cellForRowAt, llamadas frecuentes a heightForRowAt, falta de prefetching. Apple (WWDC 2025) destaca cinco prácticas clave para tablas con más de 1000 elementos.

  • Altura fija — use rowHeight constante en lugar de heightForRowAt para todas las celdas. Diferencia: 60 FPS vs 30 FPS para una tabla de 500 filas.
  • Prefetching — implemente UITableViewDataSourcePrefetching para carga asíncrona de datos (imágenes, API) antes de que la celda aparezca en pantalla.
  • Menos subvistas — cada subvista en contentView aumenta el tiempo de renderizado. Use draw() para gráficos simples (separadores, iconos) en lugar de UIImageView.
  • Operaciones pesadas en segundo plano — el formato de fechas, la localización y los cálculos deben realizarse en el modelo, no en cellForRowAt. Use NSCache para los resultados.
  • DiffableDataSource — reemplace reloadData() con apply(snapshot) — la animación ocurre sin costo, la tabla no se recarga por completo.
swift
// Optimización 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)
        }
    }
}

// Uso de DiffableDataSource para actualizaciones fluidas
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áctico: la aplicación de estas optimizaciones aumenta los FPS de desplazamiento de 30 a 60 en dispositivos iPhone 12 y posteriores (según datos de pruebas de Apple, 2025). En los proyectos de IT Sectr, usamos DiffableDataSource para todas las tablas nuevas y añadimos prefetching para tablas con imágenes (avatares, vistas previas, iconos de aplicaciones).

Preguntas frecuentes

UITableView no se desplaza — ¿qué hacer?

Verifique: (1) contentSize > frame.size — la tabla debe tener más contenido que su altura; (2) la tabla no está dentro de otro UIScrollView que intercepte gestos; (3) isScrollEnabled = true; (4) numberOfRowsInSection devuelve el número correcto. Un problema típico es una tabla con heightForRowAt = UITableView.automaticDimension sin estimatedRowHeight, lo que provoca un cálculo infinito de altura.

¿Cómo actualizar UITableView sin parpadeo?

Use performBatchUpdates o DiffableDataSource en lugar de reloadData(). reloadData() provoca una recarga completa sin animación, creando un parpadeo visual. Para actualizaciones puntuales use: reloadRows(at:with:), insertRows, deleteRows. Todos los cambios dentro de performBatchUpdates se animan simultáneamente. DiffableDataSource hace esto automáticamente sin llamadas manuales.

¿Cuál es la diferencia entre UITableView y UICollectionView?

UITableView es una lista vertical de una sola columna con estilos de celda predefinidos y edición integrada. UICollectionView es un diseño flexible (cuadrícula, lista, carrusel, cascada) con posicionamiento arbitrario de elementos. Elija UITableView para listas simples (ajustes, contactos, mensajes). Elija UICollectionView para galerías, catálogos, tableros y cualquier diseño no lineal.

¿Cómo eliminar filas vacías debajo del contenido en UITableView?

Establezca tableView.tableFooterView = UIView(frame: .zero). Por defecto, UITableView muestra filas vacías (separadores) debajo del último elemento hasta el borde inferior. Establecer un UIView vacío como tableFooterView las elimina. Para el estilo grouped esto no es necesario — la tabla solo muestra las filas reales con esquinas redondeadas en la última sección.

¿Cómo hacer UITableView con altura constante?

Establezca tableView.rowHeight = 80 y tableView.estimatedRowHeight = 0 u omita estimatedRowHeight. Con altura fija, UITableView no llama a heightForRowAt, lo que acelera el renderizado entre 5 y 10 veces para listas de más de 100 elementos. Para celdas con altura personalizada (contenido diferente), use auto-dimensionado: estimatedRowHeight + UITableView.automaticDimension.

Resumen

  • UITableView — el componente principal de UIKit para listas verticales con arquitectura DataSource y Delegate.
  • UITableViewCell — 4 estilos integrados (.default, .subtitle, .value1, .value2) y personalizados mediante contentConfiguration.
  • Cola de reutilización — un grupo de celdas reutilizables; register + dequeueReusableCell + prepareForReuse son obligatorios.
  • DiffableDataSource (iOS 13+) — un DataSource type-safe con cálculo automático de cambios y animación.
  • Rendimiento: rowHeight fija, prefetching, menos subvistas, operaciones pesadas en segundo plano.
  • El 85% de las aplicaciones iOS del top 100 de App Store usan UITableView para mostrar contenido.
  • Para cuadrículas, carruseles y diseños complejos, use UICollectionView en lugar de UITableView.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también