Back to Provenance

macOS Views & Components Reference

.agents/skills/swiftui-expert-skill/references/macos-views.md

3.3.110.6 KB
Original Source

macOS Views & Components Reference

macOS-specific SwiftUI views, file operations, drag & drop, and AppKit interop. Covers HSplitView, VSplitView, Table, PasteButton, file dialogs, cross-app drag & drop, and NSViewRepresentable.

Table of Contents


Quick Lookup Table

Views

APIAvailabilitymacOS-Only?Usage
HSplitViewmacOS 10.15+YesHorizontal resizable split layout with user-draggable dividers
VSplitViewmacOS 10.15+YesVertical resizable split layout with user-draggable dividers
TablemacOS 12.0+NoFull multi-column layout with sorting; on iOS compact, columns collapse
PasteButtonmacOS 10.15+NoSystem button that reads clipboard; does NOT auto-validate on macOS
CopyButtonmacOS 15.0+YesSystem button that copies Transferable content to clipboard

File Operations

APIAvailabilitymacOS-Only?Usage
fileImporter()macOS 11.0+NoNative NSOpenPanel with column/list/gallery view, sidebar, tags, QuickLook
fileExporter()macOS 11.0+NoNative NSSavePanel with format dropdown, tags field
fileMover()macOS 11.0+NoNative macOS move panel with Finder-like navigation
fileDialogMessage(_:)macOS 13.0+YesCustom message text in file dialogs
fileDialogConfirmationLabel(_:)macOS 13.0+YesCustom confirm button text in file dialogs
fileExporterFilenameLabel(_:)macOS 13.0+YesCustom filename field label in file exporter

Drag, Drop & Pasteboard

APIAvailabilitymacOS-Only?Usage
onDrag(_:) / draggable(_:)macOS 11.0+NoDrag image follows cursor; items draggable between apps
onDrop(of:delegate:) / dropDestination(for:action:)macOS 11.0+NoAccepts drops from any macOS app including Finder

AppKit Interop

APIAvailabilitymacOS-Only?Usage
NSViewRepresentablemacOS 10.15+YesWrap an AppKit NSView in SwiftUI
NSViewControllerRepresentablemacOS 10.15+YesWrap an AppKit NSViewController in SwiftUI
NSHostingControllermacOS 10.15+YesHost SwiftUI inside an AppKit view controller
NSHostingViewmacOS 10.15+YesHost SwiftUI inside an AppKit NSView hierarchy

HSplitView & VSplitView (macOS-only)

Resizable split layouts with user-draggable dividers. Use for IDE-style panes where all panels are equal peers. VSplitView works identically but splits vertically (use minHeight instead).

swift
HSplitView {
    FileTreeView()
        .frame(minWidth: 200)
    CodeEditorView()
        .frame(minWidth: 400)
    PreviewPane()
        .frame(minWidth: 200)
}

When to use which:

  • NavigationSplitView — sidebar-based navigation (sidebar drives content/detail)
  • HSplitView/VSplitView — IDE-style layouts where all panes are equal peers

Table

For Table basics (creation, selection, sorting, adaptive compact layout), see list-patterns.md. This section covers macOS-specific table styling.

Table styles

swift
// Bordered with visible grid lines (macOS-only)
Table(people) { /* columns */ }
    .tableStyle(.bordered)

// Bordered with alternating row backgrounds
Table(people) { /* columns */ }
    .tableStyle(.bordered(alternatesRowBackgrounds: true))

// Inset (no borders)
Table(people) { /* columns */ }
    .tableStyle(.inset)

// Hide column headers
Table(people) { /* columns */ }
    .tableColumnHeaders(.hidden)

PasteButton & CopyButton

PasteButton

System button that reads clipboard content via Transferable. On macOS, it does NOT auto-validate pasteboard changes (unlike iOS).

swift
struct ClipboardView: View {
    @State private var pastedText = ""

    var body: some View {
        HStack {
            PasteButton(payloadType: String.self) { strings in
                pastedText = strings[0]
            }
            Divider()
            Text(pastedText)
            Spacer()
        }
    }
}

CopyButton (macOS 15.0+, macOS-only)

System button that copies Transferable content to the clipboard.

swift
struct CopyableContent: View {
    let shareableText = "Hello, world!"

    var body: some View {
        HStack {
            Text(shareableText)
            CopyButton(item: shareableText)
        }
    }
}

File Operations

fileImporter

On macOS, presents a native NSOpenPanel with column/list/gallery view, sidebar favorites, tags, and QuickLook.

swift
.fileImporter(
    isPresented: $showImporter,
    allowedContentTypes: [.pdf],
    allowsMultipleSelection: false
) { result in
    if case .success(let urls) = result, let url = urls.first {
        guard url.startAccessingSecurityScopedResource() else { return }
        defer { url.stopAccessingSecurityScopedResource() }
        // use url
    }
}

Important: Always call startAccessingSecurityScopedResource() on returned URLs, and stopAccessingSecurityScopedResource() when done. These are security-scoped bookmarks — access fails without this.

