Para mostrar un array de Foundation en una ventana de Mac, valida los tipos de sus elementos y crea una vista para cada valor. Convertir NSArray a [String] te proporciona cadenas; para mostrarlas, hace falta transformarlas por separado en etiquetas de AppKit.

Esta guía está dirigida a un desarrollador que añade una lista pequeña a su propia app de macOS. La tarea concreta consiste en mostrar tres nombres sintéticos —Circle, Square y Triangle— en una pila vertical. El ejemplo deja explícitos los tipos de colección y de elementos, el diseño y el ciclo de vida de los objetos.

¿Qué cambia realmente al convertir una colección?

Apple documenta Array y NSArray como tipos correspondientes de Swift y Foundation, igual que String y NSString. Recomienda usar tipos de valor de Swift en el código Swift habitual y acceder a los tipos de referencia de Foundation cuando se necesita su comportamiento. Para esta tarea, mantén la colección de Foundation en el límite de entrada y usa después un array de Swift con tipo definido. Trabajar con tipos de Foundation

El tipo externo de una colección y el tipo de sus elementos responden a preguntas distintas. NSArray identifica una colección ordenada de objetos. [String] añade un requisito concreto: cada elemento debe poder usarse como una cadena de Swift. Ninguna de las dos declaraciones describe una colección de vistas. El inicializador de la pila de Apple requiere explícitamente [NSView]. NSArray, init(views:)

Etapa Tipo en este ejemplo Contrato
Entrada de Foundation NSArray Contiene los objetos sintéticos proporcionados
Modelo validado [String] Todos los elementos cumplen el requisito de ser cadenas
Objetos de presentación [NSView] Cada elemento es una etiqueta de texto recién creada
Contenedor de diseño NSStackView Organiza esas vistas según su configuración

La distinción útil es entre conversión y construcción. La conversión conecta representaciones compatibles, como las cadenas de Foundation y las de Swift. Construir objetos NSTextField crea objetos de presentación con sus propias propiedades y ciclo de vida. No existe una conversión documentada de cadena a etiqueta; la vía compatible consiste en llamar al inicializador de la etiqueta para cada cadena. Array, init(labelWithString:)

¿Cómo validar los elementos sin descartar ninguno?

Usa una conversión condicional cuando haya que comprobar el contrato de tipo de los elementos de la colección recibida. Apple documenta que as? produce un resultado opcional y que una conversión forzada fallida con as! provoca un error en tiempo de ejecución. Gestiona el fallo explícitamente antes de construir la interfaz. Gestión de métodos y objetos con tipos dinámicos en Swift

A continuación se muestra una comprobación completa que solo usa Foundation y una entrada sintética. Guárdala como BridgeCheck.swift y ejecútala en un Mac con las herramientas de Swift mediante swift BridgeCheck.swift. Sus aserciones expresan los resultados esperados.

import Foundation

let source: NSArray = [
    NSString(string: "Circle"),
    NSString(string: "Square"),
    NSString(string: "Triangle")
]

let names = source as? [String]
assert(names == ["Circle", "Square", "Triangle"])

let mixed: NSArray = [NSString(string: "Circle"), NSNumber(value: 7)]
assert((mixed as? [String]) == nil)

let empty: NSArray = []
assert((empty as? [String]) == [])

print("Collection checks passed.")

La primera conversión solicita un array de cadenas, así que comprueba el tipo requerido de los elementos al obtener la colección con tipo definido. La entrada mixta no cumple ese contrato. La entrada vacía sí lo cumple: no contiene ningún elemento incompatible. Estas comprobaciones ilustran el contrato de entrada elegido; no son mediciones ni resultados de una aplicación.

Evita sustituirlo por compactMap { $0 as? String } solo para que funcione una colección mixta. Apple define compactMap como una operación que devuelve únicamente los resultados de transformación que no son nil. En este ejemplo, ese enfoque descartaría el número y mostraría una lista incompleta. Filtrar puede ser adecuado si se solicita explícitamente; en este caso, la tarea requiere conservar todos los elementos proporcionados. compactMap(_:)

