Per mantenere aperto un foglio SwiftUI durante gli aggiornamenti, preserva l’identità del suo contenitore di presentazione e mantieni attivo il binding di presentazione. Associa il foglio a un contenitore stabile, così il contenuto dei figli può cambiare senza sostituire la vista che lo presenta.

Questa guida è rivolta a chi sviluppa un’app Mac con un foglio modificabile che deve restare visibile mentre cambia l’interfaccia sottostante. L’esempio usa testo locale sintetico e separa tre aspetti che puoi esaminare indipendentemente: lo stato di presentazione, il ciclo di vita della vista che presenta il foglio e il contenuto dell’editor.

Perché un aggiornamento della vista può far scomparire un foglio?

SwiftUI può creare nuovi valori di vista quando valuta body, preservando al contempo l’identità della vista visualizzata. Lo stato persiste per tutta la durata di quell’identità. Rimuovere la vista o cambiarne l’identità ne termina il ciclo di vita; anche il passaggio tra rami condizionali può creare identità distinte, persino se entrambi i rami contengono lo stesso tipo di vista. Apple spiega queste relazioni in Demistificare SwiftUI.

Per un foglio controllato da un valore booleano, il controllo di presentazione documentato è il binding passato a sheet(isPresented:onDismiss:content:): il valore true richiede la presentazione. Quando indaghi sulla scomparsa di un foglio, controlla sia quel binding sia la permanenza della vista che ospita il modificatore. Un valore true memorizzato altrove non dimostra che il contenitore di presentazione originale sia ancora attivo. Consulta la documentazione di Apple sui fogli controllati da un valore booleano.

Una causa particolarmente diretta è un identificatore esplicito che cambia. Apple documenta che cambiare il valore fornito a id(_:) reimposta l’identità della vista, incluso il suo stato. Esamina la vista che presenta il foglio e le sue antenate per individuare identificatori derivati da contatori di revisione, testo modificabile o valori generati ogni volta. Rimuovi questi identificatori quando il loro effetto di reimpostazione non è voluto. Consulta id(_:).

La conseguenza pratica è collocare il modificatore del foglio al di fuori della parte dell’interfaccia che cambia. Se una vista di caricamento, una riga filtrata o un layout alternativo dei dettagli può scomparire, di solito l’antenato persistente più vicino è un proprietario della presentazione più adatto.

Crea un proprietario stabile del foglio con un esempio Mac completo

Crea in Xcode un’app SwiftUI per macOS, imposta la versione minima di distribuzione su macOS 12 o successiva e sostituisci le dichiarazioni generate per l’app e la vista del contenuto con il singolo file seguente. Mantieni una sola dichiarazione dell’app @main. La versione minima macOS 12 consente di usare nell’esempio l’azione di ambiente dismiss; TextEditor è disponibile da macOS 11. Consulta la documentazione di Apple su dismiss e la documentazione di TextEditor.

La vista radice della finestra possiede il flag di presentazione, il testo modificabile, il contatore di revisioni e il flag del layout. Il suo VStack esterno ospita il modificatore del foglio. Cambiano solo i rami del contenuto dentro quel contenitore. Apple consiglia di dichiarare lo stato privatamente nella vista più in alto che ne ha bisogno e di passare binding ai figli che devono modificarlo. Consulta 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)
    }
}

Questa è un’app dimostrativa completa, esaminata rispetto alle API documentate. In questo ambiente non è stata compilata né eseguita, perché non sono disponibili né la toolchain Swift né un runtime macOS.

Prova l’aggiornamento senza chiudere il foglio

  1. Avvia l’app e fai clic su Edit Note.
  2. Sostituisci il testo di esempio con Sample note Beta.
  3. Fai clic più volte su Update Owner. Ogni clic incrementa lo stato del proprietario e passa da un ramo condizionale all’altro.
  4. Verifica che l’editor resti visibile, che il testo sia ancora Sample note Beta e che la revisione visualizzata avanzi.
  5. Fai clic su Done. Verifica che la finestra mostri il testo modificato nel layout espanso, quando quel layout è attivo.
  6. Riapri l’editor e verifica che il testo modificato sia ancora disponibile.

