Usa sheet(item:onDismiss:content:) con un único elemento seleccionado opcional y crea la hoja a partir del elemento que recibe su cierre. Así, una app para Mac tiene un solo valor que determina tanto si debe presentarse una hoja como lo que muestra.

Este tutorial implementa una pequeña tarea de escritorio: hacer clic en un elemento de muestra local e inspeccionar sus detalles de inmediato. También ofrece una secuencia de diagnóstico para una hoja que se abre en blanco, muestra la selección anterior o presenta texto obsoleto dentro de una vista de AppKit.

¿Por qué usar una hoja basada en un elemento?

SwiftUI ofrece dos métodos de presentación pertinentes. La sobrecarga booleana presenta la hoja cuando su enlace pasa a ser verdadero. La sobrecarga basada en un elemento acepta un valor identificable opcional y proporciona ese valor al cierre de contenido. Elige la forma booleana para un panel fijo; elige la forma basada en un elemento cuando el contenido del panel depende de una selección. Consulta la documentación de Apple sobre hojas basadas en un valor booleano y hojas basadas en elementos.

Un diseño con propiedades separadas para la selección y la presentación permite combinaciones como solicitar una presentación sin tener una selección. Al combinarlas en un único valor opcional, eliminas esa combinación de tu propio modelo de estado: nil representa que no se ha solicitado ningún elemento, y un valor asignado representa el elemento que se va a inspeccionar. Esta es una deducción de diseño basada en las dos API, no una afirmación sobre un defecto de planificación de SwiftUI que no esté documentado.

Si una implementación existente asigna una selección y luego activa un valor booleano, esas dos instrucciones por sí solas no demuestran por qué falla la primera presentación. Inspecciona el flujo de datos del contenido antes de atribuir el síntoma a la sincronización. La pregunta útil es si la hoja muestra el valor que recibe, lee otra propiedad o presenta una copia almacenada por separado.

Crea un ejemplo completo para macOS

Crea en Xcode un proyecto de app para macOS con la interfaz de SwiftUI. Para este ejemplo, usa un destino de implementación de macOS 12 o posterior. La API de hoja basada en un elemento está disponible desde macOS 10.15, mientras que la acción del entorno de cierre que se usa a continuación requiere macOS 12. Apple documenta estos límites de disponibilidad para hojas basadas en elementos y el valor de entorno de cierre.

Reemplaza la declaración de la app generada y la vista de contenido por este programa completo. Mantén un solo punto de entrada de la app en el destino. Todas las etiquetas y descripciones siguientes son datos de muestra locales y sintéticos.

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

El botón asigna el valor en el que se hizo clic a presentedItem. Luego, el cierre de presentación pasa su propio parámetro item a ItemDetailView. Observa que la vista de detalles tiene una propiedad de entrada simple, let item, en lugar de una segunda propiedad de estado para el elemento seleccionado. Apple recomienda mantener una única fuente de verdad y compartir valores de solo lectura mediante propiedades, usando enlaces cuando un elemento secundario necesita permiso de escritura. Consulta State.

La acción de cierre debe estar dentro de ItemDetailView, donde lee el entorno de la hoja. Apple advierte expresamente que leerla en la vista que presenta la hoja y llamar a esa acción desde el contenido de la hoja usa el entorno de la vista que la presenta. Declárala junto al contenido propio de la hoja, como se muestra. Consulta dismiss.

Se ha inspeccionado el programa para verificar la coherencia de las entradas y referencias; aquí no se ha compilado ni ejecutado porque este entorno no dispone de una cadena de herramientas de Swift. Ejecuta las siguientes comprobaciones en Xcode con la versión de macOS de destino.

Verifica la primera presentación con una secuencia de clics deliberada

Inicia cada comprobación de la primera presentación desde un lanzamiento nuevo. Probar solo después de abrir otro elemento puede ocultar el síntoma original. Las expectativas siguientes se basan en las etiquetas y asignaciones de datos explícitas de la muestra; son criterios de aceptación, no resultados de pruebas obtenidos.

Acción Contenido esperado de la hoja Qué comprueba
Inicia la app y haz clic primero en Inspect Blue Tile Blue Tile, su descripción rectangular y tile-b La primera presentación usa el elemento seleccionado
Haz clic en Done y luego inspecciona Amber Tile Amber Tile, su descripción cuadrada y tile-a La selección siguiente llega al contenido
Cierra la hoja e inspecciona Amber Tile otra vez Los mismos detalles ámbar La reapertura de un elemento sigue siendo coherente
Reinicia la app e inspecciona primero Amber Tile Los detalles ámbar de inmediato La corrección no depende de cuál muestra se abre primero

Mostrar tanto un título como un ID hace que esta comprobación sea más útil que verificar solo el título. Si tu interfaz real permite nombres visibles duplicados, durante el desarrollo usa dos elementos sintéticos con el mismo título y distintos ID y descripciones. Así se detecta la selección accidental por el texto visible.

Diagnostica el contenido obsoleto desde el botón hasta la vista final

