appkit-bridge

Bridging AppKit components into SwiftUI macOS apps. Covers NSViewRepresentable and NSViewControllerRepresentable protocols for hosting AppKit views in SwiftUI, NSHostingView/NSHostingController for hosting SwiftUI in AppKit, NSPanel for floating windows, NSWindow configuration (styleMask, level, collectionBehavior), responder chain integration, NSEvent monitoring (global and local), NSAnimationContext for AppKit animations, NSPopover, NSStatusItem for menu bar, and NSGlassEffectView for AppKit Liquid Glass. Use when SwiftUI lacks a native equivalent, building floating panels, custom window chrome, or integrating legacy AppKit components.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "appkit-bridge" with this command: npx skills add makgunay/claude-swift-skills/makgunay-claude-swift-skills-appkit-bridge

AppKit Bridge — SwiftUI ↔ AppKit Integration

Critical Constraints

  • ❌ DO NOT use NSViewController as app architecture → ✅ Use SwiftUI App + Scene, bridge only where needed
  • ❌ DO NOT use NSView subclass when SwiftUI modifier exists → ✅ Check SwiftUI first, bridge as last resort
  • ❌ DO NOT forget makeCoordinator() for delegate callbacks → ✅ Use Coordinator pattern for NSViewRepresentable
  • ❌ DO NOT call makeNSView to update → ✅ Use updateNSView(_:context:) for state changes

NSViewRepresentable (AppKit → SwiftUI)

import SwiftUI
import AppKit

struct WrappedNSView: NSViewRepresentable {
    var text: String

    func makeNSView(context: Context) -> NSTextField {
        let field = NSTextField()
        field.delegate = context.coordinator
        return field
    }

    func updateNSView(_ nsView: NSTextField, context: Context) {
        nsView.stringValue = text
    }

    func makeCoordinator() -> Coordinator { Coordinator(self) }

    class Coordinator: NSObject, NSTextFieldDelegate {
        var parent: WrappedNSView
        init(_ parent: WrappedNSView) { self.parent = parent }

        func controlTextDidChange(_ obj: Notification) {
            // Handle text changes
        }
    }
}

NSHostingView (SwiftUI → AppKit)

let swiftUIView = MySwiftUIView()
let hostingView = NSHostingView(rootView: swiftUIView)
hostingView.translatesAutoresizingMaskIntoConstraints = false

// Add to AppKit view hierarchy
parentView.addSubview(hostingView)
NSLayoutConstraint.activate([
    hostingView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
    hostingView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
    hostingView.topAnchor.constraint(equalTo: parentView.topAnchor),
    hostingView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor),
])

NSPanel — Floating Window

class FloatingPanel: NSPanel {
    init(contentView: NSView) {
        super.init(
            contentRect: NSRect(x: 0, y: 0, width: 600, height: 400),
            styleMask: [.nonactivatingPanel, .titled, .closable, .fullSizeContentView],
            backing: .buffered, defer: true
        )
        titlebarAppearsTransparent = true
        titleVisibility = .hidden
        isOpaque = false
        backgroundColor = .clear
        level = .floating
        collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .transient]
        isMovableByWindowBackground = true
        hidesOnDeactivate = false
        self.contentView = contentView
        center()
    }
    override var canBecomeKey: Bool { true }
    override var canBecomeMain: Bool { false }
}

// Host SwiftUI content
let panel = FloatingPanel(contentView: NSHostingView(rootView: MyView()))

Show/Hide with Animation

extension FloatingPanel {
    func showCentered() {
        center()
        alphaValue = 0
        makeKeyAndOrderFront(nil)
        NSAnimationContext.runAnimationGroup { ctx in
            ctx.duration = 0.15
            ctx.timingFunction = CAMediaTimingFunction(name: .easeOut)
            animator().alphaValue = 1
        }
    }

    func hideAnimated() {
        NSAnimationContext.runAnimationGroup({ ctx in
            ctx.duration = 0.1
            ctx.timingFunction = CAMediaTimingFunction(name: .easeIn)
            animator().alphaValue = 0
        }, completionHandler: { self.orderOut(nil) })
    }
}

NSPopover

let popover = NSPopover()
popover.contentViewController = NSHostingController(rootView: PopoverContent())
popover.behavior = .transient
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
popover.contentViewController?.view.window?.makeKey()

Window Positioning

extension NSPanel {
    func centerOnActiveScreen() {
        guard let screen = NSScreen.main ?? NSScreen.screens.first else { return }
        let frame = screen.visibleFrame
        setFrameOrigin(NSPoint(x: frame.midX - self.frame.width / 2,
                                y: frame.midY - self.frame.height / 2))
    }
}

Decision Tree

Need a floating overlay? → NSPanel + NSHostingView
Need a menu bar popover? → NSPopover + NSHostingController
Need custom window chrome? → NSWindow subclass + titlebarAppearsTransparent
Need AppKit control in SwiftUI? → NSViewRepresentable
Need SwiftUI view in AppKit? → NSHostingView or NSHostingController
Need glass effect in AppKit? → NSGlassEffectView (see liquid-glass skill)

References

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

macos-permissions

No summary provided by upstream source.

Repository SourceNeeds Review
General

macos-app-structure

No summary provided by upstream source.

Repository SourceNeeds Review
General

swiftui-core

No summary provided by upstream source.

Repository SourceNeeds Review
General

liquid-glass

No summary provided by upstream source.

Repository SourceNeeds Review