Als je een Foundation-array in een Mac-venster wilt weergeven, controleer je de elementtypen en maak je vervolgens voor elke waarde een weergave. Door NSArray te bridgen naar [String] krijg je strings; om ze weer te geven, moet je ze afzonderlijk omzetten in AppKit-labels.

Deze walkthrough is bedoeld voor een ontwikkelaar die een kleine lijst aan diens eigen macOS-app toevoegt. De concrete taak is om drie synthetische namen—Circle, Square en Triangle—in een verticale stapel weer te geven. Het voorbeeld maakt de collectietypen, elementtypen, indeling en objectlevensduur expliciet.

Wat verandert het bridgen van een collectie eigenlijk?

Apple documenteert Array en NSArray als overeenkomstige Swift- en Foundation-typen, net zoals String en NSString overeenkomen. Apple raadt aan om in gewone Swift-code Swift-waardetypen te gebruiken en Foundation-referentietypen te benaderen wanneer hun gedrag nodig is. Houd voor deze taak de Foundation-collectie aan de invoergrens en gebruik daarna een getypeerde Swift-array. Werken met Foundation-typen

Het buitenste type van een collectie en het elementtype ervan beantwoorden verschillende vragen. NSArray duidt een geordende collectie objecten aan. [String] voegt een specifieke eis toe: elk element moet bruikbaar zijn als een Swift-string. Geen van beide declaraties beschrijft een collectie weergaven. De initializer van Apple voor een stapel vereist expliciet [NSView]. NSArray, init(views:)

Fase Type in dit voorbeeld Contract
Foundation-invoer NSArray Bevat de aangeleverde synthetische objecten
Gevalideerd model [String] Elk element voldoet aan de stringvereiste
Presentatieobjecten [NSView] Elk element is een nieuw aangemaakt tekstlabel
Indelingscontainer NSStackView Schikt die weergaven volgens de configuratie

Het nuttige onderscheid is dat tussen bridgen en construeren. Bridgen verbindt ondersteunde representaties, zoals Foundation-strings en Swift-strings. Het construeren van NSTextField-objecten maakt presentatieobjecten met hun eigen eigenschappen en levensduur. Er bestaat geen gedocumenteerde bridge van string naar label; de ondersteunde aanpak is om voor elke string de labelinitializer aan te roepen. Array, init(labelWithString:)

Hoe controleer je de elementen zonder items weg te laten?

Gebruik een conditionele cast als het elementcontract van de binnenkomende collectie moet worden gecontroleerd. Apple documenteert dat as? een optioneel resultaat oplevert en dat een mislukte geforceerde cast met as! een runtimefout veroorzaakt. Handel fouten expliciet af voordat je de interface opbouwt. Omgaan met dynamisch getypeerde methoden en objecten in Swift

Hieronder staat een volledige controle die alleen Foundation gebruikt, met synthetische invoer. Sla deze op als BridgeCheck.swift en voer hem op een Mac met de Swift-tools uit met swift BridgeCheck.swift. De assertions geven de verwachte uitkomsten weer.

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

De eerste cast vraagt om een array met strings en controleert daarmee het vereiste elementtype bij het verkrijgen van de getypeerde collectie. De gemengde invoer voldoet niet aan dat contract. De lege invoer voldoet er wel aan: deze bevat geen incompatibele elementen. Dit zijn illustratieve controles van het gekozen invoercontract, geen metingen of resultaten van een applicatie.

Vervang dit niet door compactMap { $0 as? String } alleen maar om een gemengde collectie te laten slagen. Apple definieert compactMap als een functie die alleen de niet-nil transformatieresultaten teruggeeft. In dit voorbeeld zou die aanpak het getal weggooien en een onvolledige lijst weergeven. Filteren kan passend zijn als daar expliciet om wordt gevraagd; hier vereist de taak dat elk aangeleverd item behouden blijft. compactMap(_:)

Zodra de validatie slaagt, gebruik je map om één label per naam te maken. Apple documenteert map als een functie die een transformatie toepast op de elementen van een reeks, en NSTextField(labelWithString:) als een initializer die een statisch label zonder tekstterugloop en met niet-selecteerbare tekst maakt. Dat maakt de transformatie geschikt voor deze drie korte namen. map(_:), init(labelWithString:)

Hoe bouw je het volledige Mac-venster?

Voor dit zelfstandige voorbeeld heb je macOS 14 of hoger en geïnstalleerde Xcode-commandlinetools nodig. Het bouwt de interface programmatisch op, dus er zijn geen storyboard, nib, asset of extra bronbestanden nodig. De vereiste macOS-versie 14 dekt het gebruik van NSApplication.activate() in dit voorbeeld, dat om activering vraagt zonder die te garanderen. activate()

Sla het volledige programma hieronder op als CollectionDemo.swift. Het bevat bewust een eigen ingangspunt. Gebruik bij aanpassing voor een bestaande app het bestaande ingangspunt en de bestaande levenscyclus van die app in plaats van een tweede toe te voegen.

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

Compileer en start het programma vanuit Terminal in de map met het bestand:

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

