Table of Contents
Dans le développement d'iOS, et sont les deux principaux contenants défilants pour afficher des collections de données. Bien qu'ils partagent de nombreuses similitudes – à la fois en fonction de la délégation, des sources de données et des cellules réutilisables – chacun est le mieux adapté pour des cas d'utilisation spécifiques. excelle dans la présentation d'une simple liste verticale d'articles, comme un flux de titres d'article avec des descriptions courtes. offre des mises en page beaucoup plus flexibles, y compris des grilles, défilement horizontal et arrangements personnalisés, ce qui le rend idéal pour les flux d'information à forte image ou les interfaces de style magazine.
Comprendre la vue de la table d'interface et l'interface utilisateur
Avant de plonger dans la mise en œuvre, il est essentiel de comprendre les différences architecturales et quand choisir l'une par rapport à l'autre. Les deux vues font partie de l'UIKit et suivent le modèle MVC (Model-View-Controller), mais leurs capacités de mise en page et leurs API divergent considérablement.
UITableView: Le cheval de travail pour les listes
Chaque ligne correspond à une cellule, et la vue gère automatiquement le défilement vertical, la réutilisation de cellules et les animations de lignes. C'est le choix naturel pour les flux d'information où chaque élément est une mise en page cohérente, empilée verticalement : un titre, une brève description, un horodatage et éventuellement une image. Le protocole fournit deux méthodes essentielles : et . Le délégué contrôle la hauteur, la sélection et l'édition des lignes. Avec l'UIKit moderne, vous pouvez également utiliser des cellules autodimensionnelles en paramétrant .
UICollectionView: Flexibilité pour les mises en page complexes
sépare la présentation des données de l'algorithme de mise en page par l'intermédiaire de l'objet . La mise en page par défaut du flux () arrange les éléments d'une grille ou de lignes horizontales/verticales en circulation, mais vous pouvez la sous-classer ou utiliser les plans modernes pour les mises en page multisections avec des comportements uniques (par exemple, un carrousel horizontal suivi d'une grille). Pour les flux d'information qui mélangent des types de médias — vidéos, images, cartes d'article — ou nécessitent un espacement adaptatif et des tailles dynamiques de cellules, ] est le choix supérieur.
Mise en place d'un flux de nouvelles de base
Nous allons vous aider à créer un contrôleur de visionnage minimal pour chaque composant. Les deux exemples supposent que vous construisez une application à écran unique avec Xcode et Swift. Pour la brièveté, nous omettons la configuration du storyboard; nous allons créer les vues de façon programmatique.
Création d'un flux de nouvelles avec UITableView
Commencez par sous-classer et se conformer à et . Ajouter un comme sous-vue principale. Voici une implémentation complète qui utilise un échantillon de titres d'article:
import UIKit
class NewsTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
private let tableView = UITableView()
private let newsItems: [String] = [
"Breaking: Major Policy Change Announced",
"Tech Giants Unveil AI Partnership",
"Local Weather Warning: Heavy Rain Expected"
]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
title = "News Feed"
setupTableView()
}
private func setupTableView() {
tableView.frame = view.bounds
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.rowHeight = 80
view.addSubview(tableView)
}
// MARK: - UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newsItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = newsItems[indexPath.row]
cell.textLabel?.numberOfLines = 0
cell.accessoryType = .disclosureIndicator
return cell
}
// MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: Path) {
tableView.deselectRow(at: indexPath, animated: true)
// Navigate to article detail (to be implemented)
}
}
Dans cette version de base, chaque cellule affiche une seule ligne de texte. Pour un flux de nouvelles de production, vous remplaceriez la plaine par une sous-classe personnalisée contenant des étiquettes pour le titre, la description et une vue d'image. L'utilisation de pour la hauteur dynamique est recommandée lorsque la longueur du contenu varie.
Création d'un flux de nouvelles avec UICollectionView
Pour un flux de type grille (commun aux cartes de nouvelles à forte intensité d'images), nous implémentons un en utilisant . Voici un exemple autonome qui affiche un ensemble d'images de placeholder :
import UIKit
class NewsCollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private var collectionView: UICollectionView!
private let imageNames = ["news1", "news2", "news3", "news4"] // Assume images in asset catalog
override func viewDidLoad() {
super.viewDidLoad()
title = "Image Feed"
setupCollectionView()
}
private func setupCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumInteritemSpacing = 8
layout.minimumLineSpacing = 8
layout.sectionInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = .white
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(ImageCell.self, forCellWithReuseIdentifier: "ImageCell")
view.addSubview(collectionView)
}
// MARK: - UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageNames.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
cell.loadImage(named: imageNames[indexPath.item])
return cell
}
// MARK: - UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 8 * 3 // 3 gaps: left, interitem, right
let availableWidth = view.frame.width - padding
let widthPerItem = availableWidth / 2
return CGSize(width: widthPerItem, height: widthPerItem * 1.2)
}
}
// Custom cell class
class ImageCell: UICollectionViewCell {
private let imageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
contentView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
required init?(coder: NSCoder) { fatalError("init(coder:)") }
func loadImage(named: String) {
imageView.image = UIImage(named: named)
}
}
Ce code crée une grille à deux colonnes avec un rapport d'aspect 1.2:1 par cellule. Vous pouvez ajuster à pour un flux de style carousel. Pour les applications réelles, remplacer les images de placement par un chargement d'image asynchrone via ou des bibliothèques comme Kingfisher.
Personnaliser les cellules pour un flux de nouvelles plus riche
Les cellules texte simples sont insuffisantes pour une application moderne. Vous devez afficher les vignettes, les dates de publication, les noms de source et parfois les boutons interactifs (partages, sauvegardes). Cela nécessite la création de sous-classes personnalisées ou avec des sous-vues correctement définies.
Conception d'une cellule article vedette (UITableView)
Créez une cellule qui comprend pour une image de grand héros, pour le titre, une autre pour l'extrait et une petite étiquette pour l'horodatage. Utilisez la mise en page automatique pour assurer que la cellule se développe correctement. Exemple :
class ArticleCell: UITableViewCell {
let heroImageView = UIImageView()
let titleLabel = UILabel()
let excerptLabel = UILabel()
let dateLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder: NSCoder) { fatalError() }
private func setupViews() {
heroImageView.contentMode = .scaleAspectFill
heroImageView.clipsToBounds = true
titleLabel.font = UIFont.boldSystemFont(ofSize: 18)
titleLabel.numberOfLines = 2
excerptLabel.font = UIFont.systemFont(ofSize: 14)
excerptLabel.textColor = .gray
excerptLabel.numberOfLines = 3
dateLabel.font = UIFont.systemFont(ofSize: 12)
dateLabel.textColor = .lightGray
for subview in [heroImageView, titleLabel, excerptLabel, dateLabel] {
subview.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(subview)
}
// Constraints – simplified; in practice use stack views or manual anchors
NSLayoutConstraint.activate([
heroImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
heroImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
heroImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
heroImageView.heightAnchor.constraint(equalToConstant: 200),
titleLabel.topAnchor.constraint(equalTo: heroImageView.bottomAnchor, constant: 8),
titleLabel.leadingAnchor.constraint(equalTo: heroImageView.leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: heroImageView.trailingAnchor),
excerptLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
excerptLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
excerptLabel.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor),
dateLabel.topAnchor.constraint(equalTo: excerptLabel.bottomAnchor, constant: 4),
dateLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
dateLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8)
])
}
func configure(with article: Article) {
titleLabel.text = article.title
excerptLabel.text = article.excerpt
dateLabel.text = article.dateString
// Load image asynchronously (omitted)
}
}
Dans le contrôleur de vue, enregistrez cette cellule personnalisée et implémentez en conséquence. Réglez et pour permettre des hauteurs variables.
Adapter la cellule pour UICollectionView
Le même modèle s'applique à . Parce que les vues de collection peuvent avoir des mises en page différentes, vous pouvez concevoir une cellule -style --card --qui fonctionne dans les modes de défilement vertical et horizontal. Utilisez pour simplifier l'alignement. Voici une version compacte :
class NewsCardCell: UICollectionViewCell {
private let cardView = UIView()
private let imageView = UIImageView()
private let titleLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
cardView.backgroundColor = .systemBackground
cardView.layer.cornerRadius = 10
cardView.clipsToBounds = true
cardView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(cardView)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
titleLabel.numberOfLines = 2
titleLabel.translatesAutoresizingMaskIntoConstraints = false
cardView.addSubview(imageView)
cardView.addSubview(titleLabel)
// Layout
NSLayoutConstraint.activate([
cardView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4),
cardView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 4),
cardView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -4),
cardView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4),
imageView.topAnchor.constraint(equalTo: cardView.topAnchor),
imageView.leadingAnchor.constraint(equalTo: cardView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: cardView.trailingAnchor),
imageView.heightAnchor.constraint(equalToConstant: 120),
titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 8),
titleLabel.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 8),
titleLabel.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -8),
titleLabel.bottomAnchor.constraint(lessThanOrEqualTo: cardView.bottomAnchor, constant: -8)
])
}
required init?(coder: NSCoder) { fatalError() }
}
Avec cette cellule, la vue de collection peut afficher un flux de style Pinterest si vous fournissez différentes tailles d'articles par l'intermédiaire du délégué.
Ajout de mises à jour en temps réel et en temps réel
Les flux d'information modernes doivent supporter des gestes rafraîchissants et une charge dynamique. La classe s'intègre parfaitement à la fois et lorsque la vue du rouleau est déjà branchée à un contrôle rafraîchissant. Dans :
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshNewsFeed), for: .valueChanged)
tableView.refreshControl = refreshControl
// or collectionView.refreshControl = refreshControl
Dans la méthode , effectuer une recherche de données asynchrones (par exemple, à partir d'une API REST), puis recharger la vue sur la file d'attente principale et appeler . Exemple :
@objc private func refreshNewsFeed() {
NewsAPIClient.fetchLatestArticles { [weak self] articles in
DispatchQueue.main.async {
self?.newsItems = articles
self?.tableView.reloadData()
self?.tableView.refreshControl?.endRefreshing()
}
}
}
Pour les mises à jour en temps réel comme les nouvelles en direct, envisagez d'utiliser ou une bibliothèque tierce comme Firebase Realtime Database. Lorsque de nouvelles données arrivent, insérez des lignes ou des éléments avec des animations :
// For UITableView
tableView.performBatchUpdates({
tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .top)
}, completion: nil)
// For UICollectionView
collectionView.performBatchUpdates({
collectionView.insertItems(at: [IndexPath(item: 0, section: 0)])
}, completion: nil)
Manipulation des interactions et de la navigation des utilisateurs
Lorsqu'un utilisateur touche un élément de nouvelles, l'application doit naviguer vers une vue de détail. Implémenter la méthode de délégation et utiliser soit une storyboard segue ou une poussée programmatique. Pour les tables et les collections, la méthode de délégation fournit le chemin d'index. Exemple pour une simple :
// In UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let article = newsItems[indexPath.row]
let detailVC = ArticleDetailViewController(article: article)
navigationController?.pushViewController(detailVC, animated: true)
}
// In UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let article = newsItems[indexPath.item]
let detailVC = ArticleDetailViewController(article: article)
navigationController?.pushViewController(detailVC, animated: true)
}
Envisager d'ajouter des gestes à longue pression pour des actions supplémentaires (part, signet) via attaché à la collection ou à la vue de table, ou utiliser des menus contextuels introduits dans iOS 13.
Considérations de rendement pour les gros aliments du bétail
Les flux de nouvelles peuvent atteindre des centaines ou des milliers d'articles. et réutiliser des cellules pour garder la mémoire basse, mais vous devez toujours éviter de bloquer le fil principal.
- Chargement d'images asynchrones :[ Ne chargez jamais d'images du réseau sur la file d'attente principale. Utilisez des bibliothèques comme Kingfisher ou SDWebImage[ qui gèrent automatiquement la mise en cache, la pré-impression et l'annulation.
- Préconditionnement:[ Conformez-vous à ou pour commencer à charger les données pour les cellules hors écran avant qu'elles apparaissent.
- Calculer les tailles efficacement:[ Pour les cellules à hauteur variable, utilisez la mise en page automatique avec . Dans les vues de collecte, les tailles calculées pour éviter les calculs répétés de mise en page.
- Limiter le rendu des cellules invisibles:[ Surpasser dans les cellules personnalisées pour annuler les téléchargements d'images et réinitialiser l'état.
- Utilisez des sources de données différables (voir la section suivante) pour réduire les appels de rechargement de données et effectuer des mises à jour animées avec un minimum de scintillement.
Approches modernes : Mise en page de la composition et source de données à redistribuer
iOS 13 et plus tard ont introduit deux API puissantes qui simplifient considérablement les flux d'information complexes: et (qui fonctionne également avec via .
UICollectionViewLayout composite
Cette disposition vous permet de définir des sections avec différents comportements – par exemple, une section de bannières héros qui défile horizontalement, une grille de nouvelles et une liste verticale des derniers articles. Chaque section est définie par un qui contient des groupes et des éléments. Code d'échantillonnage pour un flux à deux sections (carrousel horizontal + liste verticale) :
let layout = UICollectionViewCompositionalLayout { (sectionIndex, environment) -> NSCollectionLayoutSection? in
switch sectionIndex {
case 0:
// Horizontal carousel
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.8), heightDimension: .absolute(200))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
section.interGroupSpacing = 10
return section
case 1:
// Vertical grid (2 columns)
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .estimated(150))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(150))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
group.interItemSpacing = .fixed(8)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 8
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 8)
return section
default:
return nil
}
}
collectionView.collectionViewLayout = layout
Source de données à diffuser
Au lieu de manuel et de gestion du chemin d'index, les sources de données différables vous permettent d'appliquer des instantanés. Ceci élimine les incohérences et simplifie les animations. Exemple pour :
enum Section {
case featured
case latest
}
struct Article: Hashable {
let id: UUID
let title: String
// ...
}
var dataSource: UICollectionViewDiffableDataSource!
// In viewDidLoad:
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
(collectionView, indexPath, article) -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ArticleCell
cell.configure(with: article)
return cell
}
// When data arrives:
var snapshot = NSDiffableDataSourceSnapshot()
snapshot.appendSections([.featured, .latest])
snapshot.appendItems(featuredArticles, toSection: .featured)
snapshot.appendItems(latestArticles, toSection: .latest)
dataSource.apply(snapshot, animatingDifferences: true)
Des sources de données différentes peuvent également être utilisées avec via . Ce modèle est maintenant recommandé par Apple[ pour tous les nouveaux projets car il élimine les bogues communs et simplifie la gestion de l'état.
Intégration avec les sources de données réelles
Pour rendre votre flux dynamique, connectez-le à une API backend. Les approches communes comprennent :
- REST API:[ Utilisez pour récupérer JSON, décoder avec , et remplir votre tableau de modèles. Exemple avec (iOS 15+):
func fetchArticles() async throws -> [Article] {
let url = URL(string: "https://api.example.com/news")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Article].self, from: data)
}
- Core Data / CloudKit:[ Pour les premiers flux hors ligne, entreposez des articles localement en utilisant Core Data, puis synchronisez avec un moteur de cloud. Utilisez un avec pour mettre automatiquement à jour l'interface utilisateur lorsque les données changent.
- Firebase Firestore: Les auditeurs en temps réel peuvent mettre à jour l'instantané de la collection directement.
Toujours gérer les erreurs gracieusement – montrer un détenteur de place, un bouton de réessayer, ou une version en cache du flux.
Personnalisations avancées et polonais
Pour faire ressortir votre flux de nouvelles, envisagez les améliorations suivantes:
- Transitions cellulaires animées:[ Utilisez ou la méthode de la collection pour s'estomper dans les cellules telles qu'elles apparaissent.
- Les menus contextuels et les actions de balayage:[ Sur , implémenter pour marquer les articles comme lus ou enregistrés. Pour , utiliser par l'intermédiaire du délégué.
- En-têtes de section pointus:[ Pour les flux groupés, faire des en-têtes de section -collant en utilisant le style de vue ou le réglage de la table simple sur une disposition de composition.
- Mode noir et type dynamique:[ Utilisez et pour assurer que votre alimentation respecte les paramètres du système.
Testez votre flux de nouvelles
Utilisez le simulateur pour tester différentes tailles et orientations des appareils. Pour les tests de performance, instrument avec Time Profiler et vérifier les images perdues pendant le défilement.
Conclusion
Pour construire un flux de nouvelles robuste dans iOS, il faut une solide compréhension de et , ainsi que des outils modernes comme les mises en page de composition et les sources de données différentes. En suivant les modèles décrits dans ce guide – conception de cellules personnalisées, chargement de données asynchrones, retouche et réglage de performance soigné – vous pouvez créer des flux de nouvelles attrayants et réactifs qui ravissent les utilisateurs. Le tutoriel officiel Apple UITableView et UICollectionView documentation sont des références inestimables, tout comme les ressources communautaires comme Ray Wenderlich=»s collection view tutorial.