Utilisez sheet(item:onDismiss:content:) avec un seul élément sélectionné facultatif, et construisez la feuille à partir de l’élément reçu par sa closure. Cela donne à une app Mac une seule valeur qui détermine à la fois si une feuille doit être présentée et ce qu’elle affiche.

Ce tutoriel met en œuvre une petite tâche de bureau : cliquer sur un élément d’exemple local pour en examiner immédiatement les détails. Il propose également une séquence de diagnostic pour une feuille qui s’ouvre vide, affiche la sélection précédente ou présente un texte obsolète dans une vue AppKit.

Pourquoi utiliser une feuille basée sur un élément ?

SwiftUI propose deux méthodes de présentation pertinentes. La surcharge avec booléen présente la feuille lorsque sa liaison passe à true. La surcharge avec élément accepte une valeur identifiable facultative et fournit cette valeur à la closure de contenu. Choisissez la forme avec booléen pour un panneau fixe ; choisissez celle avec élément lorsque le contenu du panneau dépend d’une sélection. Consultez la documentation Apple sur les feuilles basées sur un booléen et les feuilles basées sur un élément.

Une conception avec des propriétés distinctes pour la sélection et la présentation autorise des combinaisons comme une présentation demandée sans sélection. Les réunir dans une seule valeur facultative élimine cette combinaison de votre propre modèle d’état : nil représente l’absence d’élément demandé, et une valeur renseignée représente l’élément à examiner. Il s’agit d’une déduction de conception à partir des deux API, et non d’une affirmation concernant un défaut de planification non documenté de SwiftUI.

Si une implémentation existante définit une sélection puis bascule un booléen, ces deux instructions ne suffisent pas à établir pourquoi la première présentation échoue. Examinez le chemin des données du contenu avant d’attribuer le symptôme au minutage. La question utile est de savoir si la feuille affiche son entrée reçue, lit une autre propriété ou présente une copie stockée séparément.

Créer un exemple macOS complet

Créez dans Xcode un projet d’app macOS utilisant l’interface SwiftUI. Pour cet exemple, définissez la cible de déploiement sur macOS 12 ou une version ultérieure. L’API de feuille basée sur un élément date de macOS 10.15, tandis que l’action d’environnement de fermeture utilisée ci-dessous nécessite macOS 12. Apple documente ces limites de disponibilité pour les feuilles basées sur un élément et la valeur d’environnement de fermeture.

Remplacez la déclaration d’app générée et la vue de contenu par ce programme complet. Ne conservez qu’un seul point d’entrée d’app dans la cible. Tous les libellés et descriptions ci-dessous sont des données d’exemple locales synthétiques.

import SwiftUI

struct SampleItem: Identifiable {
    let id: String
    let title: String
    let detail: String
}

@main
struct FirstSheetDemoApp: App {
    var body: some Scene {
        WindowGroup {
            ItemListView()
        }
    }
}

struct ItemListView: View {
    private let items = [
        SampleItem(
            id: "tile-a",
            title: "Amber Tile",
            detail: "A square sample with an amber finish."
        ),
        SampleItem(
            id: "tile-b",
            title: "Blue Tile",
            detail: "A rectangular sample with a blue finish."
        )
    ]

    @State private var presentedItem: SampleItem? = nil

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Sample Items")
                .font(.title2)

            ForEach(items) { item in
                Button("Inspect \(item.title)") {
                    presentedItem = item
                }
            }
        }
        .padding(24)
        .frame(minWidth: 360, minHeight: 200)
        .sheet(item: $presentedItem) { item in
            ItemDetailView(item: item)
        }
    }
}

struct ItemDetailView: View {
    let item: SampleItem
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text(item.title)
                .font(.title)

            Text(item.detail)

            Text("Item ID: \(item.id)")
                .font(.caption)

            HStack {
                Spacer()
                Button("Done") {
                    dismiss()
                }
            }
        }
        .padding(24)
        .frame(width: 380, height: 220)
    }
}

