如何在 macOS 上检测 SwiftUI 中的键盘事件?

     2023-02-23     95

关键词:

【中文标题】如何在 macOS 上检测 SwiftUI 中的键盘事件?【英文标题】:How to detect keyboard events in SwiftUI on macOS? 【发布时间】:2020-04-11 07:44:08 【问题描述】:

如何在 macOS 上的 SwiftUI 视图中检测键盘事件?

我希望能够使用击键来控制特定屏幕上的项目,但不清楚我如何检测键盘事件,这通常通过覆盖 NSView 中的 keyDown(_ event: NSEvent) 来完成。

【问题讨论】:

【参考方案1】:

与 Xcode 12 捆绑的 SwiftUI 中的新功能是修改了 commands,它允许我们使用 keyboardShortcut view modifier 声明键输入。然后,您需要某种方式将关键输入转发到您的子视图。下面是使用Subject 的解决方案,但由于它不是引用类型,因此无法使用environmentObject 传递——这正是我们想要做的,所以我做了一个小包装器,符合ObservableObject 和为方便Subject 本身(通过subject 转发)。

使用一些额外的便利糖方法,我可以这样写:

.commands 
    CommandMenu("Input") 
        keyInput(.leftArrow)
        keyInput(.rightArrow)
        keyInput(.upArrow)
        keyInput(.downArrow)
        keyInput(.space)
    

并将关键输入转发到所有子视图,如下所示:

.environmentObject(keyInputSubject)

然后是子视图,这里GameView可以用onReceive监听事件,像这样:

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 builder 中声明键的keyInput 方法是这样的:

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)"
    


【讨论】:

没有以下两行代码是不完整的:``` import SwiftUI import Combine ``` 谢谢!我在添加对 keyInput("a") 等字符的支持时遇到了麻烦。我可以通过稍微更改您的代码来解决这个问题。如果更改默认值:return nil for lowerCaseName 以返回 String(self.character).lowercased(),那么您将能够使用字符。【参考方案2】:

到目前为止,还没有内置的原生 SwiftUI API。

这里只是一个可能的方法的演示。使用 Xcode 11.4 / macOS 10.15.4 测试

struct KeyEventHandling: NSViewRepresentable 
    class KeyView: NSView 
        override var acceptsFirstResponder: Bool  true 
        override func keyDown(with event: NSEvent) 
            print(">> key \(event.charactersIgnoringModifiers ?? "")")
        
    

    func makeNSView(context: Context) -> NSView 
        let view = KeyView()
        DispatchQueue.main.async  // wait till next event cycle
            view.window?.makeFirstResponder(view)
        
        return view
    

    func updateNSView(_ nsView: NSView, context: Context) 
    


struct TestKeyboardEventHandling: View 
    var body: some View 
        Text("Hello, World!")
            .background(KeyEventHandling())
    

输出:

【讨论】:

谢谢 - 不幸的是,这被锁定了,因为我需要创建多个形状并附加到形状上。只有选择了形状(长性)时,才应激活键盘事件处理。实现这一目标的最佳方法是什么? 忽略这一点 我刚刚意识到我需要在主布局窗口上执行此操作并向所选对象发送命令,因为可能会选择多个对象。然而,焦点处理仍然存在问题——如何检测窗口何时失去焦点? 该代码适用于 Xcode 11.6 和 macOS 10.15.6。但是,每次检测到按键都会发出错误声音。 该解决方案有效,但它只检测 alpha 和箭头键,fn、控制、选项、命令键等模块不起作用,并且按下的任何键都会发出错误声音 关于错误声音:如果您删除super.keyDown(with: event)(它告诉其余响应者链未处理击键),则不应再发出声音。

(SwiftUI, MacOS 11.0) 如何在 SecureTextFeild 中检测焦点?

】(SwiftUI,MacOS11.0)如何在SecureTextFeild中检测焦点?【英文标题】:(SwiftUI,MacOS11.0)HowtodetectfocusinSecureTextFeild?【发布时间】:2021-07-2203:57:42【问题描述】:我正在使用SwiftUI开发一个新的macOS应用程序,但不知道如何在SecureTextFeild中检... 查看详情

如何使用 SwiftUI 在 macOS 上可靠地检索窗口的背景颜色?

】如何使用SwiftUI在macOS上可靠地检索窗口的背景颜色?【英文标题】:Howtoreliablyretrieveawindow\'sbackgroundcoloronmacOSwithSwiftUI?【发布时间】:2021-04-2816:54:22【问题描述】:是否有一种Swifty方法可以在macOS上的SwiftUI中检测窗口的背景颜... 查看详情

SwiftUI - 如何在 macOS 上隐藏窗口标题

】SwiftUI-如何在macOS上隐藏窗口标题【英文标题】:SwiftUI-HowtohidewindowtitleonmacOS【发布时间】:2020-12-0317:48:58【问题描述】:在没有AppDelegate/SceneDelegate的新SwiftUI框架的macOS上,如何隐藏窗口标题?我发现这篇来自Apple的文章描述了... 查看详情

SwiftUI:如何在 macOS 11 上使用 NSSearchToolBarItem?

】SwiftUI:如何在macOS11上使用NSSearchToolBarItem?【英文标题】:SwiftUI:HowtouseNSSearchToolBarItemonmacOS11?【发布时间】:2020-10-1517:36:41【问题描述】:我观看了WWDC视频“采用macOS的新外观”,他们在视频中11:32在Swift中介绍了NSSearchToolBarIt... 查看详情

如何检测何时点击 TextField? (MacOS 上的 SwiftUI)