Una vez validada la colección, usa map para crear una etiqueta por cada nombre. Apple documenta map como una operación que aplica una transformación a los elementos de la secuencia, y NSTextField(labelWithString:) como un inicializador que crea una etiqueta estática, sin ajuste de línea y con texto no seleccionable. Esto hace que la transformación sea adecuada para estos tres nombres cortos. map(_:), init(labelWithString:)

¿Cómo se construye la ventana completa de Mac?

Este ejemplo independiente requiere macOS 14 o posterior y las herramientas de línea de comandos de Xcode instaladas. Crea la interfaz mediante código, así que no necesita storyboard, nib, recurso ni archivo fuente adicional. El requisito de macOS 14 corresponde al uso de NSApplication.activate() en el ejemplo, que solicita la activación sin garantizarla. activate()

Guarda el programa completo de abajo como CollectionDemo.swift. Incluye deliberadamente su propio punto de entrada. Al adaptarlo a una app existente, usa el punto de entrada y el ciclo de vida que ya tenga esa app en vez de añadir otro.

import Foundation
import AppKit

@MainActor
final class DemoDelegate: NSObject, NSApplicationDelegate {
    var window: NSWindow?

    func applicationDidFinishLaunching(_ notification: Notification) {
        let source: NSArray = [
            NSString(string: "Circle"),
            NSString(string: "Square"),
            NSString(string: "Triangle")
        ]

        guard let names = source as? [String] else {
            print("Expected a collection containing only strings.")
            NSApplication.shared.terminate(nil)
            return
        }

        let labels: [NSView] = names.map { name in
            NSTextField(labelWithString: name)
        }

        let stack = NSStackView(views: labels)
        stack.orientation = .vertical
        stack.alignment = .leading
        stack.spacing = 8
        stack.translatesAutoresizingMaskIntoConstraints = false

        let content = NSView(frame: NSRect(
            x: 0, y: 0, width: 360, height: 180
        ))
        content.addSubview(stack)

        NSLayoutConstraint.activate([
            stack.leadingAnchor.constraint(
                equalTo: content.leadingAnchor, constant: 20
            ),
            stack.topAnchor.constraint(
                equalTo: content.topAnchor, constant: 20
            ),
            stack.trailingAnchor.constraint(
                lessThanOrEqualTo: content.trailingAnchor, constant: -20
            ),
            stack.bottomAnchor.constraint(
                lessThanOrEqualTo: content.bottomAnchor, constant: -20
            )
        ])

        let window = NSWindow(
            contentRect: content.frame,
            styleMask: [.titled, .closable, .miniaturizable],
            backing: .buffered,
            defer: false
        )
        window.title = "Synthetic Shapes"
        window.isReleasedWhenClosed = false
        window.contentView = content
        self.window = window
        window.center()
        window.makeKeyAndOrderFront(nil)
        NSApplication.shared.activate()
    }

    func applicationShouldTerminateAfterLastWindowClosed(
        _ sender: NSApplication
    ) -> Bool {
        true
    }
}

@main
@MainActor
enum CollectionDemo {
    static let appDelegate = DemoDelegate()

    static func main() {
        let app = NSApplication.shared
        app.setActivationPolicy(.regular)
        app.delegate = appDelegate
        app.run()
    }
}

Compílalo y ejecútalo desde Terminal, en el directorio que contiene el archivo:

xcrun swiftc -parse-as-library CollectionDemo.swift -o CollectionDemo
./CollectionDemo

Con la entrada proporcionada, el resultado esperado es una ventana titulada Synthetic Shapes que contiene las tres etiquetas en su orden original. Al cerrar la ventana, se solicita la terminación de la aplicación. El ejemplo devuelve true desde el método del delegado que Apple proporciona para ese comportamiento. applicationShouldTerminateAfterLastWindowClosed(_:)

Estos ejemplos se revisaron, pero no se compilaron ni ejecutaron aquí: el entorno de investigación no dispone de compilador de Swift ni de AppKit de macOS. Ejecuta las comprobaciones de Foundation e inspecciona la ventana en el Mac de destino antes de integrar el ejemplo.

¿Qué contratos de diseño y ciclo de vida deben mantenerse?