Sigue estas comprobaciones en orden. Cada paso acota dónde está la discrepancia para que puedas cambiar la capa responsable sin añadir soluciones alternativas de presentación que no guardan relación.

  1. Comprueba la entrada de la acción. Pon un punto de interrupción en la acción del botón y revisa item.id. Confirma que la acción recibe el registro previsto.
  2. Comprueba la entrada de presentación. Revisa el parámetro item dentro del cierre de la hoja. Pasa ese parámetro a la vista de detalles en lugar de volver a consultar otra selección por separado.
  3. Comprueba la propiedad que finalmente se muestra. Confirma que el título y la descripción se leen de la entrada de la vista de detalles. Busca una propiedad almacenada en caché o un borrador local que contenga un valor anterior.
  4. Comprueba cualquier puente de AppKit. Si el texto normal de SwiftUI es correcto pero el texto dentro de la vista integrada está obsoleto, inspecciona el método de actualización de la vista representable.

Mantén una identidad estable

La API basada en elementos requiere Identifiable. Swift define ese protocolo en torno a una identidad estable y deja su duración y alcance a criterio del tipo que lo implementa y de quien lo usa. La muestra emplea ID de cadena almacenados que distinguen los dos registros. Consulta Identifiable.

Para una lista real, elige un identificador que siga identificando el mismo registro durante las actualizaciones que admita tu interfaz. Evita derivar la identidad de un título que pueda cambiar o que varios elementos puedan compartir. Evita también un id calculado que cree un UUID nuevo cada vez que se consulta; no proporcionaría una identidad estable.

Separa la entrada de visualización de un borrador editable

Un inspector puede mostrar directamente item.title. Un editor puede crear intencionadamente un borrador local, pero ese borrador introduce otro valor cuyo ciclo de vida debe definirse. Decide cuándo se crea, si los cambios posteriores del modelo deben reemplazarlo y qué ocurre cuando el usuario termina o cancela.

Apple explica que SwiftUI conserva el almacenamiento de estado de una vista según su identidad y lo inicializa al comienzo del ciclo de vida de esa identidad. Volver a crear el valor de una vista no crea necesariamente un estado nuevo. Por consiguiente, inicializar el estado local a partir de una entrada no es un mecanismo general para mantener ese estado sincronizado con entradas posteriores. Consulta Demystify SwiftUI.

Para esta tarea de solo lectura, conserva la propiedad de entrada simple. Si la amplías para permitir la edición, elige un diseño explícito de borrador y confirmación o un enlace al valor previsto del modelo. Evita añadir una identidad de vista cambiante únicamente para actualizar el texto: cambiar la identidad reemplaza el estado asociado, lo que puede descartar un borrador sin terminar. Esa consecuencia se desprende del modelo de duración del estado documentado por Apple.

¿Qué cambia cuando la hoja contiene una vista de texto de AppKit?

Una hoja que contiene un NSTextView sigue necesitando el mismo flujo de datos del elemento seleccionado. También necesita un puente implementado correctamente. El protocolo NSViewRepresentable de Apple separa la creación inicial en makeNSView(context:) de la configuración posterior en updateNSView(_:context:). Un envoltorio que asigna su texto solo durante la creación no tiene ninguna implementación para aplicar entradas de texto posteriores. Consulta NSViewRepresentable.

Para un envoltorio de solo lectura, inicializa la vista nativa con el texto proporcionado y haz que su método de actualización sincronice el texto mostrado con la entrada actual cuando sea necesario. Apple documenta que SwiftUI llama al método de actualización ante cambios que afectan a la vista de AppKit correspondiente. Consulta updateNSView(_:context:).

Un envoltorio editable necesita una política adicional: no reemplaces a ciegas el texto en cada actualización ni sobrescribas las ediciones en curso. Usa un coordinador para comunicar a SwiftUI los cambios del delegado nativo o de la acción del objetivo, como describe Apple en la documentación del protocolo representable. Deja que SwiftUI controle el marco y los límites de la vista representada; Apple advierte que no se deben establecer esas propiedades de diseño directamente.

TextKit se ocupa del almacenamiento, el diseño y la representación del texto. Apple documenta tanto la ruta moderna de NSTextLayoutManager como la ruta heredada de NSLayoutManager que expone NSTextView. Estas prestaciones no proporcionan el registro seleccionado de la hoja. Asegúrate de que el puente recibe el texto del elemento correcto antes de investigar el diseño del texto, y comprueba por separado la disponibilidad antes de incorporar otras API de TextKit. Consulta TextKit.

Elige el modelo de presentación que corresponde a la tarea

Usa la sobrecarga booleana cuando el contenido sea fijo y la visibilidad sea la única entrada que cambie. Para esta tarea de inspección, usa la sobrecarga basada en un elemento. Apple documenta que cambiar el elemento proporcionado puede cerrar una hoja existente y reemplazarla; por tanto, no interpretes el reemplazo del elemento como una promesa de edición ininterrumpida dentro de la misma presentación. Consulta sheet(item:onDismiss:content:).

Para que la primera presentación sea correcta, facilita el seguimiento del registro seleccionado: una asignación en la acción del botón, un parámetro de elemento en el cierre de la hoja y una entrada en la vista de detalles. Verifica ambos elementos de muestra desde un lanzamiento nuevo. Si el resultado sigue siendo distinto, la secuencia de diagnóstico permite determinar si la discrepancia se origina en la selección, el estado conservado o la ruta de actualización de la vista nativa.

Fuentes