】如何检测何时点击TextField?(MacOS上的SwiftUI)【英文标题】:HowcanIdetectwhenTextFieldistapped?(SwiftUIonMacOS)【发布时间】:2019-11-2204:40:54【问题描述】:如何检测何时点击了TextField?(MacOS上的SwiftUI)importSwiftUIstructContentView:View@Statev... 查看详情

SwiftUI 和 macOS:如何检测关闭的最后一个窗口并显示应用程序将退出的警报

】SwiftUI和macOS:如何检测关闭的最后一个窗口并显示应用程序将退出的警报【英文标题】:SwiftUI&macOS:Howtodetectlastwindowbeingclosedandshowalertthatappwillquit【发布时间】:2021-08-2517:56:47【问题描述】:我正在创建一个SwiftUI应用程序。... 查看详情

如何遍历 SwiftUI 结构中的键?

】如何遍历SwiftUI结构中的键?【英文标题】:HowtoloopthroughkeysinstructforSwiftUI?【发布时间】:2020-07-1610:45:00【问题描述】:我很久以前做过XCode,现在回来学习SwiftUI。我正在尝试在HStack中呈现结构的数据。结构如下所示:structProdu... 查看详情

SwiftUI:如何在 macOS 11 上设置工具栏的标题?

】SwiftUI:如何在macOS11上设置工具栏的标题?【英文标题】:SwiftUI:HowtosetthetitleforthetoolbaronmacOS11?【发布时间】:2020-10-1419:21:16【问题描述】:我有一个SwiftUI应用程序,它有两列和一个工具栏。我正在尝试模拟最新的macOS应用程... 查看详情

如何使用 SwiftUI 在 MacOS 上配置多选分段控制

】如何使用SwiftUI在MacOS上配置多选分段控制【英文标题】:Howtoconfiguremulti-selectSegmentedControlonMacOSwithSwiftUI【发布时间】:2020-05-1700:24:21【问题描述】:macOSHIG建议分段控件可以是多选的:https://developer.apple.com/design/human-interface-guid... 查看详情

使用 SwiftUI 在 MacOS 中的多列 TableViews

】使用SwiftUI在MacOS中的多列TableViews【英文标题】:MultiColumnTableViewsinMacOSusingSwiftUI【发布时间】:2020-02-2506:16:23【问题描述】:我一直在用SWiftUI进行一些试验,但似乎根本找不到任何关于如何使用SWiftUI创建多列TableView的信息,... 查看详情

如何在 SwiftUI 生命周期中删除 macOS 中的最大化、最小化和关闭按钮?

】如何在SwiftUI生命周期中删除macOS中的最大化、最小化和关闭按钮?【英文标题】:HowcanIremovemaximize,minimizeandclosebuttoninmacOSinSwiftUI-lifecycle?【发布时间】:2021-03-3113:37:39【问题描述】:我正在开发一个删除最大化、最小化和关闭... 查看详情

SwiftUI:检测 tvOS 上 NavigationView 中的选择更改

】SwiftUI:检测tvOS上NavigationView中的选择更改【英文标题】:SwiftUI:DetectingselectionchangeinNavigationViewontvOS【发布时间】:2020-10-0920:15:05【问题描述】:我正在为tvOS构建一个SwiftUI应用程序,目前正在尝试实现UI。我在底部有NavigationVie... 查看详情

检测 Python 中的键输入

...hon这么奇怪,在google上搜索也找不到这个,但是很简单。如何检测“SPACE”或任何键?我该怎么做:print(\'Youpressed%s\'%key)这应该包含在python核心中,所以请不要链接与核心python无关的模块。【问题讨论】:【参考方 查看详情

如何让 SwiftUI SidebarMenu 每次都显示相同的 DetailView 而不是创建一个新的(在 macOS 上)

】如何让SwiftUISidebarMenu每次都显示相同的DetailView而不是创建一个新的(在macOS上)【英文标题】:HowcanIgetaSwiftUISidebarMenutodisplaythesameDetailVieweachtimeratherthancreatinganewone(onmacOS)【发布时间】:2020-03-1310:15:12【问题描述】:我有一个ma... 查看详情

响应 SwiftUI 中的按键事件

】响应SwiftUI中的按键事件【英文标题】:RespondtokeypresseventsinSwiftUI【发布时间】:2020-04-2517:57:53【问题描述】:我想响应按键,例如macOS/OSX上的esc键,以及在iPad上使用外接键盘时。我该怎么做?我曾想过将@available/#available与Swift... 查看详情

如何在 MacOS 的 SwiftUI 中向 TextEditor 添加焦点环

】如何在MacOS的SwiftUI中向TextEditor添加焦点环【英文标题】:HowtoaddafocusringtoTextEditorinSwiftUIforMacOS【发布时间】:2021-12-0907:40:48【问题描述】:与具有动画蓝色焦点环的TextField相比,TextEditor控件没有它。这个怎么添加?它的外观... 查看详情

SwiftUI - 在 macOS 上翻转全局坐标空间

】SwiftUI-在macOS上翻转全局坐标空间【英文标题】:SwiftUI-GlobalcoordinatespaceisflippedonmacOS【发布时间】:2020-11-2714:29:51【问题描述】:编辑:已确认是Apple错误。现在已在macOSMonterey上修复在我看来,通过GeometryReader阅读框架在macOS上... 查看详情

SwiftUI:WKWebView 在 macOS 上截断页面内容(NSViewRepresentable)

】SwiftUI:WKWebView在macOS上截断页面内容(NSViewRepresentable)【英文标题】:SwiftUI:WKWebViewCuttingOffPageContentonmacOS(NSViewRepresentable)【发布时间】:2019-12-1520:50:57【问题描述】:我有一个基本的NSViewRepresentable实现WKWebView,用于macOS上的Sw... 查看详情