Para crear vistas previas interactivas de SwiftUI, asigna a cada una su propio estado local, pasa un modelo de vista observable al editor y expón las propiedades editables mediante enlaces. Este tutorial crea un pequeño editor de etiquetas para macOS con texto de ejemplo sintético.

Elige dónde debe residir el estado de las vistas previas

La tarea consiste en mostrar la vista previa de un editor de escritorio en el que alguien cambia el título y el texto de detalle de una etiqueta y luego activa o desactiva la visualización del detalle. El editor debe responder de inmediato y cada vista previa debe comenzar con una configuración de ejemplo explícita.

Usa la entrada mínima que permita la interacción. Apple recomienda pasar a las vistas solo los datos que necesitan, porque las dependencias complejas dificultan la creación de vistas previas. Una etiqueta estática puede recibir una cadena; un editor necesita una conexión de escritura con su estado. Añadir vistas previas a los archivos de interfaz.

Lo que necesita la vista Entrada adecuada Dónde debe estar el estado de ejemplo
Mostrar texto fijo Una cadena inmutable Directamente en la vista previa
Editar un valor que pertenece a su vista principal Una propiedad @Binding @State local en el contenedor de la vista previa
Editar varias propiedades observables relacionadas Un modelo observable expuesto mediante @Bindable Una instancia del modelo almacenada en el @State del contenedor de la vista previa
Leer un modelo proporcionado a través del entorno Una dependencia del entorno Inyección explícita en la raíz de la vista previa

En este ejemplo, el modelo de vista contiene dos cadenas, un ajuste de visibilidad y un título de visualización calculado. Cada vista previa tiene su propia instancia. Así, las condiciones iniciales quedan visibles junto a la declaración de la vista previa y el ejemplo no depende de un singleton mutable compartido.

Prepara un destino SwiftUI para macOS

Usa Xcode 16 o posterior para la sintaxis en línea @Previewable que se muestra aquí. Apple introdujo esa macro en Xcode 16. Establece el destino de implementación de este ejemplo en macOS 14 o posterior, donde SwiftUI admite Observation; la referencia actual de Apple Previewable() también indica que está disponible en macOS 14. Notas de la versión de Xcode 16, Administrar los datos del modelo en tu app, Previewable().

Añade al destino de una app SwiftUI para macOS existente un archivo Swift llamado LabelEditor.swift. Los dos bloques de código siguientes conforman juntos el ejemplo completo de vista previa. Conserva el punto de entrada existente de la app; este archivo define el modelo, el editor y las vistas previas.

El código usa la sintaxis @State en las cadenas de herramientas compatibles. La documentación actual de Apple distingue la implementación: Xcode 26 y versiones anteriores usan el contenedor de propiedad State, mientras que Xcode 27 y versiones posteriores usan la macro State(). Ten en cuenta esa distinción al inspeccionar el código generado o al razonar sobre los costos de inicialización. State, State().

Crea el modelo observable y la vista editable

Aplica @Observable al modelo para que SwiftUI pueda seguir las propiedades que lee la interfaz. Usa @Bindable en el editor para crear los enlaces de propiedades que necesitan sus campos de texto y su interruptor. Apple documenta esta combinación para editar modelos observables. Administrar los datos del modelo en tu app, Bindable.

import Foundation
import Observation
import SwiftUI

@MainActor
@Observable
final class LabelViewModel {
    var title: String
    var detail: String
    var showsDetail: Bool

    init(
        title: String,
        detail: String,
        showsDetail: Bool = true
    ) {
        self.title = title
        self.detail = detail
        self.showsDetail = showsDetail
    }

    var displayedTitle: String {
        let trimmed = title.trimmingCharacters(
            in: .whitespacesAndNewlines
        )
        return trimmed.isEmpty ? "Untitled label" : trimmed
    }
}

@MainActor
struct LabelEditor: View {
    @Bindable var model: LabelViewModel

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            VStack(alignment: .leading, spacing: 6) {
                Text("Title")
                TextField("Label title", text: $model.title)
            }

            VStack(alignment: .leading, spacing: 6) {
                Text("Detail")
                TextField("Label detail", text: $model.detail)
            }

            Toggle("Show detail", isOn: $model.showsDetail)

            Divider()

            Text("Rendered label")
                .font(.caption)
                .foregroundStyle(.secondary)

