macOS Views & Components Reference
macOS Views & Components Reference
Section titled “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, andNSViewRepresentable.
Table of Contents
Section titled “Table of Contents”- Quick Lookup Table
- HSplitView & VSplitView (macOS-only)
- Table
- PasteButton & CopyButton
- File Operations
- Drag, Drop & Pasteboard
- AppKit Interop
- Best Practices
Quick Lookup Table
Section titled “Quick Lookup Table”| API | Availability | macOS-Only? | Usage |
|---|---|---|---|
HSplitView |
macOS 10.15+ | Yes | Horizontal resizable split layout with user-draggable dividers |
VSplitView |
macOS 10.15+ | Yes | Vertical resizable split layout with user-draggable dividers |
Table |
macOS 12.0+ | No | Full multi-column layout with sorting; on iOS compact, columns collapse |
PasteButton |
macOS 10.15+ | No | System button that reads clipboard; does NOT auto-validate on macOS |
CopyButton |
macOS 15.0+ | Yes | System button that copies Transferable content to clipboard |
File Operations
Section titled “File Operations”| API | Availability | macOS-Only? | Usage |
|---|---|---|---|
fileImporter() |
macOS 11.0+ | No | Native NSOpenPanel with column/list/gallery view, sidebar, tags, QuickLook |
fileExporter() |
macOS 11.0+ | No | Native NSSavePanel with format dropdown, tags field |
fileMover() |
macOS 11.0+ | No | Native macOS move panel with Finder-like navigation |
fileDialogMessage(_:) |
macOS 13.0+ | Yes | Custom message text in file dialogs |
fileDialogConfirmationLabel(_:) |
macOS 13.0+ | Yes | Custom confirm button text in file dialogs |
fileExporterFilenameLabel(_:) |
macOS 13.0+ | Yes | Custom filename field label in file exporter |
Drag, Drop & Pasteboard
Section titled “Drag, Drop & Pasteboard”| API | Availability | macOS-Only? | Usage |
|---|---|---|---|
onDrag(_:) / draggable(_:) |
macOS 11.0+ | No | Drag image follows cursor; items draggable between apps |
onDrop(of:delegate:) / dropDestination(for:action:) |
macOS 11.0+ | No | Accepts drops from any macOS app including Finder |
AppKit Interop
Section titled “AppKit Interop”| API | Availability | macOS-Only? | Usage |
|---|---|---|---|
NSViewRepresentable |
macOS 10.15+ | Yes | Wrap an AppKit NSView in SwiftUI |
NSViewControllerRepresentable |
macOS 10.15+ | Yes | Wrap an AppKit NSViewController in SwiftUI |
NSHostingController |
macOS 10.15+ | Yes | Host SwiftUI inside an AppKit view controller |
NSHostingView |
macOS 10.15+ | Yes | Host SwiftUI inside an AppKit NSView hierarchy |
HSplitView & VSplitView (macOS-only)
Section titled “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).
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
For Table basics (creation, selection, sorting, adaptive compact layout), see list-patterns.md. This section covers macOS-specific table styling.
Table styles
Section titled “Table styles”// Bordered with visible grid lines (macOS-only)Table(people) { /* columns */ } .tableStyle(.bordered)
// Bordered with alternating row backgroundsTable(people) { /* columns */ } .tableStyle(.bordered(alternatesRowBackgrounds: true))
// Inset (no borders)Table(people) { /* columns */ } .tableStyle(.inset)
// Hide column headersTable(people) { /* columns */ } .tableColumnHeaders(.hidden)PasteButton & CopyButton
Section titled “PasteButton & CopyButton”PasteButton
Section titled “PasteButton”System button that reads clipboard content via Transferable. On macOS, it does NOT auto-validate pasteboard changes (unlike iOS).
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)
Section titled “CopyButton (macOS 15.0+, macOS-only)”System button that copies Transferable content to the clipboard.
struct CopyableContent: View { let shareableText = "Hello, world!"
var body: some View { HStack { Text(shareableText) CopyButton(item: shareableText) } }}File Operations
Section titled “File Operations”fileImporter
Section titled “fileImporter”On macOS, presents a native NSOpenPanel with column/list/gallery view, sidebar favorites, tags, and QuickLook.
.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, andstopAccessingSecurityScopedResource()when done. These are security-scoped bookmarks — access fails without this.
fileExporter
Section titled “fileExporter”On macOS, presents a native NSSavePanel with format dropdown and tags.
.fileExporter( isPresented: $showExporter, document: document, contentType: .plainText, defaultFilename: "MyFile.txt") { result in // handle Result<URL, Error>}File dialog customization (macOS-only)
Section titled “File dialog customization (macOS-only)”Customize text in file dialogs with these macOS-specific modifiers:
// 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
Section titled “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)
Section titled “Modern approach (Transferable)”// Drag sourcestruct DraggableCard: View { let item: MyItem
var body: some View { Text(item.title) .draggable(item) // Requires Transferable conformance }}
// Drop targetstruct 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)
Section titled “Legacy approach (NSItemProvider)”// Drag sourceImage(systemName: "doc") .onDrag { NSItemProvider(object: fileURL as NSURL) }
// Drop targetText("Drop files here") .onDrop(of: [.fileURL], isTargeted: nil) { providers in // handle providers return true }AppKit Interop
Section titled “AppKit Interop”NSViewRepresentable (macOS-only)
Section titled “NSViewRepresentable (macOS-only)”Wraps an AppKit NSView for use in SwiftUI. Implement makeNSView(context:) and updateNSView(_:context:).
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
Section titled “NSViewRepresentable with Coordinator”Use a Coordinator to forward delegate/target-action callbacks to SwiftUI.
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/boundsdirectly on the managedNSView— SwiftUI owns the layout.
NSViewControllerRepresentable (macOS-only)
Section titled “NSViewControllerRepresentable (macOS-only)”Wraps an AppKit NSViewController for use in SwiftUI.
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)
Section titled “NSHostingController & NSHostingView (macOS-only)”Host SwiftUI content inside AppKit (reverse direction — AppKit app embedding SwiftUI views).
// Host SwiftUI as a view controllerlet hostingController = NSHostingController(rootView: MySwiftUIView())window.contentViewController = hostingController
// Host SwiftUI directly as an NSViewlet hostingView = NSHostingView(rootView: MySwiftUIView())someNSView.addSubview(hostingView)Best Practices
Section titled “Best Practices”- Use
NavigationSplitViewfor sidebar-driven navigation — reserveHSplitView/VSplitViewfor IDE-style equal peer panes - Make
Tableadaptive — handle compact size classes by showing combined info in the first column - Always call
startAccessingSecurityScopedResource()on URLs fromfileImporter— they are security-scoped - Use
Transferablefor drag & drop (modern) — fall back toNSItemProvideronly for legacy compatibility - Use
NSViewRepresentablewith Coordinator when you need delegate callbacks from AppKit views - Never set
frame/boundsdirectly on views managed byNSViewRepresentable— SwiftUI owns the layout - Prefer native SwiftUI over AppKit interop when possible — only use
NSViewRepresentablefor features SwiftUI doesn’t provide