Technical

Building a Clipboard Manager with SwiftUI and SwiftData

A high-level architecture walkthrough of building a macOS clipboard manager with SwiftUI, SwiftData, and AppKit. Polling, persistence, menu bar integration, and the paste workflow.

Building a Clipboard Manager with SwiftUI and SwiftData
Technical | | 5 min read

Building a clipboard manager sounds simple — watch the clipboard, save what gets copied, let the user paste old items. In practice, it touches almost every layer of macOS development: AppKit for system integration, SwiftUI for the interface, SwiftData for persistence, and Core Graphics for keystroke simulation.

This isn’t a step-by-step tutorial. It’s an architecture walkthrough — the decisions, tradeoffs, and patterns that go into building something like QuietClip. If you’re a developer considering building your own, this will save you a lot of dead ends.

Architecture overview

A clipboard manager has four main components:

These components are mostly independent. The monitor doesn’t know about the UI. The paste engine doesn’t know about storage. This separation keeps the codebase manageable.

Monitoring the clipboard

The clipboard monitor is the heartbeat of the app. It runs a timer — typically every 0.3 to 0.5 seconds — that checks whether NSPasteboard.general.changeCount has incremented.

@Observable
final class ClipboardMonitor {
    private var lastChangeCount: Int
    private var timer: Timer?

    func start() {
        lastChangeCount = NSPasteboard.general.changeCount
        timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
            self?.checkForChanges()
        }
    }

    private func checkForChanges() {
        let current = NSPasteboard.general.changeCount
        guard current != lastChangeCount else { return }
        lastChangeCount = current
        // Read and store the new content
    }
}

There’s no notification API for clipboard changes — polling is the only option. The good news is that checking an integer is essentially free. Even at 0.3-second intervals, the CPU impact is unmeasurable.

Polling gets a bad reputation, but clipboard monitoring is the rare case where it’s genuinely the right approach — the API gives you no alternative, and the cost is negligible.

When a change is detected, you read all available types from the pasteboard. Text, images, file URLs, rich text — a single copy can carry multiple representations. Store the richest version you can, but always keep a plain text fallback for search.

Persisting with SwiftData

SwiftData is a natural fit for clipboard history. Each clipboard item becomes a model:

@Model
final class ClipItem {
    var content: String
    var imageData: Data?
    var sourceApp: String?
    var copiedAt: Date
    var isPinned: Bool
}

SwiftData handles the SQLite backing store, migrations, and SwiftUI integration automatically. You get @Query for free — sorting by date, filtering by search text, limiting to pinned items — all with zero boilerplate.

Design decision

Storing images

Images are the tricky part. Storing raw PNG data in the database works but can bloat the SQLite file. A better approach: store images as files on disk and keep the file path in SwiftData. This keeps the database fast for text queries while supporting large images. Set a reasonable maximum — QuietClip caps image storage at what fits within its item limit.

One important detail: set a maximum history size and enforce it. Without a cap, the database grows indefinitely. When inserting a new item, delete the oldest non-pinned item if you’ve hit the limit.

The SwiftUI panel

The UI is a floating panel — similar to Spotlight — that appears when the user presses the global hotkey. SwiftUI makes this straightforward:

  • A TextField for search at the top
  • A List of clipboard items, sorted by recency
  • Keyboard navigation with arrow keys and Enter to paste

The challenge is hosting SwiftUI inside the right kind of window. You need an NSPanel — specifically one configured as .nonactivatingPanel — so that the original app retains focus. If you use a regular NSWindow, the target app loses focus and ⌘V pastes into your clipboard manager instead.

let panel = NSPanel(
    contentRect: .zero,
    styleMask: [.nonactivatingPanel, .fullSizeContentView],
    backing: .buffered,
    defer: true
)
panel.isFloatingPanel = true
panel.level = .floating

Then host your SwiftUI view inside it with NSHostingView.

A clipboard manager lives in the menu bar, not the Dock. This means using NSStatusItem — which is still AppKit-only. Your app’s entry point will be an NSApplicationDelegate (or @NSApplicationDelegateAdaptor if you’re using SwiftUI’s App lifecycle) that creates the status item on launch.

let statusItem = NSStatusBar.system.statusItem(withLength: .squareLength)
statusItem.button?.image = NSImage(systemSymbolName: "clipboard", accessibilityDescription: "QuietClip")

Set LSUIElement to true in your Info.plist to hide the Dock icon. The app becomes menu-bar-only — present but unobtrusive.

The paste workflow

When the user selects a historical item, the paste workflow is:

  1. Write the item’s data back to NSPasteboard.general
  2. Dismiss the panel
  3. Wait a brief moment (10-50ms) for the panel to fully dismiss
  4. Simulate ⌘V using CGEvent to paste into the frontmost app

Step 4 requires Accessibility permissions. Without them, CGEvent.post() silently does nothing. Your app needs to request and guide the user through enabling Accessibility access in System Settings.

Architecture win

By keeping the panel as a non-activating NSPanel, the frontmost app never loses focus. When you simulate ⌘V, it goes straight to the right app — no need to track which app was active or manage focus switches.

This architecture — AppKit shell, SwiftUI views, SwiftData persistence, CGEvent paste — is how QuietClip is built. Each layer does one thing well, and the boundaries between them are clean.

Next step

See this architecture in action.

QuietClip is built with SwiftUI, SwiftData, and zero dependencies. Under 5 MB, runs on macOS 14+. Free to start, $8.99 once for Pro.

Download QuietClip Free

Frequently asked questions

Can you build a clipboard manager entirely in SwiftUI?
Not quite. SwiftUI handles the UI beautifully, but you still need AppKit for the menu bar icon (NSStatusItem), the floating panel (NSPanel), global keyboard shortcuts, and CGEvent keystroke simulation. The best approach is SwiftUI for views with an AppKit shell.
Why use SwiftData instead of UserDefaults or files?
Clipboard history can grow to thousands of items with image data. SwiftData (backed by SQLite) handles large datasets efficiently, supports querying and sorting, and integrates natively with SwiftUI's observation system. UserDefaults would be too slow; flat files would need custom indexing.
How do you make a clipboard manager appear with a keyboard shortcut?
Register a global hotkey using NSEvent.addGlobalMonitorForEvents or the newer KeyboardShortcuts approach. When triggered, show an NSPanel (not NSWindow) configured as a floating, non-activating panel — this keeps focus in the original app so paste works correctly.

Try QuietClip free

A privacy-first clipboard manager for macOS. Your data stays on your device, always.

Download for macOS

Related reads