Um ein Foundation-Array in einem Mac-Fenster anzuzeigen, validiere seine Elementtypen und erstelle dann für jeden Wert eine Ansicht. Durch das Bridging von NSArray zu [String] erhältst du Strings; für ihre Anzeige ist eine separate Umwandlung in AppKit-Labels erforderlich.

Diese Anleitung richtet sich an Entwickler, die ihrer eigenen macOS-App eine kleine Liste hinzufügen. Die konkrete Aufgabe besteht darin, drei fiktive Namen – Circle, Square und Triangle – vertikal anzuordnen. Das Beispiel macht Collection-Typen, Elementtypen, Layout und Objektlebensdauer explizit.

Was ändert sich beim Bridging einer Collection tatsächlich?

Apple beschreibt Array und NSArray als einander entsprechende Swift- und Foundation-Typen, ebenso wie String und NSString einander entsprechen. Für gewöhnlichen Swift-Code empfiehlt Apple Swift-Wertetypen und den Zugriff auf Foundation-Referenztypen, wenn deren Verhalten benötigt wird. Bei dieser Aufgabe bleibt die Foundation-Collection an der Eingabegrenze; anschließend wird ein typisiertes Swift-Array verwendet. Mit Foundation-Typen arbeiten

Der äußere Typ einer Collection und der Typ ihrer Elemente beantworten unterschiedliche Fragen. NSArray bezeichnet eine geordnete Collection von Objekten. [String] ergänzt eine konkrete Anforderung: Jedes Element muss als Swift-String verwendbar sein. Keine der beiden Deklarationen beschreibt eine Collection von Ansichten. Apples Stack-Initializer verlangt ausdrücklich [NSView]. NSArray, init(views:)

Phase Typ in diesem Beispiel Vertrag
Foundation-Eingabe NSArray Enthält die übergebenen fiktiven Objekte
Validiertes Modell [String] Jedes Element erfüllt die String-Anforderung
Darstellungsobjekte [NSView] Für jedes Element wird ein neues Textlabel erstellt
Layout-Container NSStackView Ordnet diese Ansichten gemäß seiner Konfiguration an

Wichtig ist die Unterscheidung zwischen Bridging und Erzeugung. Bridging verbindet unterstützte Darstellungen, etwa Foundation-Strings und Swift-Strings. Beim Erzeugen von NSTextField-Objekten entstehen Darstellungsobjekte mit eigenen Eigenschaften und eigener Lebensdauer. Es gibt kein dokumentiertes Bridging von Strings zu Labels; der unterstützte Weg besteht darin, für jeden String den Label-Initializer aufzurufen. Array, init(labelWithString:)

Wie validierst du die Elemente, ohne Einträge zu verwerfen?

Verwende einen bedingten Cast, wenn der Elementvertrag der eingehenden Collection geprüft werden muss. Apple dokumentiert, dass as? ein optionales Ergebnis liefert und ein erfolgloser erzwungener Cast mit as! einen Laufzeitfehler auslöst. Behandle Fehler ausdrücklich, bevor du die Benutzeroberfläche erstellst. Umgang mit dynamisch typisierten Methoden und Objekten in Swift

Der folgende Foundation-only-Check mit fiktiven Eingabedaten ist vollständig. Speichere ihn als BridgeCheck.swift und führe ihn auf einem Mac mit den Swift-Tools mit swift BridgeCheck.swift aus. Die Assertions drücken die erwarteten Ergebnisse aus.

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

Der erste Cast verlangt ein Array von Strings und prüft damit beim Ermitteln der typisierten Collection auch den erforderlichen Elementtyp. Die gemischte Eingabe erfüllt diesen Vertrag nicht. Die leere Eingabe erfüllt ihn: Sie enthält kein inkompatibles Element. Dies sind beispielhafte Prüfungen des gewählten Eingabevertrags, keine Messungen oder Anwendungsergebnisse.

