这是可能的方法演示。已在Xcode 11.4 / macOS 10.15.4上测试。
这个想法是通过注入发布者到环境值,将自定义NSWindow生成的键事件与SwiftUI View连接起来。这使得可以在任何视图层次结构水平上监听和处理事件。
以下是完整模块(AppDelegate.swift)代码。还请参阅代码中有用的注释。
import Cocoa
import SwiftUI
import Combine
struct WindowEventPublisherKey: EnvironmentKey {
static let defaultValue: AnyPublisher<NSEvent, Never> =
Just(NSEvent()).eraseToAnyPublisher()
}
extension EnvironmentValues {
var keyPublisher: AnyPublisher<NSEvent, Never> {
get { self[WindowEventPublisherKey.self] }
set { self[WindowEventPublisherKey.self] = newValue }
}
}
class Window: NSWindow {
private let publisher = PassthroughSubject<NSEvent, Never>()
var keyEventPublisher: AnyPublisher<NSEvent, Never> {
publisher.eraseToAnyPublisher()
}
override func keyUp(with event: NSEvent) {
publisher.send(event)
}
}
struct DemoKeyPressedView: View {
@Environment(\.keyPublisher) var keyPublisher
@State private var index: Int = 0
var body: some View {
Text("Demo \(index)")
.onReceive(keyPublisher) { event in
self.keyPressed(with: event)
}
}
func keyPressed(with event: NSEvent) {
self.index += 1
}
}
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: Window!
func applicationDidFinishLaunching(_ aNotification: Notification) {
window = Window(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
let contentView = DemoKeyPressedView()
.frame(minWidth: 400, maxWidth: .infinity, maxHeight: .infinity)
.environment(\.keyPublisher, window.keyEventPublisher)
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}