fileExporter

On macOS, presents a native NSSavePanel with format dropdown and tags.

swift
.fileExporter(
    isPresented: $showExporter,
    document: document,
    contentType: .plainText,
    defaultFilename: "MyFile.txt"
) { result in
    // handle Result<URL, Error>
}

File dialog customization (macOS-only)

Customize text in file dialogs with these macOS-specific modifiers:

swift
// Custom message and confirm button on file importer
.fileImporter(
    isPresented: $showImporter,
    allowedContentTypes: [.image]
) { result in
    // handle result
}
.fileDialogMessage("Select an image to use as your profile photo")
.fileDialogConfirmationLabel("Use This Photo")

// Custom filename label on file exporter
.fileExporter(
    isPresented: $showExporter,
    document: myDocument,
    contentType: .png
) { result in
    // handle result
}
.fileExporterFilenameLabel("Export As:")

Drag, Drop & Pasteboard

On macOS, drag and drop works across applications (e.g., drag from your app to Finder, Mail, or other apps).

Modern approach (Transferable)

swift
// Drag source
struct DraggableCard: View {
    let item: MyItem

    var body: some View {
        Text(item.title)
            .draggable(item)  // Requires Transferable conformance
    }
}

// Drop target
struct DropZone: View {
    @State private var droppedItems: [MyItem] = []

    var body: some View {
        VStack {
            ForEach(droppedItems) { item in
                Text(item.title)
            }
        }
        .dropDestination(for: MyItem.self) { items, location in
            droppedItems.append(contentsOf: items)
            return true
        }
        .frame(width: 300, height: 200)
        .border(.secondary)
    }
}

Legacy approach (NSItemProvider)

swift
// Drag source
Image(systemName: "doc")
    .onDrag {
        NSItemProvider(object: fileURL as NSURL)
    }

// Drop target
Text("Drop files here")
    .onDrop(of: [.fileURL], isTargeted: nil) { providers in
        // handle providers
        return true
    }

AppKit Interop

NSViewRepresentable (macOS-only)

Wraps an AppKit NSView for use in SwiftUI. Implement makeNSView(context:) and updateNSView(_:context:).

swift
struct WebView: NSViewRepresentable {
    let url: URL
    func makeNSView(context: Context) -> WKWebView { WKWebView() }
    func updateNSView(_ nsView: WKWebView, context: Context) {
        nsView.load(URLRequest(url: url))
    }
}

NSViewRepresentable with Coordinator

Use a Coordinator to forward delegate/target-action callbacks to SwiftUI.

swift
struct SearchField: NSViewRepresentable {
    @Binding var text: String

    func makeNSView(context: Context) -> NSSearchField {
        let field = NSSearchField()
        field.delegate = context.coordinator
        return field
    }
    func updateNSView(_ nsView: NSSearchField, context: Context) {
        nsView.stringValue = text
    }
    func makeCoordinator() -> Coordinator { Coordinator(text: $text) }

    class Coordinator: NSObject, NSSearchFieldDelegate {
        var text: Binding<String>
        init(text: Binding<String>) { self.text = text }
        func controlTextDidChange(_ obj: Notification) {
            if let field = obj.object as? NSSearchField {
                text.wrappedValue = field.stringValue
            }
        }
    }
}

Warning: Never set frame/bounds directly on the managed NSView — SwiftUI owns the layout.

NSViewControllerRepresentable (macOS-only)

Wraps an AppKit NSViewController for use in SwiftUI.

swift
struct MapViewWrapper: NSViewControllerRepresentable {
    func makeNSViewController(context: Context) -> MapViewController {
        MapViewController()
    }

    func updateNSViewController(_ nsViewController: MapViewController, context: Context) {
        // Update the controller when SwiftUI state changes
    }
}

NSHostingController & NSHostingView (macOS-only)

Host SwiftUI content inside AppKit (reverse direction — AppKit app embedding SwiftUI views).

swift
// Host SwiftUI as a view controller
let hostingController = NSHostingController(rootView: MySwiftUIView())
window.contentViewController = hostingController

// Host SwiftUI directly as an NSView
let hostingView = NSHostingView(rootView: MySwiftUIView())
someNSView.addSubview(hostingView)

Best Practices

  • Use NavigationSplitView for sidebar-driven navigation — reserve HSplitView/VSplitView for IDE-style equal peer panes
  • Make Table adaptive — handle compact size classes by showing combined info in the first column
  • Always call startAccessingSecurityScopedResource() on URLs from fileImporter — they are security-scoped
  • Use Transferable for drag & drop (modern) — fall back to NSItemProvider only for legacy compatibility
  • Use NSViewRepresentable with Coordinator when you need delegate callbacks from AppKit views
  • Never set frame/bounds directly on views managed by NSViewRepresentable — SwiftUI owns the layout
  • Prefer native SwiftUI over AppKit interop when possible — only use NSViewRepresentable for features SwiftUI doesn't provide