Per visualizzare un array Foundation in una finestra Mac, convalida i tipi dei suoi elementi, quindi crea una vista per ogni valore. Il bridging da NSArray a [String] restituisce stringhe; per visualizzarle serve una trasformazione separata in etichette AppKit.

Questa guida è rivolta a chi sviluppa una piccola lista nella propria app macOS. L’obiettivo concreto è visualizzare tre nomi sintetici — Circle, Square e Triangle — in una pila verticale. L’esempio rende espliciti i tipi delle collezioni e degli elementi, il layout e la durata degli oggetti.

Che cosa cambia davvero quando si esegue il bridging di una collezione?

Apple documenta Array e NSArray come tipi corrispondenti di Swift e Foundation, così come corrispondono String e NSString. Consiglia di usare i tipi valore di Swift nel normale codice Swift e di accedere ai tipi riferimento di Foundation quando servono le loro caratteristiche. Per questa attività, mantieni la collezione Foundation al confine di input e poi usa un array Swift tipizzato. Working with Foundation Types

Il tipo esterno di una collezione e il tipo dei suoi elementi rispondono a domande diverse. NSArray identifica una collezione ordinata di oggetti. [String] aggiunge un requisito specifico: ogni elemento deve poter essere usato come stringa Swift. Nessuna delle due dichiarazioni descrive una collezione di viste. L’inizializzatore dello stack di Apple richiede esplicitamente [NSView]. NSArray, init(views:)

Fase Tipo in questo esempio Contratto
Input Foundation NSArray Contiene gli oggetti sintetici forniti
Modello convalidato [String] Ogni elemento soddisfa il requisito del tipo stringa
Oggetti di presentazione [NSView] Ogni elemento è un’etichetta di testo appena creata
Contenitore del layout NSStackView Dispone queste viste in base alla sua configurazione

La distinzione utile è tra bridging e creazione. Il bridging collega rappresentazioni supportate, come le stringhe Foundation e le stringhe Swift. La creazione di oggetti NSTextField genera oggetti di presentazione con proprietà e durata proprie. Non esiste un bridging documentato da stringa a etichetta; il percorso supportato consiste nel chiamare l’inizializzatore dell’etichetta per ogni stringa. Array, init(labelWithString:)

Come convalidare gli elementi senza scartarne nessuno?

Usa un cast condizionale quando occorre verificare il contratto relativo al tipo degli elementi della collezione in ingresso. Apple documenta che as? restituisce un risultato opzionale e che un cast forzato non riuscito con as! provoca un errore di runtime. Gestisci esplicitamente l’errore prima di creare l’interfaccia. Handling Dynamically Typed Methods and Objects in Swift

Il codice seguente esegue una verifica completa, basata solo su Foundation, con dati di input sintetici. Salvalo come BridgeCheck.swift ed eseguilo su un Mac con gli strumenti Swift usando swift BridgeCheck.swift. Le asserzioni esprimono i risultati attesi.

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.")

Il primo cast richiede un array di stringhe, quindi verifica il tipo richiesto per gli elementi mentre ottiene la collezione tipizzata. L’input misto non soddisfa il contratto. L’input vuoto invece lo soddisfa, perché non contiene elementi incompatibili. Queste sono verifiche illustrative del contratto di input scelto, non misurazioni né risultati di un’applicazione.

Evita di sostituirlo con compactMap { $0 as? String } solo per fare in modo che una collezione mista venga accettata. Apple definisce compactMap come un’operazione che restituisce soltanto i risultati della trasformazione diversi da nil. In questo esempio, quel metodo scarterebbe il numero e mostrerebbe un elenco incompleto. Il filtraggio può essere appropriato quando viene richiesto esplicitamente; in questo caso, l’attività richiede di preservare ogni elemento fornito. compactMap(_:)

Dopo la convalida, usa map per creare un’etichetta per ogni nome. Apple documenta map come un metodo che applica una trasformazione agli elementi della sequenza e NSTextField(labelWithString:) come un inizializzatore che crea un’etichetta statica, senza a capo e con testo non selezionabile. La trasformazione è quindi adatta a questi tre nomi brevi. map(_:), init(labelWithString:)

Come creare la finestra Mac completa?

Questo esempio autonomo richiede macOS 14 o versioni successive e gli strumenti a riga di comando di Xcode installati. Crea l’interfaccia a livello di codice, quindi non servono storyboard, nib, risorse o file sorgente aggiuntivi. Il requisito di macOS 14 riguarda l’uso di NSApplication.activate() nell’esempio, che richiede l’attivazione senza garantirla. activate()

Salva il programma completo qui sotto come CollectionDemo.swift. Include intenzionalmente il proprio punto di ingresso. Quando lo adatti a un’app esistente, usa il punto di ingresso e il ciclo di vita già previsti dall’app invece di aggiungerne un secondo.

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

Compilalo e avvialo da Terminale nella directory che contiene il file:

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

