Pour une collection personnalisée à une seule colonne dans votre app Mac, utilisez la List de SwiftUI avec une liaison de sélection fondée sur un ensemble d’identifiants stables. Personnalisez les lignes tout en laissant le contrôle natif gérer la sélection par plage et la sélection avec Command-clic.
Ce tutoriel crée une collection d’étiquettes synthétiques, de Tile 01 à Tile 12. La tâche du lecteur consiste à sélectionner des éléments adjacents, à ajouter ou retirer des éléments individuellement et à garder une sélection compréhensible lors du filtrage ou de l’inversion de l’ordre d’affichage. Une section ultérieure explique la solution AppKit pour une collection qui nécessite une grille ou une autre disposition personnalisée.
Quel comportement attendre de la sélection par plage et avec Command-clic ?
Le Guide d’utilisation du Mac d’Apple décrit l’interaction habituelle : cliquez sur le premier élément, puis Maj-clic sur le dernier pour sélectionner les éléments adjacents, y compris ceux qui se trouvent entre les deux. Command-clic permet de sélectionner des éléments individuels qui ne sont pas nécessairement adjacents ; un Command-clic sur un élément déjà sélectionné le retire d’une sélection multiple. Ce sont les interactions de base à préserver dans cette collection. Sélectionner des éléments sur l’écran de votre Mac.
Pour l’implémentation initiale, utilisez une liste ordonnée unique. Ainsi, « entre » a un sens sans ambiguïté : les lignes situées entre deux extrémités dans l’ordre d’affichage actuel. Une collection spatiale soulève une autre question — une plage suit-elle la séquence des données ou une zone de la disposition ? — qui mérite des tests distincts.
Choisissez le contrôle en fonction de ce que l’utilisateur sélectionne :
| Tâche du lecteur | Point de départ adapté | Représentation de la sélection |
|---|---|---|
| Sélectionner des lignes personnalisées entières | SwiftUI List |
Ensemble d’identifiants d’éléments |
| Sélectionner des vignettes entières dans une disposition personnalisée | AppKit NSCollectionView |
Chemins d’index de sélection, associés aux identifiants du modèle |
| Sélectionner des caractères dans une étiquette | Sélection de texte SwiftUI | Sélection de texte fournie par la vue de texte |
| Sélectionner des plages dans un document texte | Système de texte AppKit | Plages de caractères ou de texte |
Apple décrit List comme un conteneur à une seule colonne avec une sélection facultative, et NSCollectionView comme une collection ordonnée avec une disposition personnalisable. Ce sont les points de départ pris en charge pour la sélection d’éléments. List, NSCollectionView.
Créer l’exemple SwiftUI complet
Créez un projet d’app macOS dans Xcode avec SwiftUI. Pour cet exemple, définissez la cible de déploiement sur macOS 14 ou une version ultérieure : la fermeture onChange à deux paramètres utilisée ci-dessous nécessite macOS 14. Remplacez la déclaration d’app générée par ce code, en ne laissant qu’un seul point d’entrée @main dans la cible. onChange(of:initial:_:), App.
Voici une petite app complète, avec ses données synthétiques et sa fenêtre. Elle n’utilise aucun élément externe. Son code a été vérifié par rapport aux API documentées, mais n’a pas été compilé ni exécuté sous macOS ici.
import Foundation
import SwiftUI
struct Tile: Identifiable {
let id: Int
let title: String
}
@main
struct CollectionSelectionDemoApp: App {
var body: some Scene {
WindowGroup {
TileCollectionView()
}
}
}
struct TileCollectionView: View {
private let tiles: [Tile] = (1...12).map {
Tile(id: $0, title: String(format: "Tile %02d", $0))
}
@State private var selectedIDs: Set<Int> = []
@State private var query = ""
@State private var reversed = false
private var visibleTiles: [Tile] {
let filtered = tiles.filter {
query.isEmpty ||
$0.title.localizedCaseInsensitiveContains(query)
}
return reversed ? Array(filtered.reversed()) : filtered
}
private var selectionDescription: String {
let names = visibleTiles
.filter { selectedIDs.contains($0.id) }
.map(\.title)
return names.isEmpty ? "None selected" : names.joined(separator: ", ")
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
TextField("Filter tiles", text: $query)
.textFieldStyle(.roundedBorder)
HStack {
Button("Reverse order") {
reversed.toggle()
}
Button("Clear selection") {
selectedIDs.removeAll()
}
.disabled(selectedIDs.isEmpty)
}
List(visibleTiles, selection: $selectedIDs) { tile in
HStack(spacing: 12) {
RoundedRectangle(cornerRadius: 4)
.fill(Color.gray.opacity(0.2))
.frame(width: 32, height: 32)
VStack(alignment: .leading) {
Text(tile.title)
Text("Sample item \(tile.id)")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
}
.padding(.vertical, 4)
.tag(tile.id)
}
Text("\(selectedIDs.count) selected")
Text(selectionDescription)
.font(.caption)
}
.padding()
.frame(minWidth: 440, minHeight: 480)
.onChange(of: query) { _, _ in
let visibleIDs = Set(visibleTiles.map(\.id))
selectedIDs.formIntersection(visibleIDs)
}
}
}
Le type des éléments de la liaison de sélection correspond à Tile.ID : les deux utilisent Int. Apple indique que lier la sélection d’une liste à un ensemble du type d’identifiant de ses données permet la sélection multiple. Sur Mac, la sélection multiple ne nécessite pas de passer en mode édition. Prise en charge de la sélection dans les listes.
La ligne reste personnalisable : elle contient une forme neutre, un titre et une étiquette secondaire. Aucun gestionnaire de clic au niveau de la ligne ni opération de bascule distincte n’est présent. En laissant la liste gérer la sélection, on évite que deux parties indépendantes du code la modifient lors d’une même interaction.
Séparer l’identité des éléments de l’ordre d’affichage
L’exemple distingue volontairement trois notions : un tableau ordonné d’éléments, un ensemble d’identifiants sélectionnés et une présentation filtrée de ce tableau. Swift définit les tableaux comme des collections ordonnées et les ensembles comme des collections de valeurs uniques sans ordre défini. Un ensemble convient donc pour représenter l’appartenance, tandis qu’un tableau fournit l’ordre utilisé pour la présentation. Types de collections.
Les identifiants entiers sont fixes pour ce jeu de données synthétiques. Inverser le tableau change la position d’un élément sans modifier son identité. Dans un modèle réel, attribuez à chaque élément un identifiant stable lors de sa création ou de son chargement, puis conservez cet identifiant lors des mises à jour ultérieures de la vue. Évitez de générer une nouvelle identité chaque fois qu’une ligne est évaluée.
Le récapitulatif de sélection suit visibleTiles et vérifie l’appartenance à selectedIDs. Ce choix d’implémentation découle des propriétés de ces collections : il produit un récapitulatif dans l’ordre d’affichage au lieu de dépendre de l’ordre d’itération d’un ensemble.
Le filtrage nécessite une décision explicite du produit. Cet exemple adopte une politique qui ne conserve que les éléments visibles : à chaque changement de filtre, la sélection est limitée aux identifiants encore visibles. Les éléments masqués perdent leur sélection, et la suppression du filtre ne restaure pas leur sélection précédente. Les étiquettes et le tableau source restent inchangés.
Par exemple, commencez par sélectionner Tile 02, Tile 03 et Tile 04. Saisissez 04 dans Filter tiles. Selon la politique de cet exemple, seule Tile 04 reste sélectionnée. Supprimez le filtre : les deux autres éléments réapparaissent sans être sélectionnés. Il s’agit d’un parcours illustratif du code, et non d’un résultat d’exécution rapporté.
Une autre app peut devoir conserver la sélection d’un filtre à l’autre. Dans ce cas, confiez la sélection au modèle, définissez la manière dont les sélections masquées apparaissent dans le décompte et le récapitulatif, puis testez leur synchronisation avec le contrôle affiché. Supprimer uniquement l’intersection ne définit pas une politique complète pour les sélections masquées.
Quand utiliser une vue de collection AppKit ?
Utilisez NSCollectionView lorsque la collection nécessite des vignettes sur plusieurs colonnes ou une disposition autre qu’une simple liste. Apple sépare la source de données de la collection, les contrôleurs de vue des éléments, le délégué et l’objet de disposition. Cela permet de personnaliser l’apparence et le positionnement tout en laissant un contrôle de collection gérer les interactions. NSCollectionView.
Configurez l’implémentation AppKit dans cet ordre :
- Créez la vue de collection et attribuez-lui une disposition moderne, telle que
NSCollectionViewFlowLayout. - Fournissez sa source de données et enregistrez la classe ou la nib de l’élément utilisée pour créer les cellules.
- Définissez
isSelectablesurtrueetallowsMultipleSelectionsurtrue. - Définissez
allowsEmptySelectionselon que la tâche autorise ou non une sélection vide. - Lisez
selectionIndexPaths, puis associez chaque chemin d’index valide à l’identifiant de modèle correspondant. - Affichez la sélection dans la vue de l’élément à l’aide de son état
isSelected.
La sélection multiple est désactivée par défaut. Il faut donc l’activer explicitement lors de la configuration ; le simple fait de dessiner des vignettes qui semblent sélectionnables ne suffit pas. La propriété isSelected de l’élément indique si celui-ci est sélectionné. allowsMultipleSelection, isSelected.
Considérez les chemins d’index comme des positions dans la source de données actuelle. Apple précise que selectionIndexPaths est mis à jour lors d’une sélection interactive et peut aussi être défini par programmation. L’attribution d’un chemin d’index qui ne désigne aucun élément valide de la source de données déclenche une exception. Après un réordonnancement ou un filtrage, reconstituez les positions de sélection à partir des identifiants stables au lieu de réutiliser d’anciennes positions. selectionIndexPaths.
Respectez la limite de la documentation : allowsMultipleSelection permet de sélectionner plusieurs éléments ; sa documentation ne précise pas un algorithme complet d’ancrage des plages pour chaque disposition personnalisée ou combinaison de modificateurs. Vérifiez le comportement de Maj-clic dans la disposition réelle et sur les versions de macOS prises en charge. Si la tâche exige une plage linéaire précise, l’implémentation à une seule colonne ci-dessus constitue un point de départ plus clair.
Pour une app SwiftUI qui nécessite le contrôle AppKit, intégrez-le avec NSViewRepresentable. Créez la hiérarchie AppKit dans makeNSView, synchronisez les changements du modèle dans updateNSView et utilisez un coordinateur pour transmettre les événements du délégué à l’état SwiftUI. SwiftUI contrôle le cadre et les limites de la vue représentée. NSViewRepresentable.
Les mises à jour par programmation nécessitent également un chemin de synchronisation explicite. Apple indique que collectionView(_:shouldSelectItemsAt:) et collectionView(_:didSelectItemsAt:) sont tous deux ignorés lorsque la sélection est définie par programmation. Regroupez les mises à jour du modèle dans une opération commune au lieu de compter sur l’exécution de ces rappels après chaque attribution. collectionView(_:shouldSelectItemsAt:), collectionView(_:didSelectItemsAt:).
La sélection de texte offre-t-elle la même fonctionnalité ?
La propriété textSelection(.enabled) de SwiftUI contrôle la sélection du texte dans les vues. Appliquée à un conteneur, elle rend sélectionnable chaque vue de texte qu’il contient. Elle ne documente ni une liaison de sélection d’éléments de collection ni un modèle partagé de sélection par plage pour des cartes arbitraires. Utilisez-la lorsque la tâche consiste à sélectionner des caractères dans une étiquette. textSelection(_:).
Pour une interface de document texte, AppKit expose les sélections de caractères via NSTextView.selectedRanges. Ses plages sont ordonnées, sans chevauchement et non contiguës. NSTextSelection.textRanges de TextKit représente également des plages de texte ordonnées logiquement et est disponible depuis macOS 12. Ces API décrivent des positions dans le texte ; leur traduction en identifiants de vignettes relève donc d’une conception propre à l’app. selectedRanges, textRanges.
Déterminez le rôle du geste sur l’étiquette d’une ligne avant d’y activer la sélection de texte. Pour la tâche de ce tutoriel, cliquer sur une étiquette doit participer à la sélection de la ligne entière ; l’exemple laisse donc la sélection de texte non configurée.
Vérifier l’interaction avec une petite matrice de sélection
Effectuez ces vérifications dans la fenêtre de l’app. Les quatre premières vérifient les conventions de base de la sélection sur Mac ; les suivantes mettent à l’épreuve les choix de l’exemple concernant l’identité et le filtrage. L’interaction documentée par Apple sert de référence, tandis que les étiquettes synthétiques facilitent la vérification des éléments sélectionnés. Sélectionner des éléments sur l’écran de votre Mac.
| Action | Résultat attendu |
|---|---|
Cliquez sur Tile 02 |
Un élément sélectionné |
Maj-clic sur Tile 05 |
Tile 02 à Tile 05 sélectionnés |
Command-clic sur Tile 08 |
Tile 08 s’ajoute à la sélection |
Command-clic sur Tile 03 déjà sélectionné |
Tile 03 est retiré de la sélection |
Cliquez sur Clear selection |
Sélection vide et récapitulatif None selected |
Sélectionnez Tile 04, puis inversez l’ordre |
L’identifiant sélectionné désigne toujours Tile 04 |
Sélectionnez plusieurs lignes, puis appliquez le filtre 04 |
Seul un élément sélectionné auparavant et toujours visible reste sélectionné |
| Supprimez le filtre | Les lignes masquées réapparaissent sans restaurer les sélections supprimées |
Vérifiez aussi la navigation au clavier après avoir cliqué sur la liste, faites défiler les lignes sélectionnées hors de la vue puis ramenez-les, et commencez une nouvelle plage après le filtrage. Pour une grille AppKit, ajoutez des vérifications qui traversent une limite de ligne. Si le résultat ne correspond pas à la tâche visée, ramenez d’abord l’interface à un contrôle natif simple, puis réintroduisez le contenu personnalisé des lignes et les gestionnaires de gestes un à un.
Laissez le contrôle natif gérer les interactions, les identifiants stables représenter les éléments sélectionnés et le tableau affiché définir l’ordre. En clarifiant ces responsabilités, le style personnalisé des lignes, le filtrage et le tri peuvent prendre en charge une sélection tout aussi compréhensible.
Sources
- Sélectionner des éléments sur l’écran de votre Mac — Assistance Apple.
- List — Documentation développeur Apple.
- App — Documentation développeur Apple.
- onChange(of:initial:_:) — Documentation développeur Apple.
- Types de collections — Le langage de programmation Swift, projet Swift.
- NSCollectionView — Documentation développeur Apple.
- allowsMultipleSelection — Documentation développeur Apple.
- selectionIndexPaths — Documentation développeur Apple.
- isSelected — Documentation développeur Apple.
- NSViewRepresentable — Documentation développeur Apple.
- collectionView(_:shouldSelectItemsAt:) — Documentation développeur Apple.
- collectionView(_:didSelectItemsAt:) — Documentation développeur Apple.
- textSelection(_:) — Documentation développeur Apple.
- selectedRanges — Documentation développeur Apple.
- textRanges — Documentation développeur Apple.