Houd de presentatiehost identiek en de presentatiebinding actief om een SwiftUI-sheet open te houden tijdens updates. Koppel de sheet aan een stabiele container, zodat veranderende onderliggende inhoud kan worden bijgewerkt zonder de presentator te vervangen.

Deze gids is bedoeld voor ontwikkelaars die een Mac-app bouwen met een bewerkbare sheet die zichtbaar moet blijven terwijl de onderliggende interface verandert. Het uitgewerkte voorbeeld gebruikt synthetische lokale tekst en behandelt drie dingen die je onafhankelijk van elkaar kunt inspecteren: de presentatiestatus, de levensduur van de presenterende weergave en de inhoud van de editor.

Waarom kan een update van een weergave een sheet laten verdwijnen?

SwiftUI kan nieuwe weergavewaarden maken tijdens het evalueren van body, terwijl de identiteit van de weergegeven weergave behouden blijft. De status blijft bestaan zolang die identiteit leeft. Als de weergave wordt verwijderd of de identiteit verandert, eindigt die levensduur; ook wisselen tussen voorwaardelijke vertakkingen kan afzonderlijke identiteiten opleveren, zelfs wanneer beide vertakkingen hetzelfde weergavetype bevatten. Apple legt deze relaties uit in Demystify SwiftUI.

Bij een sheet die door een Booleaanse waarde wordt aangestuurd, is de gedocumenteerde presentatieregeling de binding die aan sheet(isPresented:onDismiss:content:) wordt doorgegeven: een waarde van true vraagt om presentatie. Controleer bij het onderzoeken van een verdwijnende sheet zowel die binding als het voortbestaan van de weergave met de modifier. Een true-waarde die ergens anders is opgeslagen, bewijst niet dat de oorspronkelijke presentatiehost behouden is gebleven. Zie Apples documentatie over sheets met een Booleaanse waarde.

Een bijzonder directe oorzaak is een veranderende expliciete identifier. Apple documenteert dat een wijziging van de waarde die aan id(_:) wordt doorgegeven, de identiteit van de weergave opnieuw instelt, inclusief de status ervan. Controleer de presenterende weergave en de voorouders ervan op identifiers die zijn afgeleid van revisietellers, bewerkbare tekst of nieuw gegenereerde waarden. Verwijder zulke identifiers als het resetgedrag niet de bedoeling is. Zie id(_:).

De praktische ontwerpconclusie is om de sheetmodifier buiten het veranderende deel van de interface te plaatsen. Als een laadweergave, een gefilterde rij of een alternatieve detailindeling kan verdwijnen, is de dichtstbijzijnde geschikte blijvende voorouder doorgaans een betere eigenaar van de presentatie.

Maak een stabiele eigenaar voor de sheet met een compleet Mac-voorbeeld

Maak in Xcode een macOS SwiftUI-app, stel het implementatiedoel in op macOS 12 of later en vervang de gegenereerde app- en contentview-declaraties door het volgende bestand. Behoud slechts één @main-appdeclaratie. De ondergrens van macOS 12 is nodig voor de omgevingsactie dismiss in het voorbeeld; TextEditor is zelf beschikbaar vanaf macOS 11. Zie Apples documentatie over dismiss en documentatie over TextEditor.

De hoofdweergave van het venster beheert de presentatiestatus, bewerkbare tekst, revisieteller en indelingsstatus. De buitenste VStack bevat de sheetmodifier. Alleen de inhoud binnen die container wisselt tussen vertakkingen. Apple raadt aan status privé te declareren in de hoogste weergave die deze nodig heeft en bindings door te geven aan onderliggende weergaven die de status moeten wijzigen. Zie State.

import SwiftUI

@main
struct StableSheetDemoApp: App {
    var body: some Scene {
        WindowGroup {
            SheetOwnerView()
        }
    }
}

struct SheetOwnerView: View {
    @State private var isEditorPresented = false
    @State private var note = "Sample note Alpha"
    @State private var revision = 0
    @State private var compactLayout = false

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

            if compactLayout {
                Text("Compact summary: revision \(revision)")
            } else {
                VStack(alignment: .leading, spacing: 8) {
                    Text("Expanded summary")
                    Text("Revision \(revision)")
                    Text(note)
                }
            }

            Button("Edit Note") {
                isEditorPresented = true
            }
        }
        .padding(24)
        .frame(minWidth: 440, minHeight: 260)
        .sheet(isPresented: $isEditorPresented) {
            NoteEditorSheet(
                note: $note,
                revision: $revision,
                compactLayout: $compactLayout
            )
        }
    }
}

struct NoteEditorSheet: View {
    @Binding var note: String
    @Binding var revision: Int
    @Binding var compactLayout: Bool
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Edit Sample Note")
                .font(.headline)

            TextEditor(text: $note)
                .frame(minHeight: 160)

            Text("Owner revision: \(revision)")