Con l’input fornito, il risultato previsto è una finestra intitolata Synthetic Shapes che contiene le tre etichette nell’ordine originale. La chiusura della finestra richiede la terminazione dell’applicazione. L’esempio restituisce true dal metodo del delegato previsto da Apple per questo comportamento. applicationShouldTerminateAfterLastWindowClosed(_:)

Questi esempi sono stati esaminati, ma qui non sono stati compilati né eseguiti: l’ambiente di ricerca non dispone né di un compilatore Swift né di macOS AppKit. Esegui le verifiche Foundation e controlla la finestra sul Mac di destinazione prima di integrare l’esempio.

Quali contratti di layout e ciclo di vita devono restare intatti?

I valori predefiniti documentati dell’inizializzatore dello stack sono importanti: crea uno stack orizzontale, colloca le viste fornite nell’area di allineamento iniziale e disabilita la traduzione della maschera di ridimensionamento automatico per lo stack. Nell’esempio, l’orientamento viene impostato esplicitamente su verticale e l’allineamento su iniziale. Un cast dell’array non può determinare nessuna di queste impostazioni di layout. init(views:), alignment

Lo stack gestisce la disposizione delle viste che contiene. I vincoli tra lo stack e la vista del contenuto che lo racchiude ne stabiliscono la posizione nella finestra. Qui, i vincoli iniziale e superiore impostano un margine di 20 punti; le disuguaglianze finale e inferiore riservano spazio senza obbligare il breve elenco a riempire l’area del contenuto. Apple documenta che le viste stack usano Auto Layout e spiega che disabilitare la traduzione della maschera di ridimensionamento automatico permette di fornire i propri vincoli. NSStackView, translatesAutoresizingMaskIntoConstraints

Questa è una dimostrazione a dimensione fissa per tre etichette brevi. Prima di estendere l’esempio, occorre decidere come gestire testi più lunghi, molte righe o un’interfaccia ridimensionabile, per esempio con l’a capo o lo scorrimento. Eseguire con successo il bridging di una collezione più grande non garantisce che la sua presentazione entri nello spazio disponibile.

La durata degli oggetti è altrettanto distinta dalla conversione dei tipi. Apple dichiara debole la proprietà del delegato dell’applicazione. La proprietà statica appDelegate fornisce quindi un proprietario forte esplicito. Anche il delegato mantiene un riferimento alla finestra, che imposta isReleasedWhenClosed su false, seguendo le indicazioni di Apple per i client Swift che usano ARC. delegate, isReleasedWhenClosed

La creazione dell’interfaccia avviene in applicationDidFinishLaunching, che Apple documenta come punto in cui svolgere ulteriori inizializzazioni dopo l’avvio del ciclo principale e prima che vengano elaborati gli eventi. Il punto di ingresso del programma chiama run() per avviare quel ciclo di eventi. Mantieni queste responsabilità quando sposti la conversione in un’app AppKit esistente. applicationDidFinishLaunching(_:), run()

Anche le etichette rappresentano un’istantanea creata una volta sola. L’esempio assegna una sola volta il testo a ciascuna etichetta. Se in seguito cambia la collezione sorgente, occorre aggiungere codice esplicito per aggiornare le etichette o ricreare le viste disposte; né il bridging né l’operazione di mappatura stabiliscono una connessione continua.

Come individuare rapidamente un errore tra contenitore ed elementi?

Segui questi passaggi per identificare quale contratto è venuto meno. Separano la convalida dei tipi dai controlli di presentazione e ciclo di vita, così un problema di layout non ti costringe a riscrivere un bridging già riuscito.

Sintomo Verifica Passaggio successivo supportato
Un cast a [String] restituisce nil Ogni elemento di input soddisfa il requisito del tipo stringa? Rifiuta esplicitamente la collezione oppure definisci una politica di conversione intenzionale.
Si sta tentando di eseguire il cast di un array intero a String La destinazione richiede un valore o una collezione? Convalida [String]; scegli un elemento solo se l’attività ne richiede davvero uno.
Un inizializzatore dello stack rifiuta l’argomento L’argomento è [String] anziché [NSView]? Mappa prima ogni stringa a un’etichetta.
Le etichette appaiono in orizzontale È stato configurato l’orientamento dello stack? Imposta .vertical per questo elenco.
Le viste sono fuori posto o i vincoli sono in conflitto I vincoli della vista contenitore e le impostazioni della maschera di ridimensionamento automatico sono coerenti? Esamina il layout separatamente dal cast dell’input.
Le callback del delegato non vengono richiamate Un proprietario forte mantiene in vita il delegato? Mantieni in vita il delegato oltre l’assegnazione alla proprietà debole.
Le modifiche successive al modello non vengono visualizzate È presente codice esplicito per l’aggiornamento? Aggiorna intenzionalmente i valori delle etichette o le viste disposte.

Per questa attività, il percorso affidabile consiste nel convalidare una volta l’input Foundation, mantenere il modello tipizzato risultante, creare un’etichetta per ogni valore e configurare esplicitamente lo stack e il ciclo di vita dell’app. Ogni passaggio ha un contratto visibile, così è più facile diagnosticare gli errori quando l’interfaccia Mac si amplia.

Fonti