Verwende sheet(item:onDismiss:content:) mit einem einzigen optionalen ausgewählten Element und erstelle die Sheet aus dem Element, das ihre Closure erhält. So bestimmt ein einzelner Wert in einer Mac-App sowohl, ob eine Sheet angezeigt wird, als auch, was sie anzeigt.
Dieses Tutorial implementiert eine kleine Desktop-Aufgabe: Klicke auf ein lokales Beispielelement und sieh dir sofort seine Details an. Außerdem bietet es eine Diagnoseabfolge für eine Sheet, die leer geöffnet wird, die vorherige Auswahl zeigt oder veralteten Text in einer AppKit-Ansicht anzeigt.
Warum eine elementbasierte Sheet verwenden?
SwiftUI bietet zwei relevante Präsentationsmethoden. Die Boolesche Überladung zeigt die Sheet an, wenn der gebundene Wert auf true gesetzt wird. Die Element-Überladung akzeptiert einen optionalen identifizierbaren Wert und übergibt diesen Wert an die Content-Closure. Wähle die boolesche Form für ein festes Panel und die elementbasierte Form, wenn der Inhalt des Panels von einer Auswahl abhängt. Siehe Apples Dokumentation zu booleschen Sheets und elementbasierten Sheets.
Ein Entwurf mit separaten Eigenschaften für Auswahl und Präsentation lässt Kombinationen zu, etwa eine angeforderte Präsentation ohne Auswahl. Wenn du sie zu einem einzigen optionalen Wert zusammenfasst, entfällt diese Kombination im eigenen Zustandsmodell: nil steht für kein angefordertes Element, und ein gesetzter Wert steht für das zu untersuchende Element. Dies ist eine aus den beiden APIs abgeleitete Entwurfsentscheidung und keine Behauptung über einen undokumentierten SwiftUI-Fehler bei der Ausführungsplanung.
Wenn eine vorhandene Implementierung eine Auswahl zuweist und anschließend einen booleschen Wert umschaltet, erklären diese beiden Anweisungen allein nicht, warum die erste Präsentation fehlschlägt. Untersuche den Datenpfad des Inhalts, bevor du das Symptom dem Timing zuschreibst. Entscheidend ist, ob die Sheet ihre übergebene Eingabe rendert, eine andere Eigenschaft ausliest oder eine separat gespeicherte Kopie anzeigt.
Ein vollständiges macOS-Beispiel erstellen
Erstelle in Xcode ein macOS-App-Projekt mit der SwiftUI-Oberfläche. Verwende für dieses Beispiel ein Deployment-Target ab macOS 12. Die API für elementbasierte Sheets gibt es ab macOS 10.15, während die unten verwendete Umgebungsaktion zum Schließen macOS 12 voraussetzt. Apple dokumentiert diese Verfügbarkeitsgrenzen für elementbasierte Sheets und den Umgebungswert für das Schließen.
Ersetze die generierte App-Deklaration und die Inhaltsansicht durch dieses vollständige Programm. Lass im Target nur einen App-Einstiegspunkt. Alle Beschriftungen und Beschreibungen unten sind synthetische lokale Beispieldaten.
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)
}
}
Die Schaltfläche weist presentedItem den angeklickten Wert zu. Die Präsentations-Closure übergibt dann ihren eigenen Parameter item an ItemDetailView. Beachte, dass die Detailansicht eine einfache Eingabeeigenschaft let item statt einer zweiten Zustandsvariable für das ausgewählte Element hat. Apple empfiehlt, eine einzige Quelle der Wahrheit zu bewahren und schreibgeschützte Werte über Eigenschaften weiterzugeben. Bindings kommen zum Einsatz, wenn ein untergeordnetes Element Schreibzugriff benötigt. Siehe State.
Die Aktion zum Schließen gehört in ItemDetailView, wo sie die Umgebung der Sheet ausliest. Apple weist ausdrücklich darauf hin, dass das Auslesen in der präsentierenden Ansicht und der Aufruf dieser Aktion aus dem Inhalt der Sheet die Umgebung der präsentierenden Ansicht verwendet. Platziere die Deklaration wie gezeigt neben dem Inhalt der Sheet. Siehe dismiss.
Das Programm wurde auf konsistente Eingaben und Referenzen geprüft, hier jedoch nicht kompiliert oder ausgeführt, da in dieser Umgebung keine Swift-Toolchain verfügbar ist. Führe die folgenden Prüfungen in Xcode mit deiner Zielversion von macOS durch.
Die erste Präsentation mit einer gezielten Klickfolge überprüfen
Beginne jede Prüfung der ersten Präsentation mit einem frischen Start. Tests, bei denen zuvor ein anderes Element geöffnet wurde, können das ursprüngliche Symptom verdecken. Die folgenden Erwartungen ergeben sich aus den expliziten Beschriftungen und Datenzuweisungen des Beispiels. Sie sind Abnahmekriterien, keine berichteten Testergebnisse.
| Aktion | Erwarteter Inhalt der Sheet | Was geprüft wird |
|---|---|---|
Starte die App und klicke zuerst auf Inspect Blue Tile |
Blue Tile, die rechteckige Beschreibung und tile-b |
Die erste Präsentation verwendet das angeklickte Element |
Klicke auf Done und sieh dir dann Amber Tile an |
Amber Tile, die quadratische Beschreibung und tile-a |
Eine spätere Auswahl wird an den Inhalt übergeben |
Schließe die Sheet und sieh dir Amber Tile erneut an |
Dieselben Details zu Amber Tile | Das erneute Öffnen eines Elements bleibt konsistent |
Starte die App neu und sieh dir zuerst Amber Tile an |
Sofort die Details zu Amber Tile | Das Ergebnis hängt nicht davon ab, welches Beispiel zuerst geöffnet wird |
Wenn du sowohl einen Titel als auch eine ID anzeigst, ist diese Prüfung aussagekräftiger als eine Prüfung nur anhand des Titels. Wenn deine echte Oberfläche doppelte Anzeigenamen zulässt, verwende während der Entwicklung zwei synthetische Elemente mit demselben Titel und unterschiedlichen IDs und Beschreibungen. So werden versehentliche Auswahlen anhand des Anzeigetexts sichtbar.
Veraltete Inhalte von der Schaltfläche bis zur endgültigen Ansicht untersuchen
Arbeite diese Prüfungen der Reihe nach durch. Jeder Schritt grenzt die Stelle der Abweichung weiter ein, sodass du die verantwortliche Ebene ändern kannst, ohne sachfremde Präsentations-Workarounds hinzuzufügen.
- Eingabe der Aktion prüfen. Setze einen Breakpoint in der Aktion der angeklickten Schaltfläche und untersuche
item.id. Vergewissere dich, dass die Aktion den gewünschten Datensatz erhält. - Präsentationseingabe prüfen. Untersuche den Parameter
iteminnerhalb der Sheet-Closure. Übergib diesen Parameter an die Detailansicht, statt erneut eine separate Auswahl abzufragen. - Angezeigte Eigenschaft prüfen. Vergewissere dich, dass Titel und Beschreibung aus der Eingabe der Detailansicht stammen. Achte auf eine zwischengespeicherte Eigenschaft oder einen lokalen Entwurf mit einem älteren Wert.
- Eine mögliche AppKit-Brücke prüfen. Wenn gewöhnlicher SwiftUI-Text korrekt ist, umgebrochener Text aber veraltet bleibt, untersuche die Aktualisierungsmethode des Representable.
Identität stabil halten
Die elementbasierte API erfordert Identifiable. Swift definiert dieses Protokoll anhand stabiler Identität und überlässt Dauer und Geltungsbereich dem konformen Typ und seiner Verwendung. Das Beispiel verwendet gespeicherte String-IDs, die die beiden Datensätze unterscheiden. Siehe Identifiable.
Wähle für eine echte Liste eine Kennung, die denselben Datensatz über die Aktualisierungen hinweg identifiziert, die deine Oberfläche unterstützt. Vermeide eine Ableitung der Identität aus einem Titel, der sich ändern oder von mehreren Elementen verwendet werden kann. Vermeide außerdem eine berechnete Eigenschaft id, die bei jedem Abruf eine neue UUID erzeugt; das würde keine stabile Identität liefern.
Anzeigeeingabe von einem bearbeitbaren Entwurf trennen
Ein Inspektor kann item.title direkt anzeigen. Ein Editor kann absichtlich einen lokalen Entwurf erstellen, doch damit kommt ein weiterer Wert hinzu, dessen Lebensdauer festgelegt werden muss. Entscheide, wann er erstellt wird, ob spätere Modelländerungen ihn ersetzen sollen und was passiert, wenn der Benutzer fertig ist oder abbricht.
Apple erklärt, dass SwiftUI den Zustandsspeicher für die Identität einer Ansicht beibehält und ihn zu Beginn der Lebensdauer dieser Identität initialisiert. Das Erstellen eines neuen Ansichtsobjekts erzeugt nicht automatisch neuen Zustand. Daher ist die Initialisierung eines lokalen Zustands mit einer Eingabe kein allgemeiner Mechanismus, um diesen Zustand mit späteren Eingaben synchron zu halten. Siehe Demystify SwiftUI.
Für diese schreibgeschützte Aufgabe genügt die einfache Eingabeeigenschaft. Wenn du sie um Bearbeitungsmöglichkeiten erweiterst, entscheide dich für ein explizites Entwurfs- und Übernahmemodell oder ein Binding auf den vorgesehenen Modellwert. Ändere die Ansichtsidentität nicht allein deshalb, um den Text zu aktualisieren: Eine Änderung der Identität ersetzt den zugehörigen Zustand und kann dadurch einen unfertigen Entwurf verwerfen. Diese Folge ergibt sich aus Apples dokumentiertem Zustandsmodell.
Was ändert sich, wenn die Sheet eine AppKit-Textansicht enthält?
Eine Sheet mit einem NSTextView benötigt weiterhin denselben Datenpfad für das ausgewählte Element. Außerdem braucht sie eine korrekt implementierte Brücke. Apples Protokoll NSViewRepresentable unterscheidet zwischen der anfänglichen Erstellung in makeNSView(context:) und der anschließenden Konfiguration in updateNSView(_:context:). Ein Wrapper, der seinen Text nur bei der Erstellung zuweist, kann spätere Texteingaben nicht übernehmen. Siehe NSViewRepresentable.
Initialisiere bei einem schreibgeschützten Wrapper die native Ansicht mit dem übergebenen Text. Sorge dafür, dass die Aktualisierungsmethode den angezeigten Text bei Bedarf mit der aktuellen Eingabe in Einklang bringt. Apple dokumentiert, dass SwiftUI die Aktualisierungsmethode bei Änderungen aufruft, die sich auf die entsprechende AppKit-Ansicht auswirken. Siehe updateNSView(_:context:).
Ein bearbeitbarer Wrapper benötigt eine zusätzliche Richtlinie: Er darf seinen Text nicht bei jeder Aktualisierung blind ersetzen und dadurch laufende Bearbeitungen überschreiben. Verwende einen Coordinator, um Änderungen nativer Delegates oder Target-Action-Mechanismen an SwiftUI zu übermitteln, wie Apple es in der Representable-Dokumentation beschreibt. Überlass SwiftUI die Steuerung von Frame und Bounds der dargestellten Ansicht. Apple warnt davor, diese Layout-Eigenschaften direkt zu setzen.
TextKit kümmert sich um Textspeicherung, Layout und Rendering. Apple dokumentiert sowohl den modernen NSTextLayoutManager-Pfad als auch den älteren NSLayoutManager-Pfad, die über NSTextView verfügbar sind. Diese Funktionen liefern nicht den ausgewählten Datensatz der Sheet. Vergewissere dich zuerst, dass die Brücke den Text des richtigen Elements erhält, bevor du das Text-Layout untersuchst. Prüfe die Verfügbarkeit separat, bevor du weitere TextKit-APIs einführst. Siehe TextKit.
Das Präsentationsmodell passend zur Aufgabe wählen
Verwende die boolesche Überladung, wenn der Inhalt feststeht und sich nur die Sichtbarkeit ändert. Verwende für diese Inspektor-Aufgabe die Element-Überladung. Apple dokumentiert, dass eine Änderung des übergebenen Elements eine vorhandene Sheet schließen und ersetzen kann. Interpretiere den Austausch des Elements daher nicht als Zusicherung, dass eine Bearbeitung innerhalb derselben Präsentation unterbrechungsfrei fortgesetzt wird. Siehe sheet(item:onDismiss:content:).
Damit bei der ersten Präsentation das richtige Element angezeigt wird, muss sich der ausgewählte Datensatz einfach nachverfolgen lassen: eine Zuweisung in der Schaltflächenaktion, ein Elementparameter in der Sheet-Closure und eine Eingabe in der Detailansicht. Überprüfe beide Beispieldatensätze nach einem frischen Start. Weicht das Ergebnis weiterhin ab, zeigt die Diagnoseabfolge, ob die Abweichung bei der Auswahl, im beibehaltenen Zustand oder im Aktualisierungspfad der nativen Ansicht entsteht.
Quellen
- sheet(item:onDismiss:content:) — Apple
- sheet(isPresented:onDismiss:content:) — Apple
- State — Apple
- dismiss — Apple
- Identifiable — Apple
- Demystify SwiftUI — Apple
- NSViewRepresentable — Apple
- updateNSView(_:context:) — Apple
- TextKit — Apple