Il pulsante di aggiornamento si trova nel foglio, così la prova non dipende dal clic sulla finestra sottostante. AppKit descrive i fogli come modali rispetto al documento: mentre un foglio è visibile, la maggior parte degli eventi diretti alla finestra padre è vietata. Aggiornando il proprietario tramite un binding, questo esempio consente di provare direttamente il cambiamento di stato. Consulta beginSheet(_:completionHandler:).

Decidi cosa deve comportare la chiusura dell’editor

Questo esempio modifica immediatamente il testo del proprietario. Done chiude l’editor, ma non esegue un’operazione di salvataggio separata. La stringa resta nello stato locale della finestra e l’esempio non offre persistenza tra un avvio dell’app e l’altro. Questo comportamento deriva dal progetto con binding condivisi descritto in State.

Se devi consentire l’annullamento di una bozza, usa un valore separato per la bozza e copialo nel documento solo in seguito a un’azione di conferma esplicita. Definisci questo comportamento prima di implementare la chiusura: decidere se confermare o scartare una bozza è distinto dal decidere quale vista possiede il foglio.

Diagnostica la chiusura con tre verifiche indipendenti

Segui questa sequenza nella tua app. Trasforma le regole generali sull’identità in un’indagine mirata, senza presumere che tutti i fogli che scompaiono abbiano la stessa causa.

Verifica Cosa esaminare Correzione pratica
Valore di presentazione Ogni scrittura che imposta il valore booleano su false o azzera l’elemento facoltativo di presentazione Limita le scritture di chiusura alle azioni di chiusura intenzionali
Identità della vista che presenta il foglio Rami condizionali, righe rimosse, identificatori degli antenati che cambiano Sposta sia lo stato di presentazione sia il modificatore su un antenato stabile e adatto
Durata del contenuto Stato locale del foglio, identificatori del contenuto che cambiano, sincronizzazione dell’editor Preserva la sessione di modifica prevista e aggiorna deliberatamente il contenuto esistente

Per prima cosa, cerca nel progetto le assegnazioni alla proprietà di presentazione e le chiamate a dismiss(). Una callback di aggiornamento potrebbe chiudere esplicitamente il foglio. Leggi dismiss all’interno della vista del foglio, come nell’esempio: Apple documenta che l’azione si applica all’ambiente in cui viene ottenuta. Leggerla nella vista radice della finestra può invece avere come destinazione la finestra stessa. Consulta dismiss.

Poi, esamina la gerarchia al di sopra del modificatore. Se il foglio è associato a una vista dei dettagli che viene rimossa durante un aggiornamento, spostalo al di sopra di quel punto di sostituzione. Sposta anche lo stato di presentazione posseduto localmente. Se il flag resta in un figlio la cui identità termina, la memorizzazione rimane esposta alla sostituzione, secondo la spiegazione di Apple sul ciclo di vita delle viste.

Infine, distingui una presentazione chiusa da un editor ancora visibile ma che ha perso la bozza. Parti dall’esempio semplice con TextEditor, poi reinserisci il tuo contenuto personalizzato a poco a poco. Questo confronto è un metodo diagnostico: aiuta a capire se il problema dipende dalla gestione della presentazione o dalla logica di aggiornamento del contenuto.

Conviene passare a un foglio basato su un elemento?

Usa sheet(item:onDismiss:content:) quando l’origine della presentazione è una specifica entità selezionata. Apple documenta un elemento facoltativo conforme a Identifiable, passa alla closure del contenuto un elemento diverso da nil e descrive la sostituzione quando l’elemento cambia. Cambiare overload, di per sé, non mantiene attiva una vista che presenta il foglio quando il suo ciclo di vita termina. Consulta la documentazione sui fogli basati su un elemento.

Scegli un identificatore che rappresenti l’entità per tutta la sessione di modifica prevista. Il protocollo Swift Identifiable esprime un’identità stabile e la sua documentazione lascia esplicitamente ai tipi coinvolti la definizione della durata e dell’ambito richiesti. Un identificatore nuovo ogni volta che viene letta una proprietà non può rappresentare un’entità persistente. Consulta Identifiable.