Le bouton affecte la valeur cliquée à presentedItem. La closure de présentation transmet ensuite son propre paramètre item à ItemDetailView. Remarquez que la vue de détail possède une simple propriété d’entrée, let item, et non une seconde propriété d’état pour l’élément sélectionné. Apple recommande de conserver une source unique de vérité et de partager les valeurs en lecture seule au moyen de propriétés, en utilisant des liaisons lorsqu’un enfant doit pouvoir écrire. Consultez State.

L’action de fermeture doit se trouver dans ItemDetailView, où elle lit l’environnement de la feuille. Apple avertit explicitement que la lire dans la vue qui présente la feuille et invoquer cette action depuis son contenu utilise l’environnement de la vue présentatrice. Comme indiqué, gardez la déclaration à côté du contenu propre à la feuille. Consultez dismiss.

La cohérence des entrées et des références du programme a été vérifiée ; il n’a pas été compilé ni exécuté ici, car aucun outil de compilation Swift n’est disponible dans cet environnement. Effectuez les vérifications suivantes dans Xcode sur votre version cible de macOS.

Vérifier la première présentation avec une séquence de clics précise

Commencez chaque vérification de première présentation après un nouveau lancement. Tester seulement après avoir ouvert un autre élément peut masquer le symptôme d’origine. Les résultats attendus ci-dessous découlent des libellés et affectations de données explicites de l’exemple ; ce sont des critères d’acceptation, et non des résultats de test rapportés.

Action Contenu attendu de la feuille Ce qui est vérifié
Lancer l’app et cliquer d’abord sur Inspect Blue Tile Blue Tile, sa description rectangulaire et tile-b La première présentation utilise l’élément cliqué
Cliquer sur Done, puis examiner Amber Tile Amber Tile, sa description carrée et tile-a Une sélection ultérieure est transmise au contenu
Fermer la feuille et examiner de nouveau Amber Tile Les mêmes détails ambrés La réouverture d’un élément reste cohérente
Relancer l’app et examiner Amber Tile en premier Les détails ambrés immédiatement Le résultat correct ne dépend pas de l’ordre d’ouverture des exemples

Afficher à la fois un titre et un identifiant rend cette vérification plus utile que de vérifier uniquement le titre. Si votre interface réelle autorise les noms d’affichage en double, utilisez pendant le développement deux éléments synthétiques ayant le même titre, mais des identifiants et descriptions différents. Cela révèle les sélections accidentelles fondées sur le texte affiché.

Diagnostiquer un contenu obsolète, du bouton à la vue finale

Effectuez ces vérifications dans l’ordre. Chaque étape permet de localiser plus précisément l’écart, afin que vous puissiez modifier la couche responsable sans ajouter de contournements de présentation sans rapport.

  1. Vérifiez l’entrée de l’action. Placez un point d’arrêt dans l’action du bouton cliqué et examinez item.id. Vérifiez que l’action reçoit l’enregistrement attendu.
  2. Vérifiez l’entrée de présentation. Examinez le paramètre item dans la closure de la feuille. Transmettez ce paramètre à la vue de détail au lieu de consulter à nouveau une sélection distincte.
  3. Vérifiez la propriété affichée en dernier. Assurez-vous que le titre et la description proviennent de l’entrée de la vue de détail. Recherchez une propriété mise en cache ou un brouillon local contenant une valeur antérieure.
  4. Vérifiez tout pont AppKit. Si le texte SwiftUI ordinaire est correct, mais que le texte enveloppé est obsolète, examinez la méthode de mise à jour du representable.

Conserver une identité stable

L’API basée sur un élément exige Identifiable. Swift définit ce protocole autour d’une identité stable et laisse sa durée et sa portée au type conforme et à son consommateur. L’exemple utilise des identifiants chaîne stockés qui distinguent les deux enregistrements. Consultez Identifiable.

Pour une vraie liste, choisissez un identifiant qui continue à désigner le même enregistrement au fil des mises à jour prises en charge par votre interface. Évitez de dériver l’identité d’un titre susceptible de changer ou d’être partagé par plusieurs éléments. Évitez aussi un id calculé qui crée un nouveau UUID à chaque lecture ; cela ne fournirait pas une identité stable.

Distinguer l’entrée d’affichage d’un brouillon modifiable

Un inspecteur peut afficher directement item.title. Un éditeur peut créer intentionnellement un brouillon local, mais ce brouillon ajoute une autre valeur dont la durée de vie doit être définie. Décidez quand il est créé, si des modifications ultérieures du modèle doivent le remplacer et ce qui se passe lorsque l’utilisateur termine ou annule.

Apple explique que SwiftUI conserve le stockage de l’état en fonction de l’identité d’une vue et l’initialise au début de la durée de vie de cette identité. La recréation d’une valeur de vue ne crée donc pas nécessairement un nouvel état. Par conséquent, initialiser un état local à partir d’une entrée n’est pas un mécanisme général pour synchroniser cet état avec les entrées ultérieures. Consultez Demystify SwiftUI.

Pour cette tâche en lecture seule, conservez la simple propriété d’entrée. Si vous ajoutez une fonction de modification, choisissez explicitement une conception avec brouillon et validation ou une liaison vers la valeur du modèle concernée. Évitez de modifier l’identité d’une vue dans le seul but d’actualiser le texte : changer l’identité remplace l’état associé, ce qui peut supprimer un brouillon inachevé. Cette conséquence découle du modèle de durée de vie de l’état documenté par Apple.

Que change la présence d’une vue de texte AppKit dans la feuille ?

Une feuille contenant un NSTextView a toujours besoin du même chemin de données pour l’élément sélectionné. Elle nécessite également un pont correctement implémenté. Le protocole NSViewRepresentable d’Apple distingue la création initiale dans makeNSView(context:) de la configuration ultérieure dans updateNSView(_:context:). Un wrapper qui n’affecte son texte qu’au moment de la création ne contient aucune implémentation permettant d’appliquer les entrées textuelles ultérieures. Consultez NSViewRepresentable.

Pour un wrapper en lecture seule, initialisez la vue native avec le texte fourni et faites en sorte que sa méthode de mise à jour synchronise le texte affiché avec l’entrée actuelle, si nécessaire. Apple indique que SwiftUI appelle la méthode de mise à jour lorsque des changements affectent la vue AppKit correspondante. Consultez updateNSView(_:context:).

Un wrapper modifiable nécessite une règle supplémentaire : ne remplacez pas aveuglément son texte à chaque mise à jour au risque d’écraser des modifications en cours. Utilisez un coordinateur pour transmettre à SwiftUI les changements apportés par le délégué natif ou par les actions de cible, comme le décrit Apple dans la documentation du representable. Laissez SwiftUI contrôler le cadre et les limites de la vue représentée ; Apple déconseille de définir directement ces propriétés de mise en page.

TextKit gère le stockage, la mise en page et le rendu du texte. Apple documente à la fois le parcours moderne NSTextLayoutManager et l’ancien parcours NSLayoutManager exposés par NSTextView. Ces fonctionnalités ne fournissent pas l’enregistrement sélectionné dans la feuille. Assurez-vous que le pont reçoit le texte du bon élément avant d’examiner la mise en page du texte, et vérifiez séparément la disponibilité avant d’introduire d’autres API TextKit. Consultez TextKit.

Choisir le modèle de présentation adapté à la tâche

Utilisez la surcharge avec booléen lorsque le contenu est fixe et que seule sa visibilité change. Utilisez la surcharge avec élément pour cette tâche d’inspection. Apple indique qu’une modification de l’élément fourni peut fermer une feuille existante et la remplacer ; ne considérez donc pas le remplacement de l’élément comme une garantie de poursuivre la modification sans interruption dans la même présentation. Consultez sheet(item:onDismiss:content:).

Pour garantir un affichage correct dès la première présentation, facilitez le suivi de l’enregistrement sélectionné : une affectation dans l’action du bouton, un paramètre d’élément dans la closure de la feuille et une entrée dans la vue de détail. Vérifiez les deux éléments d’exemple après un nouveau lancement. Si le résultat diffère encore, la séquence de diagnostic permet de déterminer si l’écart provient de la sélection, de l’état conservé ou du parcours de mise à jour de la vue native.

Sources