Skip to content

Instantly share code, notes, and snippets.

@orgmir
Last active September 1, 2021 03:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save orgmir/f48a138339d3f1d175ba3161b2501439 to your computer and use it in GitHub Desktop.
Save orgmir/f48a138339d3f1d175ba3161b2501439 to your computer and use it in GitHub Desktop.
SwiftUI DDHotKeyTextField wrapper, so you can set hotkeys using https://github.com/davedelong/DDHotKey
import Combine
import SwiftUI
// Don't forget to add https://github.com/davedelong/DDHotKey to your project
struct HotKeyTextField: View {
@Binding var keyCode: Int
@Binding var modifierFlags: Int
var body: some View {
DDHotKeyTextFieldWrapper(keyCode: $keyCode, modifierFlags: $modifierFlags)
.padding(4)
.background(Capsule().fill(Color(NSColor.textBackgroundColor)))
}
private struct DDHotKeyTextFieldWrapper: View, NSViewRepresentable {
typealias NSViewType = DDHotKeyTextField
@Binding var keyCode: Int
@Binding var modifierFlags: Int
func makeNSView(context: Context) -> DDHotKeyTextField {
let textField = DDHotKeyTextField()
textField.hotKey = DDHotKey(keyCode: UInt16(keyCode), modifierFlags: UInt(modifierFlags), task: { _ in })
textField.alignment = NSTextAlignment.center
textField.isBordered = false
textField.bezelStyle = .roundedBezel
textField.sizeToFit()
textField.delegate = context.coordinator
// Use KVO to detect when the user sets a new hotkey
textField.addObserver(context.coordinator, forKeyPath: "hotKey", options: .new, context: nil)
return textField
}
func updateNSView(_ textField: DDHotKeyTextField, context: Context) {
textField.hotKey = DDHotKey(keyCode: UInt16(keyCode), modifierFlags: UInt(modifierFlags), task: { _ in })
context.coordinator.window = textField.window
}
static func dismantleNSView(textField: DDHotKeyTextField, coordinator: Coordinator) {
textField.removeObserver(coordinator, forKeyPath: "hotKey")
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, NSTextDelegate, NSTextFieldDelegate {
var parent: DDHotKeyTextFieldWrapper
var window: NSWindow?
private var sink = Set<AnyCancellable>()
init(_ parent: DDHotKeyTextFieldWrapper) {
self.parent = parent
super.init()
setupNotification()
}
// For some reason SwiftUI doesn't resignFirstResponder on the text field,
// so we have to do it ourselves. Otherwise, the DDHotKey global listener will
// still register events even if we are in a different same app window
private func setupNotification() {
NotificationCenter.default.publisher(for: NSWindow.didResignKeyNotification)
.sink { notif in
guard let window = self.window, let notifWindow = notif.object as? NSWindow else { return }
if window == notifWindow {
// this will disable the DDHotKeyTextField global key listener
window.makeFirstResponder(nil)
}
}
.store(in: &sink)
}
@objc override func observeValue(forKeyPath _: String?,
of object: Any?,
change _: [NSKeyValueChangeKey: Any]?,
context _: UnsafeMutableRawPointer?)
{
guard let textField = object as? DDHotKeyTextField else { return }
let keyCode = Int(textField.hotKey.keyCode)
let modifierFlags = Int(textField.hotKey.modifierFlags)
if keyCode != parent.keyCode || modifierFlags != parent.modifierFlags {
parent.keyCode = keyCode
parent.modifierFlags = modifierFlags
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment