How to detect keyboard events in SwiftUI on macOS?

30 min read

In SwiftUI on macOS, you can detect keyboard events using the onReceive modifier combined with the NotificationCenter and NSApp (NSApplication.shared) to monitor the events.

Here's an example of how to detect keyboard events in SwiftUI on macOS:

First, create a new SwiftUI view and import the necessary frameworks:

import SwiftUI
import Combine
import AppKit

Next, create a new view struct and add the following code inside it:

struct ContentView: View {
    @State private var keyboardModifiers: NSEvent.ModifierFlags = []
    @State private var key = ""
    
    var body: some View {
        VStack {
            Text("Keyboard Event: \(key)")
                .font(.title)
            Text("Keyboard Modifiers: \(keyboardModifiers)")
                .font(.title)
        }
        .frame(width: 400, height: 200)
        .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
            NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event -> NSEvent? in
                self.key = event.charactersIgnoringModifiers ?? ""
                self.keyboardModifiers = event.modifierFlags
                return event
            }
        }
        .onReceive(NotificationCenter.default.publisher(for: NSApplication.willResignActiveNotification)) { _ in
            NSEvent.removeMonitor(self)
        }
    }
}

In the above code:

  • We create two @State properties: keyboardModifiers to store the detected modifier flags, and key to store the pressed key.
  • Inside the onReceive modifier, we use NotificationCenter.default.publisher to observe NSApplication.didBecomeActiveNotification to start monitoring keyboard events.
  • In that closure, we call NSEvent.addLocalMonitorForEvents to register a callback for .keyDown events. Whenever a key is pressed, the callback is called, and we update the key and keyboardModifiers properties.
  • When the application is inactive (e.g., the focus is on another app), we stop monitoring the keyboard events by removing the monitor in the onReceive modifier for NSApplication.willResignActiveNotification.

Finally, create a new NSHostingController and set the rootView to your ContentView:

let contentView = ContentView()
let hostingController = NSHostingController(rootView: contentView)

Now, when you run your macOS app, you should see the pressed key and the modifier flags being displayed in the ContentView.