Met de aangeleverde invoer is het beoogde resultaat een venster met de titel Synthetic Shapes, met de drie labels in de oorspronkelijke volgorde. Het sluiten van het venster vraagt om beëindiging van de applicatie. Het voorbeeld retourneert true uit de delegatemethode die Apple voor dat gedrag aanbiedt. applicationShouldTerminateAfterLastWindowClosed(_:)

Deze voorbeelden zijn hier bekeken, maar niet gecompileerd of uitgevoerd: de onderzoeksomgeving beschikt niet over een Swift-compiler of macOS AppKit. Voer de Foundation-controles uit en bekijk het venster op de doel-Mac voordat je het voorbeeld integreert.

Welke contracten voor indeling en levensduur moeten intact blijven?

De gedocumenteerde standaardinstellingen van de stackinitializer zijn van belang: deze maakt een horizontale stapel, plaatst de aangeleverde weergaven in het gebied met de leidende zwaartekracht en schakelt het vertalen van het autoresizing-mask voor de stapel uit. In het voorbeeld wordt de oriëntatie expliciet op verticaal en de uitlijning op leading ingesteld. Een arraycast kan geen van die indelingsinstellingen kiezen. init(views:), alignment

De stapel beheert de rangschikking van de weergaven die erin staan. Constraints tussen de stapel en de omsluitende contentweergave bepalen waar die rangschikking in het venster komt. Hier stellen de leading- en topconstraints een marge van 20 punten in; de trailing- en bottom-ongelijkheden laten ruimte over zonder te vereisen dat de korte lijst het contentgebied vult. Apple documenteert dat stackviews Auto Layout gebruiken en legt uit dat je zelf constraints kunt opgeven als je het vertalen van het autoresizing-mask uitschakelt. NSStackView, translatesAutoresizingMaskIntoConstraints

Dit is een demonstratie met een vaste grootte en drie korte labels. Langere tekst, veel rijen of een aanpasbare interface vereisen een afzonderlijke indelingskeuze—zoals tekstterugloop of scrollen—voordat je het voorbeeld uitbreidt. Het succesvol bridgen van een grotere collectie garandeert niet dat deze in de presentatie past.

Levensduur staat los van typeconversie. Apple declareert de delegate-eigenschap van de applicatie als weak. De statische eigenschap appDelegate zorgt daarom voor een expliciete sterke eigenaar. De delegate behoudt ook het venster, en het venster stelt isReleasedWhenClosed in op false, volgens de richtlijnen van Apple voor Swift- en ARC-clients. delegate, isReleasedWhenClosed

De interface wordt opgebouwd in applicationDidFinishLaunching, dat Apple documenteert als een plek voor verdere initialisatie nadat de hoofdloop is gestart en voordat deze gebeurtenissen verwerkt. Het ingangspunt van het programma roept run() aan om die gebeurtenislus te starten. Behoud die verantwoordelijkheden wanneer je de conversie naar een bestaande AppKit-app verplaatst. applicationDidFinishLaunching(_:), run()

De labels vormen ook een geconstrueerde momentopname. Het voorbeeld kent de tekst van elk label één keer toe. Als je de broncollectie daarna wijzigt, is expliciete code nodig om de labels bij te werken of de gerangschikte weergaven opnieuw op te bouwen; de bridge en de mapbewerking brengen geen doorlopende verbinding tot stand.

Hoe vind je snel een verwarring tussen container en element?

Gebruik de volgende volgorde om te bepalen welk contract niet is nagekomen. Zo houd je typevalidatie gescheiden van controles op presentatie en levensduur, zodat een indelingsprobleem je er niet toe brengt een geslaagde bridge opnieuw te schrijven.

Symptoom Controle Ondersteunde vervolgstap
Een cast naar [String] retourneert nil Voldoet elk invoerelement aan de stringvereiste? Wijs de collectie expliciet af of definieer een bewust conversiebeleid.
Een volledige array wordt naar String gecast Heeft de bestemming één waarde nodig of een collectie? Valideer [String]; kies alleen een element als de taak er echt één nodig heeft.
Een stackinitializer accepteert het argument niet Is het argument [String] in plaats van [NSView]? Map eerst elke string naar een label.
Labels verschijnen horizontaal Is de stackoriëntatie geconfigureerd? Stel voor deze lijst .vertical in.
Weergaven staan verkeerd of constraints conflicteren Zijn de constraints van de omsluitende weergave en de instellingen voor het autoresizing-mask consistent? Inspecteer de indeling los van de invoercast.
Delegate-callbacks komen niet binnen Behoudt een sterke eigenaar de delegate? Houd de delegate in leven nadat deze aan de weak-eigenschap is toegewezen.
Latere modelwijzigingen verschijnen niet Bestaat er expliciete code om bij te werken? Werk de labelwaarden of de gerangschikte weergaven bewust bij.

Voor deze taak is de betrouwbare aanpak om de Foundation-invoer één keer te valideren, het resulterende getypeerde model te behouden, één label per waarde te maken en de stack en levensduur van de app expliciet te configureren. Elke stap heeft een zichtbaar contract, waardoor fouten gemakkelijker te diagnosticeren zijn wanneer de Mac-interface groeit.

Bronnen