Per un editor semplice come quello dimostrativo, un flag booleano di presentazione e binding separati per il testo rendono facile esaminare la relazione prevista. Per un editor basato su un elemento selezionato, mantieni separata l’identità della selezione dai campi che cambiano spesso. Considera la selezione di un’altra entità una decisione deliberata sulla presentazione e stabilisci prima cosa deve accadere alla bozza corrente.

Disabilitare la chiusura interattiva mantiene attivo il foglio?

interactiveDismissDisabled(_:) controlla la chiusura non programmatica. Apple afferma esplicitamente che non impedisce la chiusura causata dal cambiamento del binding di presentazione o dalla chiamata a dismiss. Il suo ruolo documentato, quindi, non offre una soluzione alla sostituzione dell’identità. Consulta interactiveDismissDisabled(_:).

Applica il modificatore all’interno della gerarchia del foglio solo se la tua interfaccia richiede quel comportamento di chiusura e mantieni un percorso esplicito per completare l’operazione. Per questo caso, inizia preservando il contenitore e il suo stato. Aggiungere una restrizione alla chiusura prima di averne identificato la causa può lasciare irrisolto il problema di proprietà originario.

Cosa cambia se il foglio contiene contenuto AppKit o TextKit?

Per incorporare un NSTextView con NSViewRepresentable, separa la creazione dagli aggiornamenti. Apple definisce makeNSView(context:) come fase di creazione e updateNSView(_:context:) come punto in cui applicare le informazioni SwiftUI successive. Un coordinatore trasferisce a SwiftUI le modifiche delegate o target-action. Consulta NSViewRepresentable e makeNSView(context:).

Una regola pratica derivata da questo ciclo di vita è evitare di ricreare l’editor solo perché è cambiato uno stato del proprietario non correlato. Prima di sostituirne il contenuto, verifica se il testo del modello in arrivo è davvero diverso e come gestire eventuali modifiche locali concorrenti. Un foglio stabile, da solo, non risolve le scritture in conflitto sul testo.

TextKit fornisce l’archiviazione, il layout e il rendering del testo. NSTextView di AppKit riunisce queste funzionalità ed espone motori di layout moderni e legacy. Questi meccanismi riguardano il contenuto dell’editor; la proprietà della presentazione richiede comunque un ciclo di vita stabile. Usa TextKit quando lo richiede l’attività di modifica e indaga sul comportamento degli aggiornamenti del testo separatamente dalla chiusura del foglio. Consulta la panoramica di Apple su TextKit.

Quando è opportuno affidare la presentazione ad AppKit a livello di finestra?

Se la tua app esistente gestisce già il ciclo di vita delle finestre in AppKit, macOS 26 introduce NSWindow.beginSheet(content:completionHandler:) per presentare contenuto SwiftUI direttamente in quella finestra. Apple documenta il supporto per l’azione dell’ambiente del foglio per la chiusura, il dimensionamento, le barre degli strumenti e il controllo della chiusura interattiva. Usa un SDK macOS 26 e rispetta il requisito di disponibilità di macOS 26 prima di adottarlo. Consulta beginSheet(content:completionHandler:).

Il valore restituito NSWindow.HostingSheetRepresentation può cambiare la vista radice presentata o supportare la chiusura programmatica da AppKit. Offre un’opzione documentata quando la gestione a livello di finestra si adatta all’architettura dell’app. Non è necessario per l’esempio con il contenitore SwiftUI stabile riportato sopra. Consulta NSWindow.HostingSheetRepresentation.

Nelle versioni precedenti di macOS, l’API AppKit esistente beginSheet(_:completionHandler:) accetta un foglio NSWindow e documenta l’accodamento delle presentazioni. Non presumere che l’overload più recente con contenuto SwiftUI sia disponibile su quei sistemi. Consulta beginSheet(_:completionHandler:).

Mantieni stabile il proprietario, lascia attivo il valore di presentazione e preserva la fonte di verità prevista per l’editor. Verifica queste condizioni con una piccola azione di aggiornamento locale prima di ripristinare contenuti più complessi. Avrai così un metodo concreto per continuare a modificare mentre l’interfaccia si aggiorna, rispettando i limiti di ciclo di vita e delle API documentati da Apple.

Fonti