How to Add Global Keyboard Shortcuts to MacOS Apps SwiftUI

In the tutorial, you'll learn how to configure a global keyboard shortcut to your SwiftUI macOS app in only a few lines of swift code.

Share:

Apr 25, 2023 766 Words

Read Time: 4 Minutes

A screenshot of swiftUI code snippet to add global keyboard shortcut to your macOS app.

Overview

SwiftUI makes it easy to add keyboard shortcuts to your macOS apps.

Keyboard shortcuts can help users quickly access app features and improve productivity. In this article, we’ll walk through how to use keyboard shortcuts in SwiftUI using a simple example app.

We’ll begin by creating a simple encoding/decoding app in SwiftUI. We’ll then add keyboard shortcuts to the app using the keyboardShortcut()1 modifier. Finally, we’ll show how to use global keyboard shortcuts using the HotKey2 extension.

Creating the App

Our app will have two text fields and two buttons. One text field will be for entering a string to encode/decode, and the other will display the result. The buttons will be used to perform the encoding/decoding and copying the result to the clipboard.

Here’s the code for our app:

struct ContentView: View {
    @State private var inputText = ""
    @State private var resultText = ""

    var body: some View {
        VStack {
            TextField("Enter text", text: $inputText)
                .padding()
            Button("Encode") {
                resultText = encode(inputText)
            }
            .keyboardShortcut("e", modifiers: [.command])
            Button("Decode") {
                resultText = decode(inputText)
            }
            .keyboardShortcut("d", modifiers: [.command])
            TextField("Result", text: $resultText)
                .padding()
            Button("Copy to Clipboard") {
                copyToClipboard(resultText)
            }
            .keyboardShortcut("c", modifiers: [.command])
        }
        .padding()
    }

    func encode(_ string: String) -> String {
        // Encoding logic here
    }

    func decode(_ string: String) -> String {
        // Decoding logic here
    }

    func copyToClipboard(_ string: String) {
        // Copy to clipboard logic here
    }
}

Adding Keyboard Shortcuts

To add keyboard shortcuts to our app, we’ll use the keyboardShortcut() modifier. This modifier takes a key and an optional set of modifiers. In our app, we’ve added keyboard shortcuts for the encode, decode, and copy to clipboard buttons.

Button("Encode") {
    resultText = encode(inputText)
}
.keyboardShortcut("e", modifiers: [.command])

Button("Decode") {
    resultText = decode(inputText)
}
.keyboardShortcut("d", modifiers: [.command])

Button("Copy to Clipboard") {
    copyToClipboard(resultText)
}
.keyboardShortcut("c", modifiers: [.command])

Now, when the user presses ⌘ + E, ⌘ + D, or ⌘ + C, the corresponding button will be triggered.

Global Keyboard Shortcuts

Sometimes, it’s useful to have keyboard shortcuts that work even when the app isn’t in the foreground. This is where the HotKey extension comes in. We can use this extension to register global keyboard shortcuts that work no matter which app is in the foreground.

To use the HotKey package in your SwiftUI app, you’ll need to add it to your project. You can do this by adding the package to your project’s dependencies in Xcode.

Xcode -> File -> Add Packages

A Screenshot of Swift Package Additional Dialog Box

Once you’ve added the package, you can import it into your project and start using it to register global keyboard shortcuts.

import HotKey

let hotKey = HotKey(key: .e, modifiers: [.command])

hotKey.keyDownHandler = {
    // Handle global keyboard shortcut here
}

In this example, we’re registering a global keyboard shortcut for ⌘ + E. When the user presses this keyboard shortcut, the keyDownHandler closure will be executed.

In our app, we could use a global keyboard shortcut to show the app’s main window, even if it’s in the background. We could then use the same keyboard shortcuts to trigger the encoding/decoding and copying to clipboard functionality.

How I used it

Here’s how I used it in my swiftUI code:

import SwiftUI
import HotKey

struct ClipInput: Codable, Hashable {
    var inputText: String
    var encodedText: String
}

@main
struct Base64ToolApp: App {
    @Environment(\.openWindow) var openWindow
    var ehotkey = HotKey(key: .e, modifiers: [.option, .command])
    var dhotkey = HotKey(key: .d, modifiers: [.option, .command])
    @State var clipboardText: String = "nothing"
    var body: some Scene {
        WindowGroup(id: "OMG") {
            ContentView(inputText: "", encodedText: "").frame(width: 800, height: 300)
            .onAppear {
                ehotkey.keyDownHandler =  ebuttonAction
                dhotkey.keyDownHandler = dbuttonAction
            }
        }
        WindowGroup(id: "IMG", for: ClipInput.self) { $value in
            ContentView(inputText: value?.inputText ?? "", encodedText: value?.encodedText ?? "").frame(width: 800, height: 300)
        }
        MenuBarExtra("Base64Tool"){
            VStack {
                ContentView(inputText: "", encodedText: "").frame(width: 800, height: 300)
                Button("Full Quit - ⌘Q") {
                    NSApplication.shared.terminate(nil)
                }.keyboardShortcut("q")
            }
        }.menuBarExtraStyle(.window)
    }
    private func ebuttonAction(){
        DispatchQueue.main.async {
            clipboardText = getTextFromClipboard() ?? "No text in clipboard"
            let eclipinput = ClipInput(inputText: clipboardText, encodedText: encodeToBase64(clipboardText))
            openWindow(id: "IMG", value: eclipinput)
        }
    }
    private func dbuttonAction(){
        DispatchQueue.main.async {
            clipboardText = getTextFromClipboard() ?? "No text in clipboard"
            let dclipinput = ClipInput(inputText: clipboardText, encodedText: decodeFromBase64(clipboardText))
            openWindow(id: "IMG", value: dclipinput)
        }
    }
    func getTextFromClipboard() -> String? {
        let pasteboard = NSPasteboard.general
        let pasteboardItems = pasteboard.pasteboardItems
        if let string =  pasteboardItems!.last!.string(forType: NSPasteboard.PasteboardType(rawValue: "public.utf8-plain-text")) {
            return string
        }
        return nil
    }
}

