如何在macOS的SwiftUI中检测键盘事件? [英] How to detect keyboard events in SwiftUI on macOS?
问题描述
如何在macOS的SwiftUI视图中检测键盘事件?
How can I detect keyboard events in a SwiftUI view on macOS?
我希望能够使用按键来控制特定屏幕上的项目,但尚不清楚我如何检测键盘事件,通常是通过覆盖NSView中的keyDown(_ event:NSEvent)来关闭键盘事件.
I want to be able to use key strokes to control items on a particular screen but it's not clear how I detect keyboard events, which is usually down by overriding the keyDown(_ event: NSEvent) in NSView.
推荐答案
与xcode 12捆绑在一起的SwiftUI中的新功能是对commands
进行了修改,它使我们可以使用
New in SwiftUI bundled with Xcode 12 is the commands
modified, which allows us to declare key input with keyboardShortcut
view modifier. You then need some way of forwarding the key inputs to your child views. Below is a solution using a Subject
, but since it is not a reference type it cannot be passed using environmentObject
- which is really what we wanna do, so I've made a small wrapper, conforming to ObservableObject
and for conveninece Subject
itself (forwarding via the subject
).
使用一些其他便利的糖方法,我可以这样写:
Using some additional convenience sugar methods, I can just write like this:
.commands {
CommandMenu("Input") {
keyInput(.leftArrow)
keyInput(.rightArrow)
keyInput(.upArrow)
keyInput(.downArrow)
keyInput(.space)
}
}
将键输入转发给所有子视图,如下所示:
And forward key inputs to all subviews like this:
.environmentObject(keyInputSubject)
然后是子视图,在这里GameView
可以使用onReceive
监听事件,就像这样:
And then a child view, here GameView
can listen to the events with onReceive
, like so:
struct GameView: View {
@EnvironmentObject private var keyInputSubjectWrapper: KeyInputSubjectWrapper
@StateObject var game: Game
var body: some View {
HStack {
board
info
}.onReceive(keyInputSubjectWrapper) {
game.keyInput($0)
}
}
}
用于声明CommandMenu
构建器内部键的keyInput
方法就是这样:
The keyInput
method used to declare the keys inside CommandMenu
builder is just this:
private extension ItsRainingPolygonsApp {
func keyInput(_ key: KeyEquivalent, modifiers: EventModifiers = .none) -> some View {
keyboardShortcut(key, sender: keyInputSubject, modifiers: modifiers)
}
}
extension KeyEquivalent: Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.character == rhs.character
}
}
public typealias KeyInputSubject = PassthroughSubject<KeyEquivalent, Never>
public final class KeyInputSubjectWrapper: ObservableObject, Subject {
public func send(_ value: Output) {
objectWillChange.send(value)
}
public func send(completion: Subscribers.Completion<Failure>) {
objectWillChange.send(completion: completion)
}
public func send(subscription: Subscription) {
objectWillChange.send(subscription: subscription)
}
public typealias ObjectWillChangePublisher = KeyInputSubject
public let objectWillChange: ObjectWillChangePublisher
public init(subject: ObjectWillChangePublisher = .init()) {
objectWillChange = subject
}
}
// MARK: Publisher Conformance
public extension KeyInputSubjectWrapper {
typealias Output = KeyInputSubject.Output
typealias Failure = KeyInputSubject.Failure
func receive<S>(subscriber: S) where S : Subscriber, S.Failure == Failure, S.Input == Output {
objectWillChange.receive(subscriber: subscriber)
}
}
@main
struct ItsRainingPolygonsApp: App {
private let keyInputSubject = KeyInputSubjectWrapper()
var body: some Scene {
WindowGroup {
#if os(macOS)
ContentView()
.frame(idealWidth: .infinity, idealHeight: .infinity)
.onReceive(keyInputSubject) {
print("Key pressed: \($0)")
}
.environmentObject(keyInputSubject)
#else
ContentView()
#endif
}
.commands {
CommandMenu("Input") {
keyInput(.leftArrow)
keyInput(.rightArrow)
keyInput(.upArrow)
keyInput(.downArrow)
keyInput(.space)
}
}
}
}
private extension ItsRainingPolygonsApp {
func keyInput(_ key: KeyEquivalent, modifiers: EventModifiers = .none) -> some View {
keyboardShortcut(key, sender: keyInputSubject, modifiers: modifiers)
}
}
public func keyboardShortcut<Sender, Label>(
_ key: KeyEquivalent,
sender: Sender,
modifiers: EventModifiers = .none,
@ViewBuilder label: () -> Label
) -> some View where Label: View, Sender: Subject, Sender.Output == KeyEquivalent {
Button(action: { sender.send(key) }, label: label)
.keyboardShortcut(key, modifiers: modifiers)
}
public func keyboardShortcut<Sender>(
_ key: KeyEquivalent,
sender: Sender,
modifiers: EventModifiers = .none
) -> some View where Sender: Subject, Sender.Output == KeyEquivalent {
guard let nameFromKey = key.name else {
return AnyView(EmptyView())
}
return AnyView(keyboardShortcut(key, sender: sender, modifiers: modifiers) {
Text("\(nameFromKey)")
})
}
extension KeyEquivalent {
var lowerCaseName: String? {
switch self {
case .space: return "space"
case .clear: return "clear"
case .delete: return "delete"
case .deleteForward: return "delete forward"
case .downArrow: return "down arrow"
case .end: return "end"
case .escape: return "escape"
case .home: return "home"
case .leftArrow: return "left arrow"
case .pageDown: return "page down"
case .pageUp: return "page up"
case .return: return "return"
case .rightArrow: return "right arrow"
case .space: return "space"
case .tab: return "tab"
case .upArrow: return "up arrow"
default: return nil
}
}
var name: String? {
lowerCaseName?.capitalizingFirstLetter()
}
}
public extension EventModifiers {
static let none = Self()
}
extension String {
func capitalizingFirstLetter() -> String {
return prefix(1).uppercased() + self.lowercased().dropFirst()
}
mutating func capitalizeFirstLetter() {
self = self.capitalizingFirstLetter()
}
}
extension KeyEquivalent: CustomStringConvertible {
public var description: String {
name ?? "\(character)"
}
}
这篇关于如何在macOS的SwiftUI中检测键盘事件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!