Usa sheet(item:onDismiss:content:) con un solo elemento selezionato opzionale e crea la sheet a partire dall’elemento ricevuto dalla sua closure. In questo modo, un’app Mac ha un unico valore che determina sia se presentare una sheet sia cosa mostrare al suo interno.

Questo tutorial implementa una piccola attività desktop: fai clic su un elemento di esempio locale e visualizzane subito i dettagli. Fornisce anche una sequenza diagnostica per una sheet che si apre vuota, mostra la selezione precedente o visualizza testo non aggiornato all’interno di una vista AppKit.

Perché usare una sheet basata su un elemento?

SwiftUI offre due metodi di presentazione pertinenti. La variante con valore Booleano presenta la sheet quando il relativo binding diventa true. La variante con elemento accetta un valore identificabile opzionale e lo fornisce alla closure del contenuto. Scegli la variante con valore Booleano per un pannello fisso; scegli quella con elemento quando il contenuto del pannello dipende da una selezione. Consulta la documentazione Apple per le sheet basate su un valore Booleano e le sheet basate su un elemento.

Un design con proprietà separate per la selezione e la presentazione consente combinazioni come una presentazione richiesta senza alcuna selezione. Riunirle in un unico valore opzionale elimina questa combinazione dal tuo modello di stato: nil rappresenta l’assenza di un elemento richiesto, mentre un valore presente rappresenta l’elemento da esaminare. Si tratta di una deduzione progettuale basata sulle due API, non di un’affermazione su un difetto di pianificazione non documentato di SwiftUI.

Se un’implementazione esistente assegna una selezione e poi attiva un valore Booleano, queste due istruzioni da sole non dimostrano perché la prima presentazione non riesca. Esamina il percorso dei dati del contenuto prima di attribuire il sintomo ai tempi di esecuzione. La domanda utile è se la sheet visualizza l’input ricevuto, legge un’altra proprietà o mostra una copia memorizzata separatamente.

Crea un esempio macOS completo

Crea in Xcode un progetto per un’app macOS con interfaccia SwiftUI. Per questo esempio, usa una versione minima di macOS 12 o successiva. L’API delle sheet basate su un elemento è disponibile da macOS 10.15, mentre l’azione di chiusura tramite l’ambiente usata sotto richiede macOS 12. Apple documenta questi requisiti di disponibilità per le sheet basate su un elemento e il valore dismiss dell’ambiente.

Sostituisci la dichiarazione dell’app generata e la vista del contenuto con questo programma completo. Nel target deve esserci un solo punto di ingresso dell’app. Tutte le etichette e le descrizioni seguenti sono dati di esempio locali sintetici.

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)
    }
}

Il pulsante assegna il valore selezionato a presentedItem. La closure di presentazione passa poi il proprio parametro item a ItemDetailView. Nota che la vista dei dettagli ha una semplice proprietà di input, let item, anziché una seconda proprietà di stato per l’elemento selezionato. Apple consiglia di mantenere un’unica fonte di verità e condividere valori di sola lettura tramite proprietà, usando i binding quando una vista figlia deve poter scrivere. Consulta State.

L’azione di chiusura va dichiarata all’interno di ItemDetailView, dove legge l’ambiente della sheet. Apple avverte esplicitamente che leggerla nella vista che presenta la sheet e invocare quell’azione dal contenuto della sheet significa usare l’ambiente della vista che la presenta. Come mostrato, dichiara l’azione accanto al contenuto della sheet stessa. Consulta dismiss.

Il programma è stato esaminato per verificare la coerenza degli input e dei riferimenti; qui non è stato compilato né eseguito perché in questo ambiente non è disponibile una toolchain Swift. Esegui i controlli seguenti in Xcode sulla versione di macOS di destinazione.

Verifica la prima presentazione con una sequenza di clic precisa

Inizia ogni verifica della prima presentazione con un avvio pulito. Eseguire i test solo dopo aver aperto un altro elemento può nascondere il sintomo originale. I risultati attesi seguenti derivano dalle etichette e dalle assegnazioni esplicite dei dati nell’esempio; sono criteri di accettazione, non risultati di test dichiarati.

Azione Contenuto atteso nella sheet Cosa verifica
Avvia l’app e fai clic per primo su Inspect Blue Tile Blue Tile, la sua descrizione rettangolare e tile-b La prima presentazione usa l’elemento selezionato
Fai clic su Done, poi esamina Amber Tile Amber Tile, la sua descrizione quadrata e tile-a La selezione successiva raggiunge il contenuto
Chiudi la sheet ed esamina di nuovo Amber Tile Gli stessi dettagli ambrati La riapertura dell’elemento rimane coerente
Riavvia l’app ed esamina per primo Amber Tile Subito i dettagli ambrati La correttezza non dipende da quale elemento di esempio viene aperto per primo

Mostrare sia un titolo sia un ID rende questa verifica più utile che controllare solo il titolo. Se la tua interfaccia reale consente nomi visualizzati duplicati, durante lo sviluppo usa due elementi sintetici con lo stesso titolo e ID e descrizioni diversi. Questo rende visibili le selezioni accidentali basate sul testo visualizzato.

Diagnostica il contenuto non aggiornato, dal pulsante alla vista finale

