UICollectionView est un composant puissant d'UIKit pour iOS qui gère l'affichage de collections d'objets sous forme de grilles, listes et layouts personnalisés. Contrairement à UITableView, UICollectionView n'est pas limité à une seule colonne : il prend en charge le positionnement arbitraire des éléments via UICollectionViewLayout. Nous expliquons l'essence d'UICollectionView, comment fonctionnent FlowLayout et CompositionalLayout, et comment créer des cellules personnalisées en Swift. Chez IT Sectr, nous utilisons UICollectionView pour les galeries, catalogues et panneaux d'annonces. Pour une comparaison avec les tableaux, lisez l'article sur UITableView.
Points clés
UICollectionView est une classe du framework UIKit introduite dans iOS 6 (2012). Elle affiche une collection ordonnée de cellules (UICollectionViewCell) dans un layout flexible géré par un objet UICollectionViewLayout. UICollectionView résout le même problème que UITableView (listes) mais avec un positionnement arbitraire des éléments : grilles, carrousels, mosaïques, cascades.
Architecture UICollectionView est construite sur le modèle MVC : les données dans la DataSource, le layout dans UICollectionViewLayout, le comportement dans UICollectionViewDelegate. Cette séparation permet de remplacer les layouts sans modifier le code des données. Selon Apple (WWDC 2025), 72% des applications iOS du top 100 de l'App Store utilisent UICollectionView pour afficher du contenu. CollectionView prend en charge les layouts multi-colonnes depuis iOS 6, et depuis iOS 13, il a reçu CompositionalLayout pour les sections complexes.
UICollectionView utilise un pool de cellules réutilisables (reuse queue), similaire à UITableView. La méthode dequeueReusableCell(withReuseIdentifier:for:) retourne une cellule du pool ou en crée une nouvelle si le pool est vide. Contrairement à UITableView, UICollectionViewCell dispose d'un contentView intégré dans lequel vous devez ajouter toutes les sous-vues — ne les ajoutez pas directement à la cellule, car contentView gère l'animation correcte de sélection et d'édition.
UICollectionViewFlowLayout — le layout standard qui fonctionne depuis iOS 6. Il dispose les éléments en lignes (flow) avec la possibilité de configurer la taille de cellule, la direction de défilement, les espacements minimaux et les sections. UICollectionViewCompositionalLayout — un layout moderne (iOS 13+) qui décrit le layout via une combinaison de blocs : groupe → section → layout, où chaque groupe peut être horizontal, vertical ou personnalisé.
| Paramètre | UICollectionViewFlowLayout | UICollectionViewCompositionalLayout |
|---|---|---|
| Version minimale | iOS 6 | iOS 13 |
| Layouts complexes | Seulement grille ou liste simple | Sections avec différents layouts, groupes, carrousels |
| Adaptabilité | Via la taille du délégué | NSCollectionLayoutDimension.fractionalWidth/Height |
| Performance | Rend tous les éléments visibles | Création paresseuse des sections |
| Support iPad | Nécessite une adaptation manuelle | Groupes adaptatifs via fractionalWidth |
| Complexité d'implémentation | Faible (5–10 lignes) | Moyenne (15–30 lignes) |
// UICollectionView de base avec FlowLayout
class ViewController: UIViewController {
private var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumInteritemSpacing = 8
layout.minimumLineSpacing = 12
layout.sectionInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
layout.itemSize = CGSize(width: view.frame.width - 32, height: 100)
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "cell")
collectionView.dataSource = self
view.addSubview(collectionView)
}
}
// DataSource
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int { 20 }
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
as! CustomCell
cell.configure(with: "Item \(indexPath.row)")
return cell
}
}
Recommandation d'Apple (WWDC 2025) : utilisez CompositionalLayout pour les nouveaux projets. Il offre plus de capacités pour les layouts adaptatifs, fonctionne mieux avec UIKit et SwiftUI via UICollectionView.Representable, et prend en charge les en-têtes de section avec taille automatique. Gardez FlowLayout pour les écrans simples avec une grille de cellules identiques et une version minimale iOS 12 et inférieure.
UICollectionViewCell — la classe de base pour les cellules d'UICollectionView. Chaque cellule contient contentView (conteneur principal), backgroundView (arrière-plan par défaut), selectedBackgroundView (arrière-plan sélectionné). Configurez l'apparence de la cellule à l'intérieur de contentView — n'ajoutez jamais de sous-vues directement à UICollectionViewCell.
// Cellule personnalisée avec configuration
class ProductCell: UICollectionViewCell {
private let imageView = UIImageView()
private let titleLabel = UILabel()
private let priceLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
contentView.addSubview(imageView)
contentView.addSubview(titleLabel)
contentView.addSubview(priceLabel)
// Contraintes Auto Layout
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 8),
titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
priceLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
priceLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8)
])
}
func configure(with product: Product) {
imageView.image = UIImage(named: product.imageName)
titleLabel.text = product.title
priceLabel.text = product.price
}
}
UICollectionViewDelegate fournit des méthodes pour gérer la sélection, le surlignage, les menus contextuels et le réordonnancement. Contrairement à UITableView, UICollectionViewDelegate permet de contrôler non seulement les tap sur les éléments mais aussi l'appui long (menu contextuel via UIContextMenuInteraction). Pour gérer les tap, implémentez collectionView(_:didSelectItemAt:) — il est appelé lorsque l'animation de sélection est terminée.
UICollectionViewDiffableDataSource — un remplacement type-safe d'UICollectionViewDataSource introduit dans iOS 13. Au lieu de gérer manuellement les cellules via cellForItemAt, DiffableDataSource utilise NSDiffableDataSourceSnapshot — un instantané de l'état des données qui calcule automatiquement la différence entre l'état ancien et le nouvel état et anime les changements.
// DiffableDataSource avec sections
enum Section {
case featured, recommended, categories
}
class ModernDataSource: UIViewController {
private var dataSource: UICollectionViewDiffableDataSource<Section, Product>!
override func viewDidLoad() {
super.viewDidLoad()
configureDataSource()
applyInitialSnapshot()
}
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<ProductCell, Product> { cell, _, product in
cell.configure(with: product)
}
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
collectionView, indexPath, product in
collectionView.dequeueConfiguredReusableCell(
using: cellRegistration, for: indexPath, item: product
)
}
}
private func applyInitialSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Section, Product>()
snapshot.appendSections([.featured, .recommended, .categories])
snapshot.appendItems(featuredProducts, toSection: .featured)
snapshot.appendItems(recommendedProducts, toSection: .recommended)
snapshot.appendItems(categories, toSection: .categories)
dataSource.apply(snapshot, animatingDifferences: true)
}
// Mise à jour avec animation automatique
func updateProducts(_ products: [Product]) {
var snapshot = dataSource.snapshot()
snapshot.deleteItems(snapshot.itemIdentifiers)
snapshot.appendItems(products)
dataSource.apply(snapshot, animatingDifferences: true)
}
}
Avantages de DiffableDataSource : (1) animation automatique — insert, delete, reload se produisent avec une animation par défaut ; (2) sécurité de type — les sections et les éléments sont typés, erreurs de compilation au lieu de plantages runtime ; (3) Snapshot — l'instantané d'état peut être sauvegardé, annulé, comparé ; (4) performance — DiffableDataSource utilise l'algorithme de Hellier pour calculer l'ensemble minimal de changements. Apple (WWDC 2024) recommande DiffableDataSource pour tous les nouveaux UICollectionView, et de conserver l'ancienne DataSource uniquement pour iOS 12 et versions antérieures.
UICollectionViewLayout — une classe abstraite qui contrôle entièrement le positionnement des éléments de la collection. Si FlowLayout et CompositionalLayout ne conviennent pas (par exemple, layout circulaire, spirale, diagramme), créez une sous-classe d'UICollectionViewLayout et remplacez les méthodes de préparation du layout.
// Layout circulaire personnalisé
class CircularLayout: UICollectionViewLayout {
private var attributesCache: [UICollectionViewLayoutAttributes] = []
private let radius: CGFloat = 120
override func prepare() {
super.prepare()
attributesCache.removeAll()
guard let collectionView else { return }
let count = collectionView.numberOfItems(inSection: 0)
let center = CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY)
for i in 0..<count {
let indexPath = IndexPath(item: i, section: 0)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
let angle = (CGFloat(i) / CGFloat(count)) * 2 * .pi
attributes.center = CGPoint(
x: center.x + radius * cos(angle),
y: center.y + radius * sin(angle)
)
attributes.size = CGSize(width: 60, height: 60)
attributesCache.append(attributes)
}
}
override var collectionViewContentSize: CGSize {
collectionView?.bounds.size ?? .zero
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
attributesCache.filter { $0.frame.intersects(rect) }
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
attributesCache[indexPath.item]
}
}
Performance : un UICollectionViewLayout personnalisé doit mettre en cache les attributs dans prepare() et les retourner en O(1) dans layoutAttributesForItem(at:). Pour les collections de 1000+ éléments, utilisez des mises à jour incrémentielles du cache via shouldInvalidateLayout(forBoundsChange:) — cela évite de recalculer l'ensemble du layout lors du défilement. Apple (WWDC 2023) recommande CompositionalLayout plutôt que les layouts personnalisés : il couvre 95% des scénarios sans avoir à écrire un layout à partir de zéro.
UITableView et UICollectionView sont les deux principaux composants UIKit pour afficher des listes. UITableView est plus simple et conçu pour les listes à une seule colonne. UICollectionView est plus flexible et prend en charge tout type de layout. Le choix entre eux dépend de la structure visuelle des données et de la personnalisation requise.
| Paramètre | UITableView | UICollectionView |
|---|---|---|
| Layout | Liste verticale uniquement | Liste, grille, cascade, carrousel, personnalisé |
| Complexité | Faible (DataSource simple) | Moyenne (Layout + DataSource + Delegate) |
| Cellule par défaut | 4 styles (basic, subtitle, value1, value2) | Vide uniquement (personnalisée via contentView) |
| Mode édition | Intégré (delete, move, insert) | Nécessite une implémentation manuelle |
| Header/Footer | Intégrés (viewForHeaderInSection) | Via les supplementary views |
| Performance | Élevée pour les listes | Élevée pour tout layout |
| iOS 6+ | Disponible depuis iOS 2 | Disponible depuis iOS 6 |
Règle pratique : utilisez UITableView si l'écran est une simple liste de lignes (paramètres, contacts, menu). Utilisez UICollectionView si l'écran nécessite une grille, un défilement horizontal, un carrousel, différentes tailles de cellule ou un regroupement complexe. Depuis iOS 14, UITableView a également obtenu le support de CompositionalLayout via UICollectionLayoutListConfiguration, permettant de combiner les avantages des deux composants.
Foire aux questions
Définissez scrollDirection = .horizontal dans UICollectionViewFlowLayout. Pour CompositionalLayout, utilisez NSCollectionLayoutSize avec fractionalWidth pour la largeur adaptative du groupe. Le défilement horizontal est adapté aux carrousels, galeries et catégories. Pour le défilement paginé, définissez collectionView.isPagingEnabled = true ou utilisez UICollectionViewFlowLayout avec itemSize = bounds.width.
Vérifiez : (1) la cellule est enregistrée via register(_:forCellWithReuseIdentifier:), (2) la DataSource retourne un nombre d'éléments > 0, (3) la cellule est défilée avec le bon reuseIdentifier, (4) itemSize n'est pas nul (pour FlowLayout). Une erreur typique est itemSize = CGSize.zero, qui donne aux cellules une hauteur nulle. Définissez itemSize explicitement ou implémentez le délégué collectionView(_:layout:sizeForItemAt:).
Pour FlowLayout : layout.minimumInteritemSpacing (entre les éléments d'une ligne) et layout.minimumLineSpacing (entre les lignes). Pour CompositionalLayout : NSCollectionLayoutGroup.interItemSpacing et contentInsets sur le groupe/la section. sectionInset définit les marges externes de la section. Utilisez sectionInsetReference = .fromContentInset pour une gestion correcte de la Safe Area.
Utilisez performBatchUpdates pour les animations groupées : collectionView.performBatchUpdates { insertItems, deleteItems, reloadItems }. Pour des mises à jour sûres et animées, utilisez UICollectionViewDiffableDataSource : appelez dataSource.apply(snapshot, animatingDifferences: true) — tous les changements seront calculés et appliqués automatiquement sans insert/delete manuel.
Dans SwiftUI, les équivalents d'UICollectionView sont LazyVGrid et LazyHGrid (iOS 14+). SwiftUI Grid est plus simple à écrire (syntaxe déclarative) mais inférieur à UICollectionView en performance pour 500+ éléments et en personnalisation de layouts. Pour les collections complexes, utilisez UICollectionView via UIViewRepresentable. Dans les projets IT Sectr, nous choisissons UICollectionView pour les catalogues et galeries, et SwiftUI Grid pour les écrans simples.
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