Pour créer des aperçus SwiftUI interactifs, attribuez à chaque aperçu son propre état local, transmettez un modèle de vue observable à l’éditeur et exposez les propriétés modifiables au moyen de liaisons. Ce guide pas à pas crée un petit éditeur d’étiquettes macOS à l’aide de textes d’exemple synthétiques.
Choisir où stocker l’état de l’aperçu
L’objectif est de prévisualiser un éditeur de bureau dans lequel une personne modifie le titre et le texte détaillé d’une étiquette, puis active ou désactive l’affichage du détail. L’éditeur doit réagir immédiatement, et chaque aperçu doit démarrer avec une configuration d’exemple explicite.
Utilisez l’entrée la plus simple qui permette l’interaction. Apple recommande de ne transmettre aux vues que les données dont elles ont besoin, car des dépendances complexes rendent les aperçus plus difficiles à construire. Une étiquette statique peut recevoir une chaîne ; un éditeur a besoin d’une liaison modifiable avec son état. Ajouter des aperçus à vos fichiers d’interface.
| Besoin de la vue | Entrée appropriée | Emplacement de l’état d’exemple |
|---|---|---|
| Afficher un texte fixe | Une chaîne immuable | Directement dans l’aperçu |
| Modifier une valeur détenue par son parent | Une propriété @Binding |
@State local dans le conteneur de l’aperçu |
| Modifier plusieurs propriétés observables liées | Un modèle observable exposé via @Bindable |
Une instance du modèle stockée dans le @State du conteneur de l’aperçu |
| Lire un modèle fourni via l’environnement | Une dépendance d’environnement | Injection explicite à la racine de l’aperçu |
Dans cet exemple, le modèle de vue contient deux chaînes, un paramètre de visibilité et un titre d’affichage calculé. Chaque aperçu possède sa propre instance. Ainsi, les conditions initiales sont visibles à côté de la déclaration de l’aperçu, et l’exemple ne dépend pas d’un singleton mutable partagé.
Préparer une cible SwiftUI pour macOS
Utilisez Xcode 16 ou une version ultérieure pour la syntaxe @Previewable intégrée présentée ici. Apple a introduit cette macro dans Xcode 16. Définissez la cible de déploiement de cet exemple sur macOS 14 ou une version ultérieure, où SwiftUI prend en charge Observation ; la référence actuelle d’Apple sur Previewable() indique également la disponibilité à partir de macOS 14. Notes de version de Xcode 16, Gérer les données de modèle dans votre app, Previewable().
Ajoutez un fichier Swift nommé LabelEditor.swift à une cible d’app macOS SwiftUI existante. Les deux blocs de code suivants forment ensemble l’exemple complet d’aperçu. Conservez le point d’entrée existant de l’app ; ce fichier définit le modèle, l’éditeur et les aperçus.
Le code utilise la notation @State sur les chaînes d’outils prises en charge. La documentation actuelle d’Apple précise que son implémentation diffère : Xcode 26 et les versions antérieures utilisent le wrapper de propriété State, tandis que Xcode 27 et les versions ultérieures utilisent la macro State(). Gardez cette distinction à l’esprit lorsque vous examinez le code généré ou évaluez les coûts d’initialisation. State, State().
Créer le modèle observable et la vue modifiable
Appliquez @Observable au modèle pour que SwiftUI puisse suivre les propriétés lues par l’interface. Utilisez @Bindable dans l’éditeur afin de créer les liaisons de propriétés nécessaires à ses champs de texte et à son interrupteur. Apple documente cette combinaison pour modifier des modèles observables. Gérer les données de modèle dans votre app, Bindable.
import Foundation
import Observation
import SwiftUI
@MainActor
@Observable
final class LabelViewModel {
var title: String
var detail: String
var showsDetail: Bool
init(
title: String,
detail: String,
showsDetail: Bool = true
) {
self.title = title
self.detail = detail
self.showsDetail = showsDetail
}
var displayedTitle: String {
let trimmed = title.trimmingCharacters(
in: .whitespacesAndNewlines
)
return trimmed.isEmpty ? "Untitled label" : trimmed
}
}
@MainActor
struct LabelEditor: View {
@Bindable var model: LabelViewModel
var body: some View {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 6) {
Text("Title")
TextField("Label title", text: $model.title)
}
VStack(alignment: .leading, spacing: 6) {
Text("Detail")
TextField("Label detail", text: $model.detail)
}
Toggle("Show detail", isOn: $model.showsDetail)
Divider()
Text("Rendered label")
.font(.caption)
.foregroundStyle(.secondary)
VStack(alignment: .leading, spacing: 8) {
Text(model.displayedTitle)
.font(.title2)
.fixedSize(horizontal: false, vertical: true)
if model.showsDetail {
Text(model.detail)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(
.quaternary,
in: RoundedRectangle(cornerRadius: 10)
)
}
.textFieldStyle(.roundedBorder)
.padding(20)
}
}
Le modèle accepte délibérément toutes les valeurs d’exemple par son initialiseur. Celui-ci se contente d’affecter les propriétés locales. Le calcul de remplacement modifie le texte de l’étiquette affiché sans écraser le titre saisi : un titre composé uniquement d’espaces reste modifiable, tandis que le résultat affiché indique Untitled label.
L’appelant fournit son modèle à l’éditeur. Sa déclaration @Bindable fournit des liaisons telles que $model.title ; la propriété de l’état appartient au conteneur de l’aperçu. Si cette vue se contentait d’afficher les valeurs du modèle, une simple référence au modèle suffirait pour l’observation. Le stockage avec Bindable est utile ici, car les contrôles ont besoin de liaisons modifiables avec les propriétés. Bindable.
Attribuer un état d’exemple indépendant à chaque aperçu
Ajoutez les déclarations suivantes au même fichier. Chacune crée son propre modèle avec des valeurs synthétiques explicites. Les largeurs servent à vérifier la mise en page à titre d’illustration ; elles ne constituent pas des tailles minimales de fenêtre recommandées.
#Preview("Standard label") {
@Previewable @State var model = LabelViewModel(
title: "Sample tile",
detail: "Blue square, medium size"
)
LabelEditor(model: model)
.frame(width: 420)
.preferredColorScheme(.light)
}
#Preview("Long label in a narrow editor") {
@Previewable @State var model = LabelViewModel(
title: "Sample collection with a longer descriptive label",
detail: "Round tiles arranged in three columns"
)
LabelEditor(model: model)
.frame(width: 300)
.preferredColorScheme(.dark)
}
#Preview("Blank title and hidden detail") {
@Previewable @State var model = LabelViewModel(
title: " ",
detail: "An optional detail line",
showsDetail: false
)
LabelEditor(model: model)
.frame(width: 420)
}
@Previewable intègre la déclaration d’état local à une vue SwiftUI générée par #Preview. Apple indique que les déclarations annotées deviennent des propriétés de cette vue générée. Placez la déclaration à la racine de la fermeture de l’aperçu ; utiliser @Previewable en dehors du corps d’un aperçu provoque une erreur. Previewable().
La chaîne de propriété obtenue est explicite : le conteneur d’aperçu généré stocke le modèle dans @State, LabelEditor reçoit la référence au modèle et @Bindable expose ses propriétés modifiables. SwiftUI permet de stocker un modèle Observation dans l’état et de transmettre cette référence aux vues enfants. State.
Considérez les valeurs de l’initialiseur comme des données de départ. Un aperçu interactif peut les modifier pendant son exécution. Pour reproduire une mise en page précise, revenez à une nouvelle instance de l’aperçu avant d’en comparer l’apparence initiale.
Vérifier le comportement à l’aide d’une petite matrice de scénarios
Ouvrez LabelEditor.swift, choisissez Show Canvas et sélectionnez un aperçu nommé dans les onglets du canevas. Si le canevas est en pause, choisissez Restart. Activez le mode Live pour tester la saisie de texte et la logique des contrôles. Le guide actuel d’Apple sur le canevas décrit ces commandes. Interagir avec les aperçus dans le canevas.
Utilisez les vérifications suivantes pour distinguer les problèmes d’état des problèmes de mise en page. Les résultats attendus découlent du code d’exemple ; ils ne sont pas présentés comme des résultats de test.
| Scénario | Action | Résultat attendu |
|---|---|---|
| Étiquette standard | Remplacer le titre par Amber circle |
Le titre affiché change immédiatement |
| Affichage du détail | Désactiver Show detail, puis le réactiver |
Le détail disparaît, puis réapparaît avec son texte conservé |
| Titre vide | Supprimer tous les caractères du titre | Le titre affiché devient Untitled label |
| Titre composé d’espaces | Saisir uniquement des espaces | Le champ conserve ces espaces ; le titre affiché utilise la valeur de remplacement |
| Éditeur étroit | Examiner l’aperçu avec un titre long | Le titre affiché revient à la ligne dans la carte |
| Données distinctes | Modifier un aperçu, puis examiner un autre aperçu neuf | L’autre aperçu démarre avec les valeurs déclarées pour son propre modèle |
Si la saisie fonctionne, mais que le résultat affiché ne change pas, vérifiez la liaison entre le champ et la propriété lue par le résultat. Ici, les deux chemins mènent à model.title, directement ou par l’intermédiaire de displayedTitle. Si l’activation ou la désactivation de la visibilité efface le détail, recherchez du code qui lui attribue une valeur vide ; cet exemple modifie uniquement showsDetail.
L’environnement de recherche ne disposait ni d’un compilateur Swift ni d’un environnement d’exécution des aperçus macOS ; les exemples ont donc été confrontés aux API documentées, sans être compilés ni exécutés. Construisez la cible et effectuez les vérifications d’interaction dans Xcode avant d’adapter l’éditeur.
Séparer la configuration partagée des données modifiables
Pour un modèle aussi réduit, une construction locale rend chaque aperçu facile à comprendre. Si plusieurs aperçus ont besoin d’une configuration réutilisable et coûteuse, Apple fournit PreviewModifier : son contexte partagé est mis en cache et réutilisé par les aperçus participants. Cette API nécessite macOS 15 ou une version ultérieure. PreviewModifier.
Ce comportement de partage documenté conduit à un choix de conception pratique : gardez les modèles de données modifiables indépendamment en local et réservez le contexte partagé à la configuration que vous souhaitez délibérément réutiliser dans les aperçus participants. Un modèle mutable partagé partagerait aussi ses modifications, ce qui peut compliquer la comparaison de scénarios distincts.
Si une vue existante récupère son modèle par type depuis l’environnement, injectez l’instance requise à la racine de l’aperçu. Apple indique qu’un modèle non facultatif du type demandé, s’il est absent, provoque une exception. Un aperçu doit donc fournir les dépendances effectivement lues par la racine choisie. Gérer les données de modèle dans votre app.
Préserver les frontières entre AppKit et TextKit
L’éditeur ci-dessus utilise des contrôles SwiftUI. Si votre éditeur macOS existant contient une vue de texte AppKit, utilisez NSViewRepresentable pour intégrer cette vue à la hiérarchie SwiftUI. Créez-la et configurez-la avec makeNSView, puis appliquez l’état SwiftUI entrant avec updateNSView. Transmettez les modifications du délégué AppKit par l’intermédiaire d’un coordinateur lorsque les modifications doivent revenir vers SwiftUI. Laissez SwiftUI contrôler le cadre et les limites de la vue représentée. NSViewRepresentable.
Apple prend également en charge l’aperçu direct des vues et des contrôleurs de vue AppKit. Cependant, @Previewable est propre à SwiftUI et ne s’applique pas aux aperçus AppKit directs. Pour utiliser le modèle d’état local de cet article avec du contenu AppKit, prévisualisez un conteneur SwiftUI qui comprend la vue représentable. Ajouter des aperçus à vos fichiers d’interface.
Conservez le moteur de texte choisi lors de la création d’un NSTextView. Apple documente NSTextView(usingTextLayoutManager: true) pour sélectionner TextKit 2 à l’initialisation. Ses conseils sur TextKit expliquent également que l’accès à layoutManager de TextKit 1 peut entraîner un retour irréversible pour cette instance de vue de texte. Si vous souhaitez utiliser TextKit 2, choisissez les API appropriées ; un aperçu ne supprime pas ces contraintes. Nouveautés de TextKit et des vues de texte.
Diagnostiquer les échecs d’aperçu à la frontière de propriété de l’état
Commencez par le premier diagnostic de compilation. Les macros Swift génèrent du code lors de la compilation ; une erreur mise en évidence sur un appel de macro peut donc concerner des déclarations générées. La commande Editor > Expand Macro de Xcode permet d’examiner ce code. Appliquer des macros.
Vérifiez ensuite le mécanisme d’état. Un ObservableObject existant fondé sur Combine nécessite le modèle de propriété correspondant : Apple avertit que le stockage d’un tel objet dans State ne suit pas les changements de ses propriétés publiées et recommande aux propriétaires d’utiliser StateObject. L’exemple utilise @Observable ; la propriété de son état suit donc le modèle Observation. State.
Veillez à ce que le fichier d’aperçu final mette en évidence les valeurs de départ explicites et les interactions visibles. Une fois que les scénarios standard, de titre long et de titre vide fonctionnent comme prévu, réutilisez le même éditeur avec un modèle détenu par la vue parente appropriée de votre app. Les données d’aperçu restent un moyen compact de reproduire ces conditions d’interface.
Sources
- Ajouter des aperçus à vos fichiers d’interface — Apple
- Notes de version de Xcode 16 — Apple
- Gérer les données de modèle dans votre app — Apple
- Previewable() — Apple
- Bindable — Apple
- State — Apple
- State() — Apple
- Interagir avec les aperçus dans le canevas — Apple
- PreviewModifier — Apple
- NSViewRepresentable — Apple
- Nouveautés de TextKit et des vues de texte — Apple
- Appliquer des macros — Apple