Verwende nicht stattdessen compactMap { $0 as? String }, nur damit eine gemischte Collection akzeptiert wird. Apple definiert compactMap so, dass nur die Transformationsergebnisse zurückgegeben werden, die nicht nil sind. In diesem Beispiel würde dadurch die Zahl verworfen und eine unvollständige Liste angezeigt. Filtern kann sinnvoll sein, wenn es ausdrücklich gewünscht ist; hier müssen jedoch alle übergebenen Elemente erhalten bleiben. compactMap(_:)

Sobald die Validierung erfolgreich ist, verwende map, um für jeden Namen ein Label zu erstellen. Apple dokumentiert, dass map eine Transformation auf die Elemente der Sequenz anwendet und NSTextField(labelWithString:) ein statisches, nicht umbrechendes Label mit nicht auswählbarem Text erstellt. Damit eignet sich diese Transformation für die drei kurzen Namen. map(_:), init(labelWithString:)

Wie erstellst du das vollständige Mac-Fenster?

Dieses eigenständige Beispiel erfordert macOS 14 oder neuer sowie installierte Xcode Command Line Tools. Die Benutzeroberfläche wird programmatisch erstellt; Storyboard, Nib, Asset oder zusätzliche Quelldatei sind daher nicht erforderlich. Die Voraussetzung macOS 14 gilt wegen der Verwendung von NSApplication.activate() im Beispiel, die eine Aktivierung anfordert, aber nicht garantiert. activate()

Speichere das vollständige Programm unten als CollectionDemo.swift. Es enthält absichtlich einen eigenen Einstiegspunkt. Wenn du es an eine bestehende App anpasst, verwende deren etablierten Einstiegspunkt und Lebenszyklus, statt einen zweiten hinzuzufügen.

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

Kompiliere und starte es im Verzeichnis, in dem sich die Datei befindet, über Terminal:

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

Bei der angegebenen Eingabe ist das beabsichtigte Ergebnis ein Fenster mit dem Titel Synthetic Shapes, das die drei Labels in ihrer ursprünglichen Reihenfolge enthält. Beim Schließen des Fensters wird die Beendigung der Anwendung angefordert. Das Beispiel gibt aus der Delegate-Methode, die Apple für dieses Verhalten bereitstellt, true zurück. applicationShouldTerminateAfterLastWindowClosed(_:)

Diese Beispiele wurden geprüft, hier jedoch nicht kompiliert oder ausgeführt: In der Rechercheumgebung sind weder ein Swift-Compiler noch macOS AppKit verfügbar. Führe die Foundation-Checks aus und prüfe das Fenster auf dem Ziel-Mac, bevor du das Beispiel integrierst.

Welche Layout- und Lebenszyklusverträge müssen bestehen bleiben?

Die dokumentierten Standardwerte des Stack-Initializers sind wichtig: Er erstellt einen horizontalen Stack, platziert die übergebenen Ansichten im führenden Gravity-Bereich und deaktiviert die Übersetzung der Autoresizing-Maske für den Stack. Im Beispiel werden die Ausrichtung explizit auf vertikal und die Alignment-Einstellung auf führend gesetzt. Ein Array-Cast kann keine dieser Layouteinstellungen festlegen. init(views:), alignment

Der Stack verwaltet die Anordnung seiner enthaltenen Ansichten. Constraints zwischen dem Stack und der umschließenden Inhaltsansicht legen fest, wo diese Anordnung im Fenster positioniert wird. Hier setzen die führenden und oberen Constraints einen Abstand von 20 Punkten; die Ungleichungen für rechts und unten lassen Platz, ohne zu verlangen, dass die kurze Liste den Inhaltsbereich ausfüllt. Apple dokumentiert, dass Stack-Views Auto Layout verwenden, und erklärt, dass du durch Deaktivieren der Übersetzung der Autoresizing-Maske eigene Constraints festlegen kannst. NSStackView, translatesAutoresizingMaskIntoConstraints