            VStack(alignment: .leading, spacing: 8) {
                Text(model.displayedTitle)
                    .font(.title2)
                    .fixedSize(horizontal: false, vertical: true)

                if model.showsDetail {
                    Text(model.detail)
                        .foregroundStyle(.secondary)
                        .fixedSize(horizontal: false, vertical: true)
                }
            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding()
            .background(
                .quaternary,
                in: RoundedRectangle(cornerRadius: 10)
            )
        }
        .textFieldStyle(.roundedBorder)
        .padding(20)
    }
}

El modelo acepta deliberadamente todos los valores de ejemplo mediante su inicializador. Este solo asigna propiedades locales. El cálculo alternativo cambia lo que muestra la etiqueta renderizada sin sobrescribir el título que alguien haya introducido: si el título contiene solo espacios en blanco, sigue siendo editable, mientras que el resultado renderizado muestra Untitled label.

El llamador proporciona el modelo al editor. Su declaración @Bindable ofrece enlaces como $model.title; el estado será propiedad del contenedor de la vista previa. Si esta vista solo mostrara los valores del modelo, bastaría con una referencia simple al modelo para la observación. El almacenamiento Bindable resulta útil aquí porque los controles necesitan enlaces de escritura a las propiedades. Bindable.

Da a cada vista previa su propio estado de ejemplo

Añade las siguientes declaraciones al mismo archivo. Cada una crea su propio modelo con valores sintéticos explícitos. Los anchos sirven como comprobaciones ilustrativas de diseño, no como tamaños mínimos recomendados para ventanas.

#Preview("Standard label") {
    @Previewable @State var model = LabelViewModel(
        title: "Sample tile",
        detail: "Blue square, medium size"
    )

    LabelEditor(model: model)
        .frame(width: 420)
        .preferredColorScheme(.light)
}

#Preview("Long label in a narrow editor") {
    @Previewable @State var model = LabelViewModel(
        title: "Sample collection with a longer descriptive label",
        detail: "Round tiles arranged in three columns"
    )

    LabelEditor(model: model)
        .frame(width: 300)
        .preferredColorScheme(.dark)
}

#Preview("Blank title and hidden detail") {
    @Previewable @State var model = LabelViewModel(
        title: "   ",
        detail: "An optional detail line",
        showsDetail: false
    )

    LabelEditor(model: model)
        .frame(width: 420)
}

@Previewable incorpora la declaración de estado local a una vista SwiftUI integrada que genera #Preview. Apple documenta que las declaraciones marcadas se convierten en propiedades de esa vista generada. Coloca la declaración en la raíz del cierre de la vista previa; usar @Previewable fuera del cuerpo de una vista previa produce un error. Previewable().

La cadena de propiedad resultante es explícita: el contenedor de vista previa generado almacena el modelo en @State, LabelEditor recibe la referencia al modelo y @Bindable expone sus propiedades editables. SwiftUI admite almacenar un modelo de Observation en el estado y pasar esa referencia a las vistas secundarias. State.

Considera los valores del inicializador como datos de prueba iniciales. Una vista previa interactiva puede cambiarlos mientras se ejecuta. Si quieres reproducir un diseño concreto, vuelve a una instancia nueva de la vista previa antes de comparar su aspecto inicial.

Comprueba el comportamiento con una pequeña matriz de escenarios

Abre LabelEditor.swift, selecciona Show Canvas y elige una vista previa con nombre en las pestañas del lienzo. Si el lienzo está en pausa, selecciona Restart. Activa el modo Live para probar la entrada de texto y la lógica de los controles. Estas opciones se describen en la guía actual de Apple sobre el lienzo. Interactuar con vistas previas en el lienzo.

Usa las siguientes comprobaciones para distinguir los problemas de estado de los problemas de diseño. Los resultados esperados se desprenden del código del ejemplo; no son resultados de pruebas realizadas.

Escenario Acción Resultado esperado
Etiqueta estándar Sustituye el título por Amber circle El título renderizado cambia de inmediato
Visibilidad del detalle Desactiva y luego vuelve a activar Show detail El detalle desaparece y reaparece con su texto conservado
Título vacío Elimina todos los caracteres del título El título renderizado pasa a ser Untitled label
Título con espacios Introduce solo espacios El campo conserva esos espacios; el título renderizado usa el valor alternativo
Editor estrecho Inspecciona la vista previa con el título largo El título renderizado se ajusta dentro de la tarjeta
Datos de prueba separados Edita una vista previa y luego inspecciona otra vista previa nueva La otra vista previa comienza con los valores de modelo declarados para ella