            Button("Update Owner") {
                revision += 1
                compactLayout.toggle()
            }

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

Dit is een complete demonstratie-app die is gecontroleerd aan de hand van de gedocumenteerde API's. De app is hier niet gecompileerd of uitgevoerd, omdat deze omgeving geen Swift-toolchain of macOS-runtime heeft.

Voer de update uit zonder de sheet te sluiten

  1. Start de app en klik op Edit Note.
  2. Vervang de voorbeeldtekst door Sample note Beta.
  3. Klik meerdere keren op Update Owner. Elke klik verhoogt de status van de eigenaar en schakelt tussen de voorwaardelijke inhoud van de eigenaar.
  4. Controleer of de editor zichtbaar blijft, de tekst Sample note Beta blijft staan en de revisieweergave verder telt.
  5. Klik op Done. Controleer of het venster de bewerkte tekst toont in de uitgevouwen indeling wanneer die indeling actief is.
  6. Open de editor opnieuw en controleer of de bewerkte tekst beschikbaar blijft.

De updateknop staat in de sheet, zodat de test niet afhankelijk is van klikken op het onderliggende venster. AppKit documenteert sheets als documentmodaal: zolang een sheet zichtbaar is, zijn de meeste gebeurtenissen die naar het bovenliggende venster zijn gericht niet toegestaan. Door de eigenaar via een binding bij te werken, kan dit voorbeeld de statuswijziging rechtstreeks uitvoeren. Zie beginSheet(_:completionHandler:).

Bepaal wat het sluiten van de editor moet betekenen

In dit voorbeeld wordt de tekst van de eigenaar direct bewerkt. Done sluit de editor; er wordt geen afzonderlijke opslagactie uitgevoerd. De tekenreeks blijft in de lokale status van het venster en het voorbeeld biedt geen persistentie tussen appstarts. Dit gedrag volgt uit het gedeelde bindingsontwerp dat wordt beschreven in State.

Als je taak een concept vereist dat kan worden geannuleerd, gebruik dan een afzonderlijke conceptwaarde en kopieer die alleen naar het document na een expliciete bevestigingsactie. Bepaal dat gedrag voordat je het sluiten implementeert: beslissen of je een concept vastlegt of verwerpt, staat los van de keuze welke weergave eigenaar is van de sheet.

Onderzoek het sluiten met drie onafhankelijke controles

Gebruik de volgende volgorde in je eigen app. Zo pas je de algemene identiteitsregels toe op een gericht onderzoek, zonder ervan uit te gaan dat elke verdwijnende sheet dezelfde oorzaak heeft.

Controle Wat je inspecteert Praktische correctie
Presentatiewaarde Elke toewijzing die de Booleaanse waarde instelt op false of het optionele presentatie-item wist Beperk toewijzingen die sluiten tot bewuste sluitacties
Presenteridentiteit Voorwaardelijke vertakkingen, verwijderde rijen, veranderende identifiers van voorouders Verplaats zowel de presentatiestatus als de modifier naar een geschikte stabiele voorouder
Levensduur van de inhoud Status van de sheet, veranderende inhoudsidentifiers, synchronisatie van de editor Behoud de bedoelde bewerkingssessie en werk bestaande inhoud bewust bij

Zoek eerst in je project naar toewijzingen aan de presentatie-eigenschap en aanroepen van dismiss(). Een callback voor verversing kan de sheet expliciet sluiten. Lees dismiss binnen de eigen weergave van de sheet, zoals in het voorbeeld: Apple documenteert dat de actie van toepassing is op de omgeving waarin deze wordt opgehaald. Als je deze in de hoofdweergave van het venster leest, kan de actie in plaats daarvan dat venster sluiten. Zie dismiss.

Inspecteer vervolgens de hiërarchie boven de modifier. Als de sheet aan een detailweergave is gekoppeld die tijdens een verversing wordt verwijderd, verplaats deze dan naar boven de grens waar die weergave wordt vervangen. Verplaats ook de presentatiestatus die lokaal wordt beheerd. Als de identiteit van de onderliggende weergave eindigt, kan de opslag ervan worden vervangen, zoals Apple uitlegt bij de levensduur van weergaven.

Maak tot slot onderscheid tussen een gesloten presentatie en een editor die zichtbaar blijft maar het concept verliest. Begin met het eenvoudige TextEditor-voorbeeld en voeg daarna je aangepaste inhoud stapsgewijs weer toe. Deze vergelijking is een diagnostische methode: zo kun je bepalen of de fout te maken heeft met eigenaarschap van de presentatie of met je logica voor inhoudsupdates.

Moet je overstappen op een sheet die door een item wordt aangestuurd?

Gebruik sheet(item:onDismiss:content:) wanneer een specifiek geselecteerd object de bron van de presentatie is. Apple documenteert een optioneel item dat voldoet aan Identifiable, geeft een niet-nil-item door aan de contentclosure en beschrijft vervanging wanneer het item verandert. Overstappen op een andere overload behoudt op zichzelf geen presenter waarvan de levensduur eindigt. Zie sheets die door een item worden aangestuurd.

Kies een identifier die het object gedurende de bedoelde bewerkingssessie vertegenwoordigt. Het Swift-protocol Identifiable drukt stabiele identiteit uit en de documentatie laat de vereiste duur en reikwijdte expliciet over aan de deelnemende typen. Een nieuwe identifier telkens wanneer een eigenschap wordt uitgelezen, kan geen doorlopend object vertegenwoordigen. Zie Identifiable.

Voor een eenvoudige editor zoals in de demonstratie zijn de bedoelde relatie en de afzonderlijke tekstbindings eenvoudig te controleren met een Booleaanse presentatiestatus. Houd bij een editor voor een geselecteerd item de selectie-identiteit gescheiden van velden die vaak veranderen. Behandel het selecteren van een ander object als een bewuste presentatiekeuze en bepaal eerst wat er met het huidige concept moet gebeuren.

Houdt het uitschakelen van interactieve sluiting de sheet in stand?

interactiveDismissDisabled(_:) regelt niet-programmatisch sluiten. Apple vermeldt expliciet dat deze modifier sluiten door een wijziging van de presentatiebinding of door een aanroep van dismiss niet voorkomt. De gedocumenteerde rol ervan biedt dus geen oplossing voor het vervangen van een identiteit. Zie interactiveDismissDisabled(_:).

Pas de modifier alleen binnen de hiërarchie van de sheet toe als je interface dat sluitgedrag nodig heeft en zorg voor een expliciete manier om af te ronden. Begin voor deze taak met het behouden van de host en de status ervan. Als je het sluiten beperkt voordat je de oorzaak hebt vastgesteld, kan het oorspronkelijke probleem met het eigenaarschap blijven bestaan.

Wat verandert er wanneer de sheet AppKit- of TextKit-inhoud bevat?

Voor een NSTextView die is ingebed met NSViewRepresentable moet je het maken scheiden van het bijwerken. Apple definieert makeNSView(context:) als de stap waarin de weergave wordt gemaakt en updateNSView(_:context:) als de plek waar volgende SwiftUI-informatie wordt toegepast. Een coordinator geeft wijzigingen via delegates of target-action terug aan SwiftUI. Zie NSViewRepresentable en makeNSView(context:).

Een nuttige implementatieregel die uit deze levenscyclus volgt, is dat je de editor niet opnieuw moet maken alleen omdat een andere status van de eigenaar is veranderd. Bepaal voordat je de inhoud vervangt of de binnenkomende modeltekst daadwerkelijk verschilt en hoe gelijktijdige lokale bewerkingen moeten worden afgehandeld. Een stabiele sheet lost op zichzelf geen conflicterende schrijfacties naar de tekst op.

TextKit levert tekstopslag, indeling en weergave. AppKits NSTextView bundelt die mogelijkheden en biedt moderne en verouderde indelingsengines. Deze onderdelen hebben betrekking op de inhoud van de editor; het eigenaarschap van de presentatie heeft een eigen stabiele levensduur nodig. Gebruik TextKit wanneer de bewerkingstaak dat vereist en onderzoek het gedrag van tekstupdates los van het sluiten van de sheet. Zie Apples overzicht van TextKit.

Wanneer is een presentatie door AppKit op vensterniveau geschikt?

Als je bestaande app de levenscyclus van vensters al in AppKit beheert, introduceert macOS 26 NSWindow.beginSheet(content:completionHandler:) om SwiftUI-inhoud rechtstreeks in dat venster te presenteren. Apple documenteert ondersteuning voor de omgevingsactie voor het sluiten van de sheet, de afmetingen, toolbars en de regeling voor interactief sluiten. Gebruik een macOS 26 SDK en zorg dat je de beschikbaarheidsgrens van macOS 26 handhaaft voordat je deze mogelijkheid gebruikt. Zie beginSheet(content:completionHandler:).

De geretourneerde NSWindow.HostingSheetRepresentation kan de hoofdweergave van de gepresenteerde inhoud wijzigen of programmatisch sluiten vanuit AppKit ondersteunen. Dit is een gedocumenteerde optie wanneer eigenaarschap op vensterniveau bij de architectuur van de app past. Voor het bovenstaande voorbeeld met een stabiele SwiftUI-container is deze optie niet nodig. Zie NSWindow.HostingSheetRepresentation.

Voor eerdere versies van macOS accepteert AppKits bestaande beginSheet(_:completionHandler:) een NSWindow-sheet en documenteert deze het in de wachtrij zetten van presentaties. Ga er niet van uit dat de nieuwere overload voor SwiftUI-inhoud op die systemen beschikbaar is. Zie beginSheet(_:completionHandler:).

Houd de eigenaar stabiel, houd de presentatiewaarde actief en behoud de bedoelde bron van waarheid voor de editor. Controleer die voorwaarden met een kleine lokale updateactie voordat je complexere inhoud terugzet. Zo kun je tijdens updates van de interface blijven bewerken, binnen de levensduur- en API-grenzen die Apple documenteert.

Bronnen