Los valores predeterminados documentados del inicializador de la pila son importantes: crea una pila horizontal, coloca las vistas proporcionadas en el área de gravedad inicial y desactiva la traducción de la máscara de redimensionamiento automático para la pila. El ejemplo cambia explícitamente la orientación a vertical y la alineación a inicial. Una conversión de array no puede elegir ninguno de esos ajustes de diseño. init(views:), alignment

La pila gestiona la disposición de las vistas que contiene. Las restricciones entre la pila y la vista de contenido que la contiene determinan dónde va esa disposición dentro de la ventana. Aquí, las restricciones inicial y superior fijan un margen de 20 puntos; las desigualdades derecha e inferior reservan espacio sin exigir que la lista corta ocupe toda el área de contenido. Apple documenta que las vistas de pila usan Auto Layout y explica que desactivar la traducción de la máscara de redimensionamiento automático permite proporcionar restricciones propias. NSStackView, translatesAutoresizingMaskIntoConstraints

Esta es una demostración de tamaño fijo para tres etiquetas cortas. El texto más largo, muchas filas o una interfaz redimensionable requieren decidir por separado cómo diseñarlos —por ejemplo, con ajuste de línea o desplazamiento— antes de ampliar el ejemplo. Convertir correctamente una colección más grande no garantiza que quepa en la presentación.

El ciclo de vida es independiente de la conversión de tipos. Apple declara como débil la propiedad del delegado de la aplicación. Por eso, la propiedad estática appDelegate proporciona un propietario fuerte explícito. El delegado también retiene la ventana, y esta establece isReleasedWhenClosed en false, siguiendo las indicaciones de Apple para clientes de Swift y ARC. delegate, isReleasedWhenClosed

La construcción de la interfaz ocurre en applicationDidFinishLaunching, que Apple documenta como un lugar para realizar más inicialización después de que se inicia el bucle principal y antes de que procese eventos. El punto de entrada del programa llama a run() para iniciar ese bucle de eventos. Mantén esas responsabilidades al trasladar la conversión a una app de AppKit existente. applicationDidFinishLaunching(_:), run()

Las etiquetas también representan una instantánea construida. El ejemplo asigna el texto de cada etiqueta una sola vez. Si después cambia la colección de origen, hará falta código explícito para actualizar las etiquetas o reconstruir las vistas organizadas; la conversión y la operación de asignación no crean una conexión continua.

¿Cómo localizar rápidamente un error entre el contenedor y sus elementos?

Sigue esta secuencia para identificar qué contrato falló. Separa la validación de tipos de las comprobaciones de presentación y ciclo de vida, para que un problema de diseño no te lleve a reescribir una conversión que funciona.

Síntoma Comprobación Siguiente paso compatible
Una conversión a [String] devuelve nil ¿Cumplen todos los elementos de entrada el requisito de ser cadenas? Rechaza la colección explícitamente o define una política de conversión intencional.
Se está intentando convertir un array completo a String ¿El destino necesita un valor o una colección? Valida [String]; elige un elemento solo si la tarea realmente lo requiere.
Un inicializador de la pila rechaza el argumento ¿El argumento es [String] en vez de [NSView]? Primero asigna cada cadena a una etiqueta mediante map.
Las etiquetas aparecen en horizontal ¿Se configuró la orientación de la pila? Establece .vertical para esta lista.
Las vistas están mal ubicadas o las restricciones entran en conflicto ¿Son coherentes las restricciones de la vista contenedora y los ajustes de la máscara de redimensionamiento automático? Inspecciona el diseño por separado de la conversión de entrada.
No llegan las llamadas al delegado ¿Un propietario fuerte mantiene el delegado? Mantén vivo el delegado después de asignarlo a la propiedad débil.
Los cambios posteriores al modelo no aparecen ¿Hay código explícito de actualización? Actualiza los valores de las etiquetas o las vistas organizadas de forma deliberada.

Para esta tarea, la ruta fiable consiste en validar una vez la entrada de Foundation, conservar el modelo resultante con tipo definido, construir una etiqueta por valor y configurar explícitamente la pila y el ciclo de vida de la app. Cada paso tiene un contrato visible, lo que facilita diagnosticar los fallos a medida que crece la interfaz de Mac.

Fuentes