Si la entrada de texto funciona pero el resultado renderizado no cambia, inspecciona la conexión entre el enlace del campo y la propiedad que lee el resultado. En este caso, ambas rutas conducen a model.title, directamente o a través de displayedTitle. Si al activar o desactivar la visibilidad se borra el detalle, busca código que asigne un valor vacío; este ejemplo solo cambia showsDetail.

En el entorno de investigación no había compilador de Swift ni entorno de ejecución de vistas previas de macOS, así que se revisaron estos ejemplos con respecto a las API documentadas, pero no se compilaron ni ejecutaron. Compila el destino y completa las comprobaciones de interacción en Xcode antes de adaptar el editor.

Mantén separada la configuración compartida de los datos de prueba editables

Para un modelo tan pequeño, la creación local facilita la comprensión de cada vista previa. Si varias vistas previas necesitan más adelante una configuración reutilizable costosa, Apple ofrece PreviewModifier: su contexto compartido se almacena en caché y se reutiliza en las vistas previas que participan. La API requiere macOS 15 o posterior. PreviewModifier.

Ese comportamiento documentado conduce a una decisión de diseño práctica: conserva localmente los modelos de datos de prueba que se editarán por separado y reserva el contexto compartido para la configuración que quieras que reutilicen las vistas previas participantes. Un modelo mutable compartido también compartiría las ediciones, lo que puede dificultar la comparación de escenarios separados.

Si una vista existente obtiene su modelo del entorno por tipo, inyecta la instancia necesaria en la raíz de la vista previa. Apple documenta que la ausencia de un modelo no opcional del tipo solicitado provoca una excepción. Por lo tanto, una vista previa debe proporcionar las dependencias que realmente lee la raíz elegida. Administrar los datos del modelo en tu app.

Respeta los límites entre AppKit y TextKit

El editor anterior usa controles de SwiftUI. Si tu editor existente para macOS contiene una vista de texto de AppKit, usa NSViewRepresentable para integrar esa vista en la jerarquía de SwiftUI. Créala y configúrala mediante makeNSView y, luego, aplica el estado de SwiftUI entrante mediante updateNSView. Reenvía los cambios del delegado de AppKit a través de un coordinador cuando las ediciones deban volver a SwiftUI. Deja que SwiftUI controle el marco y los límites de la vista representada. NSViewRepresentable.

Apple también admite vistas previas directas de vistas y controladores de vista de AppKit. Sin embargo, @Previewable es una función exclusiva de SwiftUI y no se aplica a las vistas previas directas de AppKit. Para usar con contenido de AppKit el patrón de estado local de este artículo, muestra una vista previa de un contenedor SwiftUI que incluya la vista representable. Añadir vistas previas a los archivos de interfaz.

Conserva la elección del motor de texto al crear un NSTextView. Apple documenta NSTextView(usingTextLayoutManager: true) para elegir TextKit 2 durante la inicialización. Sus instrucciones sobre TextKit también explican que acceder a layoutManager de TextKit 1 puede activar una reversión unidireccional para esa instancia de vista de texto. Usa las API adecuadas de TextKit 2 si ese es el motor que pretendes utilizar; una vista previa no elimina esos límites. Novedades de TextKit y las vistas de texto.

Diagnostica los fallos de las vistas previas en el límite de propiedad

Empieza por el primer diagnóstico de compilación. Las macros de Swift generan código durante la compilación, por lo que un error señalado en una llamada a una macro puede involucrar declaraciones generadas. El comando Editor > Expand Macro de Xcode permite inspeccionar ese código. Aplicar macros.

Después, comprueba el mecanismo de estado. Un ObservableObject existente basado en Combine necesita su patrón de propiedad correspondiente: Apple advierte que almacenar un objeto de este tipo en State no sigue los cambios de sus propiedades publicadas y recomienda usar StateObject para los propietarios. Este ejemplo usa @Observable, así que la propiedad del estado sigue la vía de Observation. State.

Mantén el archivo de vista previa terminado centrado en valores iniciales explícitos e interacciones visibles. Cuando los escenarios estándar, de título largo y de título vacío se comporten como se espera, reutiliza el mismo editor con un modelo propiedad de la vista principal adecuada de tu app. Los datos de prueba de las vistas previas siguen siendo una forma compacta de reproducir esas condiciones de interfaz.

Fuentes