Dies ist eine Demonstration mit fester Größe für drei kurze Labels. Längerer Text, viele Zeilen oder eine größenveränderliche Benutzeroberfläche erfordern vor einer Erweiterung des Beispiels eine separate Layoutentscheidung, etwa für Umbruch oder Scrollen. Erfolgreiches Bridging einer größeren Collection bedeutet nicht, dass sie auch in die Darstellung passt.

Die Lebensdauer ist ebenso unabhängig von der Typumwandlung. Apple deklariert die Delegate-Property der Anwendung als weak. Die statische Property appDelegate stellt daher einen expliziten starken Besitzer bereit. Das Delegate behält außerdem das Fenster, und das Fenster setzt isReleasedWhenClosed auf false, gemäß Apples Empfehlungen für Swift- und ARC-Clients. delegate, isReleasedWhenClosed

Die Benutzeroberfläche wird in applicationDidFinishLaunching erstellt, das Apple als Ort für weitere Initialisierungen dokumentiert, nachdem die Main Loop gestartet wurde und bevor sie Ereignisse verarbeitet. Der Einstiegspunkt des Programms ruft run() auf, um diese Ereignisschleife zu starten. Wenn du die Umwandlung in eine bestehende AppKit-App übernimmst, behalte diese Zuständigkeiten bei. applicationDidFinishLaunching(_:), run()

Die Labels bilden außerdem einen erstellten Schnappschuss. Das Beispiel weist den Text jedes Labels einmal zu. Wenn sich die Quell-Collection danach ändert, ist expliziter Code nötig, um die Labels zu aktualisieren oder die angeordneten Ansichten neu zu erstellen; das Bridging und die Mapping-Operation stellen keine fortlaufende Verbindung her.

Wie findest du schnell einen Fehler zwischen Container- und Elementtyp?

Gehe die folgende Reihenfolge durch, um festzustellen, welcher Vertrag verletzt wurde. Sie trennt Typvalidierung von Darstellung und Lebenszyklusprüfungen, damit ein Layoutproblem nicht dazu führt, dass du ein erfolgreiches Bridging neu schreibst.

Symptom Prüfung Unterstützter nächster Schritt
Ein Cast zu [String] liefert nil Erfüllt jedes Eingabeelement die String-Anforderung? Weise die Collection ausdrücklich zurück oder lege eine bewusste Umwandlungsrichtlinie fest.
Ein ganzes Array wird zu String gecastet Benötigt das Ziel einen einzelnen Wert oder eine Collection? Validiere [String]; wähle nur dann ein Element aus, wenn die Aufgabe tatsächlich eines benötigt.
Ein Stack-Initializer weist das Argument zurück Ist das Argument [String] statt [NSView]? Wandle zuerst jeden String in ein Label um.
Labels erscheinen horizontal Wurde die Stack-Ausrichtung konfiguriert? Setze für diese Liste .vertical.
Ansichten sind falsch positioniert oder Constraints stehen im Konflikt Sind die Constraints der umschließenden Ansicht und die Einstellungen der Autoresizing-Maske konsistent? Prüfe das Layout unabhängig vom Eingabe-Cast.
Delegate-Callbacks treffen nicht ein Hält ein starker Besitzer das Delegate am Leben? Sorge dafür, dass das Delegate über die Zuweisung an die weak Property hinaus erhalten bleibt.
Spätere Modelländerungen werden nicht angezeigt Gibt es expliziten Aktualisierungscode? Aktualisiere die Labelwerte oder die angeordneten Ansichten gezielt.

Für diese Aufgabe besteht der zuverlässige Ablauf darin, die Foundation-Eingabe einmal zu validieren, das resultierende typisierte Modell zu behalten, für jeden Wert ein Label zu erstellen und Stack sowie App-Lebenszyklus explizit zu konfigurieren. Jeder Schritt hat einen klaren Vertrag. So lassen sich Fehler leichter diagnostizieren, wenn die Mac-Benutzeroberfläche wächst.

Quellen