Procedi con questi controlli nell’ordine indicato. Ogni passaggio restringe il punto in cui si verifica la discrepanza, così puoi modificare il livello responsabile senza aggiungere soluzioni alternative di presentazione non correlate.

  1. Controlla l’input dell’azione. Imposta un breakpoint nell’azione del pulsante selezionato e controlla item.id. Verifica che l’azione riceva il record desiderato.
  2. Controlla l’input della presentazione. Esamina il parametro item all’interno della closure della sheet. Passa quel parametro alla vista dei dettagli invece di cercare di nuovo una selezione separata.
  3. Controlla la proprietà visualizzata alla fine. Verifica che il titolo e la descrizione vengano letti dall’input della vista dei dettagli. Cerca una proprietà memorizzata nella cache o una bozza locale contenente un valore precedente.
  4. Controlla eventuali bridge AppKit. Se il testo SwiftUI normale è corretto ma quello racchiuso in una vista è obsoleto, esamina il metodo di aggiornamento del representable.

Mantieni stabile l’identità

L’API basata su un elemento richiede Identifiable. Swift definisce questo protocollo in termini di identità stabile e lascia che siano il tipo conforme e il suo consumer a determinarne durata e ambito. L’esempio usa ID stringa memorizzati che distinguono i due record. Consulta Identifiable.

Per un elenco reale, scegli un identificatore che continui a identificare lo stesso record durante gli aggiornamenti supportati dall’interfaccia. Evita di ricavare l’identità da un titolo che può cambiare o essere condiviso da più elementi. Evita anche un id calcolato che crea un nuovo UUID a ogni lettura: non fornirebbe un’identità stabile.

Separa l’input visualizzato da una bozza modificabile

Un pannello di ispezione può visualizzare direttamente item.title. Un editor può creare intenzionalmente una bozza locale, ma una bozza introduce un altro valore di cui va definito il ciclo di vita. Decidi quando viene creata, se le modifiche successive al modello devono sostituirla e cosa succede quando l’utente termina o annulla.

Apple spiega che SwiftUI conserva l’archiviazione dello stato per l’identità di una vista e la inizializza all’inizio del ciclo di vita di tale identità. Ricreare il valore di una vista non crea automaticamente uno stato nuovo. Di conseguenza, inizializzare uno stato locale da un input non è un metodo generale per mantenerlo sincronizzato con gli input successivi. Consulta Demystify SwiftUI.

Per questa attività di sola lettura, mantieni la semplice proprietà di input. Se la estendi per consentire modifiche, scegli un design esplicito con bozza e conferma oppure un binding al valore del modello desiderato. Evita di aggiungere un’identità di vista variabile solo per aggiornare il testo: cambiarla sostituisce lo stato associato e può scartare una bozza non completata. Questa conseguenza deriva dal modello del ciclo di vita dello stato documentato da Apple.

Cosa cambia quando la sheet contiene una vista di testo AppKit?

Una sheet che contiene un NSTextView richiede ancora lo stesso percorso dei dati per l’elemento selezionato. Richiede inoltre un bridge implementato correttamente. Il protocollo NSViewRepresentable di Apple distingue la creazione iniziale in makeNSView(context:) dalla configurazione successiva in updateNSView(_:context:). Un wrapper che assegna il testo solo durante la creazione non implementa l’applicazione di input testuali successivi. Consulta NSViewRepresentable.

Per un wrapper di sola lettura, inizializza la vista nativa con il testo fornito e fai in modo che il suo metodo di aggiornamento sincronizzi il testo visualizzato con l’input corrente quando necessario. Apple documenta che SwiftUI chiama il metodo di aggiornamento per le modifiche che influiscono sulla vista AppKit corrispondente. Consulta updateNSView(_:context:).

Un wrapper modificabile richiede una politica aggiuntiva: non sostituire ciecamente il testo a ogni aggiornamento, sovrascrivendo così le modifiche in corso. Usa un coordinatore per comunicare a SwiftUI le modifiche provenienti dai delegate nativi o dalle azioni target, come descritto da Apple nella documentazione del representable. Lascia che sia SwiftUI a controllare frame e bounds della vista rappresentata; Apple sconsiglia di impostare direttamente queste proprietà di layout.

TextKit gestisce l’archiviazione, il layout e il rendering del testo. Apple documenta sia il percorso moderno NSTextLayoutManager sia quello precedente NSLayoutManager, esposti da NSTextView. Questi strumenti non forniscono il record selezionato per la sheet. Assicurati che il bridge riceva il testo dell’elemento corretto prima di esaminare il layout del testo e verifica separatamente la disponibilità prima di introdurre altre API TextKit. Consulta TextKit.

Scegli il modello di presentazione adatto all’attività

Usa la variante con valore Booleano quando il contenuto è fisso e l’unico input che cambia è la visibilità. Per questa attività di ispezione, usa la variante con elemento. Apple documenta che la modifica dell’elemento fornito può chiudere una sheet esistente e sostituirla; non considerare quindi la sostituzione dell’elemento come una garanzia di poter continuare a modificare senza interruzioni nella stessa presentazione. Consulta sheet(item:onDismiss:content:).

Per garantire una prima presentazione corretta, rendi facile tracciare il record selezionato: un’assegnazione nell’azione del pulsante, un parametro elemento nella closure della sheet e un input nella vista dei dettagli. Verifica entrambi gli elementi di esempio dopo un avvio pulito. Se il risultato è ancora diverso, la sequenza diagnostica permette di individuare se la discrepanza proviene dalla selezione, dallo stato conservato o dal percorso di aggiornamento della vista nativa.

Fonti