Conclusion

Adding keyboard shortcuts to your SwiftUI apps is easy and can greatly improve the user experience. By using the keyboardShortcut() modifier and the HotKey extension, you can create keyboard shortcuts that work both in and out of your app’s window.

Reference

Find more posts from following topics

12
13
16
18
19
20
2024
21
27
28
29
4
@binding
abbacchio
accurate-requests
anime
api-development
api-testing
api-testing-tools
array
automated-testing
bad-habits
base64-decoder
base64-encoder
biography
blog
blogging
books
browser
bulma-css
bulma.io
bulmacss
button-swiftui
chatgpt
chrome
clipboard
code
code-block
code-navigation
code-snippet
codecoverage
comparison
compile
computer-networks
configuring-debugger-for-django-in-vs-code
configuring-launch.json-for-python-debugger
copy
copy-to-clipboard
copy-to-clipboard-neovim
css
current-date
current-time
current-timestamp
database
debugger-setup-in-visual-studio-code
debugging-django-app-in-visual-studio-code
debugging-python-code-in-visual-studio-code
debugging-python-programs-with-visual-studio-code
debugging-python-with-virtual-environment-in-vs-code
developer-productivity
developers
development-workflow
django
django-rest-framework
dom
dynamic-sitemap-in-nextjs
encryption
engineering-dashboard
enumerate
experience
fiction
flowcharts
generics
git
git-diff
github
global-keyboard-shorcut
global-shortcut
go
go-hugo
go-programming
go-test
go-to-line
go-tool
go1.18
golang
golang-development
golden-wind
good-habits
gorilla-websocket
gpt
gpt-3.5
gpt-4
gpt-4-api
guide
gumroad
habits
habits-tracker-notion-template
hamburger-menu
hotkeys
html
hugo
ide
image
image-sharing
image-tool-for-ios
imagerenderer
include-timestamp
integrated-development-environment
ios
ios-16
ios16
iphone
iphone-13
iphone-13-pro
iphone-13-pro-max
javascript
jojos-bizzarre-adventure
jump-to-definition
keyboard-shortcut
leonardo-da-vinci
lessons
linux
logging
ls
lsp
macos
map
markdown
markdown-code
mental-programming
menu
menubarextra
mergesort
mermaid-syntax
mistake-tracker-notion
mobile-view
modifier
modulo-operation
navbar
navigationlink
navigationstack
neovim
nested-functions
next.js
nextjs
nextjs-markdown
nextjs-sitemap
nextjs-sitemaps
nice-shot
nice-shot-pro
notion
notion-api
notion-api-python
notion-budget
notion-budget-template
notion-budget-tracker
notion-bug-report-tracker
notion-dashboard
notion-expense-manager
notion-habits
notion-habits-dashboard
notion-habits-template
notion-habits-tracker
notion-habits-tracker-template
notion-issue-tracker
notion-mistake-tracker
notion-product
notion-product-dashboard
notion-product-roadmap
notion-product-roadmap-dashboard
notion-tasks
notion-tasks-dashboard
notion-tasks-template
notion-tasks-tracker
notion-template
notionworkspaces
october
openai
osx
pagination
personal-ifttt-framework
photospicker
photospickeritem
phpickerfilter
postgres
postman-capabilities
postman-request
pre-request-script
product-roadmap-notion-template
product-roadmap-template
productivity
programming
python
python-api
python-debugger-tutorial-for-vs-code
python-debugging-mode-in-vs-code
python-notion-api
python3
reading
real-time-communication
rehype
remark
request-data
running-debugger-in-visual-studio-code
running-django-app-in-debugging-mode
running-program-in-debugging-mode-in-vs-code
running-python-code-in-debugging-mode
safari
screenshot-app-for-ios
screenshot-app-ios
screenshot-ios
screenshot-tool-for-ios
september
set-current-timestamp
setting-up-debugger-in-vs-code-for-python
share-extension
sharelink
sharepreview
sharesheet
simple-websocket-server
sitemap
slice
slices
slider
sort
sorting
space-complexity
sql
ssh
step-by-step-guide
stocks-profits-tracker
stocks-profits-tracker-template
stocks-tracker
struct
sustained-vigilance
swift
swiftui
swiftui-button
swiftui-button-action
swiftui-button-style
table-of-contents
tasks-tracker-notion-template
tcp
test
testing
textfield-swiftui
til
tim-sort
time-complexity
timeliness
timestamp-integration
timsort
transferable
triggers-and-actions
tutorial
unittest
unix
us-stocks
usa-stocks
useful-ios-features
using-breakpoints-in-python-debugger
using-virtual-environment-with-python-debugger
vanilla-javascript
variable
vim
visual-mode
visual-studio-code
vs-code
vscode
vscode-go-to-line
walter-isaacson
web-sockets-in-go
websocket-client
websocket-programming
websocket-server
white-screen-of-death
wsod
xcode
xss