UITableView — le composant principal d'UIKit pour afficher des listes verticales en iOS. Chaque ligne du tableau est une cellule UITableViewCell avec des styles prédéfinis. Nous expliquons les bases d'UITableView : DataSource, délégué, réutilisation des cellules et performances des tableaux dans les applications iOS. Selon Apple (Human Interface Guidelines, 2026), UITableView reste le composant d'interface utilisateur le plus utilisé en iOS — 85% des applications du top 100 de l'App Store contiennent au moins un tableau. Pour les mises en page complexes, lisez l'article sur UICollectionView.
Points clés
UITableView est une classe du framework UIKit conçue pour afficher une liste défilable de lignes dans une seule colonne. Chaque ligne est représentée par un objet UITableViewCell. UITableView est apparue pour la première fois dans iPhone OS 2 (2008) et est depuis restée le composant principal pour les listes en iOS. L'architecture d'UITableView est basée sur le modèle MVC : les données sont gérées par la DataSource, l'apparence et le comportement par le Délégué, et la présentation par le tableau lui-même.
Deux styles de tableau : .plain (liste continue avec sections optionnelles) et .grouped (sections groupées avec coins arrondis et marges). iOS 13 a ajouté .insetGrouped — un style groupé avec des marges sur les bords, utilisé dans les applications Réglages et Santé. Le choix du style affecte l'apparence par défaut : les tableaux plain fixent les en-têtes de section en haut lors du défilement (en-tête collant), les tableaux grouped non.
À partir d'iOS 8, UITableView prend en charge les cellules à taille automatique (self-sizing cells). Pour activer, définissez tableView.estimatedRowHeight (par exemple, 80) et tableView.rowHeight = UITableView.automaticDimension. Le tableau calcule automatiquement la hauteur de ligne en fonction des contraintes Auto Layout dans la cellule. L'auto-dimensionnement réduit les performances pour les cellules complexes — utilisez une hauteur fixe (rowHeight) pour les tableaux de plus de 500 éléments.
UITableViewCell fournit quatre styles intégrés qui couvrent 80% des scénarios sans nécessiter la création d'une cellule personnalisée. Chaque style se compose d'une combinaison de textLabel, detailTextLabel et imageView. Le style est choisi lors de l'initialisation de la cellule via init(style:reuseIdentifier:).
| Style | textLabel | detailTextLabel | imageView | Exemple |
|---|---|---|---|---|
| .default (.basic) | Gauche, gras | Aucun | Optionnel | Menu, liste d'éléments |
| .subtitle | Gauche, gras | Sous textLabel, gris | Optionnel | Contacts, listes de lecture |
| .value1 | Gauche, gras | Droite, gris | Aucun | Réglages avec valeurs |
| .value2 | Droite, bleu | Gauche, gris | Aucun | Annuaire téléphonique (style iOS 6) |
// Création d'un tableau avec des cellules personnalisées
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+) — une façon moderne de configurer les cellules via contentConfiguration au lieu d'accéder directement à textLabel/detailTextLabel. Utilisez cell.defaultContentConfiguration() ou créez une UIContentConfiguration personnalisée. Avantages : prise en charge intégrée du Dynamic Type, du mode sombre et de VoiceOver. Apple (WWDC 2024) recommande contentConfiguration pour toutes les nouvelles UITableView.
UITableViewDataSource — un protocole qui fournit les données au tableau. Méthodes obligatoires : tableView(_:numberOfRowsInSection:) et tableView(_:cellForRowAt:). Sans elles, le tableau ne peut afficher aucune ligne. UITableViewDelegate — un protocole pour gérer l'apparence et le comportement : hauteur des lignes, sélection, édition, actions contextuelles.
// Exemple avec plusieurs sections et édition
extension ContactListController: UITableViewDelegate {
// Hauteur personnalisée pour chaque ligne
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
80
}
// Gestion de la sélection
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: "Supprimer") {
[weak self] _, _, completion in
self?.contacts.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
completion(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction])
}
}
Performances du Délégué : les méthodes de taille (heightForRowAt) sont appelées pour chaque ligne visible à chaque défilement. Pour une hauteur uniforme, définissez rowHeight directement — c'est 10 fois plus rapide que heightForRowAt. Pour les cellules à taille automatique, définissez estimatedRowHeight et automaticDimension — le tableau appelle heightForRowAt uniquement pour les lignes visibles et utilise estimatedRowHeight pour le reste.
File d'attente de réutilisation — un pool de cellules UITableView réutilisables. Lorsqu'une cellule sort de l'écran, elle n'est pas supprimée mais placée dans la file d'attente. Les nouvelles cellules ne sont pas créées de zéro — le système appelle dequeueReusableCell(withIdentifier:for:), qui renvoie une cellule de la file ou en crée une nouvelle si la file est vide. C'est le mécanisme de performance clé d'UITableView.
// Cellule personnalisée avec mise en 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)
// Configuration des contraintes Auto Layout
avatarView.translatesAutoresizingMaskIntoConstraints = false
// ... constraints ...
}
func configure(with contact: Contact) {
nameLabel.text = contact.name
subtitleLabel.text = contact.phone
// Chargement asynchrone de l'avatar
}
override func prepareForReuse() {
super.prepareForReuse()
avatarView.image = nil
nameLabel.text = nil
subtitleLabel.text = nil
}
}
// Enregistrement et utilisation
tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")
// Dans cellForRowAt:
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactCell
cell.configure(with: contacts[indexPath.row])
return cell
prepareForReuse() — une méthode appelée avant de placer une cellule dans la file d'attente de réutilisation. Réinitialisez tout l'état de la cellule : texte, images, animations, opérations de chargement. Sans prepareForReuse, une cellule réutilisée peut afficher d'anciennes données : l'image précédente sera visible pendant une fraction de seconde jusqu'au chargement de la nouvelle. Apple (WWDC 2023) recommande d'annuler les opérations asynchrones via URLSessionTask.cancel() dans prepareForReuse.
Les sections dans UITableView regroupent les lignes logiquement liées. Chaque section peut avoir un en-tête (header) et un pied de page (footer). Le nombre de sections est déterminé par la méthode numberOfSections(in:) (la valeur par défaut est 1). Pour les styles grouped et insetGrouped, les sections sont visuellement séparées par des marges et des coins arrondis.
| Méthode DataSource | Description | Valeur de retour |
|---|---|---|
| numberOfSections | Nombre de sections (par défaut 1) | Int |
| numberOfRowsInSection | Nombre de lignes dans une section | Int |
| titleForHeaderInSection | Texte du titre de l'en-tête de section | String? |
| titleForFooterInSection | Texte du titre du pied de section | String? |
| viewForHeaderInSection | Vue personnalisée pour l'en-tête | UIView? |
| heightForHeaderInSection | Hauteur de l'en-tête | CGFloat |
Indexation : pour une navigation rapide entre les sections, ajoutez un titre d'index — un tableau de chaînes affichées sur le côté droit. Implémentez sectionIndexTitles(for:) — renvoie un tableau des premières lettres de chaque section. Pour 26 sections ou plus, cela améliore considérablement l'UX. Sous iOS 15+, les tableaux prennent en charge sectionHeaderTopPadding — le padding entre la barre d'état et le premier en-tête, qui peut être réinitialisé à 0 pour une disposition dense.
Les performances d'UITableView sont cruciales pour les applications avec de longues listes. Problèmes courants : défilement lent (jank) dû à des opérations lourdes dans cellForRowAt, appels fréquents à heightForRowAt, absence de prefetching. Apple (WWDC 2025) met en avant cinq pratiques clés pour les tableaux de plus de 1000 éléments.
// Optimisation avec le 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)
}
}
}
// Utilisation de DiffableDataSource pour des mises à jour fluides
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)
}
}
Résultat pratique : l'application de ces optimisations augmente les FPS de défilement de 30 à 60 sur les appareils iPhone 12 et plus récents (selon les données de test d'Apple, 2025). Dans les projets IT Sectr, nous utilisons DiffableDataSource pour tous les nouveaux tableaux et ajoutons le prefetching pour les tableaux avec images (avatars, aperçus, icônes d'applications).
Questions fréquentes
Vérifiez : (1) contentSize > frame.size — le tableau doit avoir plus de contenu que sa hauteur ; (2) le tableau n'est pas à l'intérieur d'un autre UIScrollView qui intercepte les gestes ; (3) isScrollEnabled = true ; (4) numberOfRowsInSection renvoie le nombre correct. Un problème typique est un tableau avec heightForRowAt = UITableView.automaticDimension sans estimatedRowHeight, ce qui entraîne un calcul infini de la hauteur.
Utilisez performBatchUpdates ou DiffableDataSource au lieu de reloadData(). reloadData() provoque un rechargement complet sans animation, créant un scintillement visuel. Pour les mises à jour ciblées, utilisez : reloadRows(at:with:), insertRows, deleteRows. Toutes les modifications dans performBatchUpdates sont animées simultanément. DiffableDataSource le fait automatiquement sans appels manuels.
UITableView est une liste verticale à une seule colonne avec des styles de cellule prédéfinis et une édition intégrée. UICollectionView est une mise en page flexible (grille, liste, carrousel, cascade) avec un positionnement arbitraire des éléments. Choisissez UITableView pour les listes simples (réglages, contacts, messages). Choisissez UICollectionView pour les galeries, catalogues, tableaux et toute mise en page non linéaire.
Définissez tableView.tableFooterView = UIView(frame: .zero). Par défaut, UITableView affiche des lignes vides (séparateurs) sous le dernier élément jusqu'au bord inférieur. Définir une UIView vide comme tableFooterView les supprime. Pour le style grouped, ce n'est pas nécessaire — le tableau n'affiche que les lignes réelles avec des coins arrondis dans la dernière section.
Définissez tableView.rowHeight = 80 et tableView.estimatedRowHeight = 0 ou omettez estimatedRowHeight. Avec une hauteur fixe, UITableView n'appelle pas heightForRowAt, ce qui accélère le rendu de 5 à 10 fois pour les listes de plus de 100 éléments. Pour les cellules avec une hauteur personnalisée (contenu différent), utilisez l'auto-dimensionnement : estimatedRowHeight + UITableView.automaticDimension.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi