Support Copy/Paste keyboard shortcuts with Swift UI for Mac OS X
Tags: swiftui, macosx
Published: 2023-11-04 08:20:25

Thought this might be a great opportunity for my first post! 🎉 



---

#### Objectives:

- Add a `paste` command to your app menu (window commands) and a `Cmd+V` keyboard shortcut
- Make a `copy` button that you can add to your window commands



> TL;DR: 
> Add the [PasteButton](https://developer.apple.com/documentation/swiftui/pastebutton) system button in your window commands.
> Find snippet for `copy` button action at the end.


---

### Setup

Create an [ObservableObject](https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-observedobject-to-manage-state-from-external-objects) to store and publish your clipboard content to the rest of the app.

```swift
// AppClipboard.swift

class AppClipboard: ObservableObject {
    @Published var content: URL?

    init(content: URL? = nil) {
        self.content = content
    }
}
```

Attach an instance in your app entry point:

```swift
// MyApp.swift

import SwiftUI

@main
struct MyApp: App {
    @StateObject var clipboard = AppClipboard() // <---- 1. //

    var body: some Scene {

        WindowGroup("My App Name") {
            ContentView()
                .environmentObject(clipboard) // <---- 2. //

        }

    }
}
```



And now you can access the object in child views:

```swift
import SwiftUI

struct MyView: View {
    @EnvironmentObject var clipboard: AppClipboard // <---- 1. //

    var body: some View {
      Text(clipboard.content) // <----- 2. //
    }
}
```



Next, create a [FocusBinding](https://developer.apple.com/documentation/swiftui/focusedbinding) by extending the [FocusedValues](https://developer.apple.com/documentation/swiftui/focusedvalue) struct and adding a key for the clipboard. This makes the `ObservableObject` we created earlier accessible from the window menu commands we will create later.

```swift
// FocusedValuesExtension.swift

private struct ClipboardContentKey: FocusedValueKey {
    typealias Value = Binding<String?>
}

extension FocusedValues {
    var clipboardContent: Binding<String?>? {
        get { self[ClipboardContentKey.self] }
        set { self[ClipboardContentKey.self] = newValue }
    }
}
```



Now in your window menu commands add the clipboard `FocusedBinding` like so:

```swift
// Commands.swift

import SwiftUI

struct MyWindowCommands: Commands {
    @FocusedBinding(\.clipboardContent) var clipboardContent // <--- 1. //

    var body: some Commands {
        // Option 1: Replace system pasteboard menu...
        CommandGroup(replacing: CommandGroupPlacement.pasteboard) {
            PasteButton(payloadType: String.self) { values in
                 guard let value = values.first else { return }
                 // make sure we updating on the main thread (or we get a warning)
                 DispatchQueue.main.async{
                     clipboardContent = value // <---- 2. //
                 }
                 return
            }
            .keyboardShortcut("V", modifiers: [.command]) // <--- shortcut 3. // 
        }

        // Option 2: Create custom menu
        CommandMenu("My Custom Menu") {
            PasteButton(payloadType: String.self) { values in
                 guard let value = values.first else { return }
                 DispatchQueue.main.async{
                     clipboardContent = value 
                 }
                 return
            }
            .keyboardShortcut("V", modifiers: [.command])  
        }
    }
}
```



If you build your app in XCode, you should now see a working `paste` under the `Edit` menu or your custom menu.



### Making a `copy` button

This is a little trickier because it depends on how you plan to use the copy action in your app. But I will include here a simple button action snippet that lets you put content in the system clipboard.

```swift
Button(action: {
    NSPasteboard.general.declareTypes([.string], owner: nil)
                            
    let pasteboard = NSPasteboard.general // <----- This is important! Do not convert to a one liner!
    pasteboard.setString(webURL.absoluteString, forType: .string)
}, label: {
    Image(systemName: "square.on.square")
})
```

You can incorporate this into your window commands like the `paste` button.

Hope you found this useful! 

### Resources

[SwiftUI Mac Menus](https://troz.net/post/2021/swiftui_mac_menus/)

---
_[Photo by Jørgen Håland](https://unsplash.com/@jhaland?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)_