如何在 swiftUI (macOS) 中检测按键按下和释放

2024-05-17

除了标题之外没什么可说的。我希望能够在按下按键和释放按键时(在 macOS 上)在 swiftUI 视图中执行操作。在 swiftUI 中是否有任何好的方法可以做到这一点,如果没有,有什么解决方法吗?


不幸的是,键盘事件处理是其中一个令人痛苦的明显问题,SwiftUI 首先是为 iOS 设计的,而 macOS 是事后才想到的。

如果您尝试检测的键是鼠标单击的修饰符,例如cmd, option, or shift,您可以使用.modifiers with onTapGesture以区别于未修改的onTapGesture。在这种情况下,我的经验是你想要.onTapGesture调用使用.modifiers to precede未修改的。

处理任意视图的一般按键事件需要离开 SwiftUI。

如果您只需要一个视图,一种可能性是使用AppKit这样你就可以通过普通的Cocoa接收键盘事件firstResponder机制,然后将该视图包装在 SwiftUI 中NSViewRepresentable。在这种情况下,你的包裹NSView会更新一些@State财产在NSViewRespresentable。许多在 macOS 上使用 SwiftUI 的开发人员都是这样做的。虽然这对于少量视图来说没什么问题,但如果事实证明您必须在 AppKit 中实现大量视图才能使它们在 SwiftUI 中可用,那么您就失去了使用 SwiftUI 的意义。在这种情况下,只需将其设为普通的 Cocoa 应用程序即可。

但还有另一种方法...

您可以使用另一个线程CGEventSource与 SwiftUI 结合主动轮询键盘状态@EnvironmentObject or @StateObject将键盘状态更改传达给 SwiftUIView对他们感兴趣的人。

假设您想检测何时按下向上箭头。为了检测密钥,我使用了扩展CGKeyCode.

import CoreGraphics

extension CGKeyCode
{
    // Define whatever key codes you want to detect here
    static let kVK_UpArrow: CGKeyCode = 0x7E

    var isPressed: Bool {
        CGEventSource.keyState(.combinedSessionState, key: self)
    }
}

当然,您必须使用正确的密钥代码。我有一个gist https://gist.github.com/chipjarred/cbb324c797aec865918a8045c4b51d14包含所有旧的密钥代码。如果您愿意,可以将它们重命名为更 Swifty。列出的名称可追溯到经典 MacOS,并在中定义麦金塔内部.

定义该扩展后,您可以随时测试是否按下了某个键:

if CGKeyCode.kVK_UpArrow.isPressed { 
    // Do something in response to the key press.
}

注意这些是not键向上或键向下事件。它只是一个布尔值,用于检测执行检查时是否按下了该键。为了使行为更像事件,您需要通过跟踪关键状态更改来自己完成该部分。

有多种方法可以做到这一点,下面的代码并不意味着暗示这是“最好”的方法。这简直就是a方式。无论如何,当应用程序启动时,无论您在哪里进行全局初始化,类似以下代码的内容都会被调用(或被调用)。

// These will handle sending the "event" and will be fleshed 
// out further down
func dispatchKeyDown(_ key: CGKeyCode) {...}
func dispatchKeyUp(_ key: CGKeyCode) {...}

fileprivate var keyStates: [CGKeyCode: Bool] =
[
    .kVK_UpArrow: false,
    // populate with other key codes you're interested in
]

fileprivate let sleepSem = DispatchSemaphore(value: 0)
fileprivate let someConcurrentQueue = DispatchQueue(label: "polling", attributes: .concurrent)

someConcurrentQueue.async 
{
    while true
    {
        for (code, wasPressed) in keyStates
        {
            if code.isPressed 
            {
                if !wasPressed 
                {
                    dispatchKeyDown(code)
                    keyStates[code] = true
                }
            }
            else if wasPressed 
            {
                dispatchKeyUp(code)
                keyStates[code] = false
            }
        }
        
        // Sleep long enough to avoid wasting CPU cycles, but 
        // not so long that you miss key presses.  You may 
        // need to experiment with the .milliseconds value.
        let_ = sleepSem.wait(timeout: .now() + .milliseconds(50))
    }
}

这个想法只是让一些代码定期轮询关键状态,将它们与以前的状态进行比较,在它们发生变化时调度适当的“事件”,并更新以前的状态。上面的代码通过在并发任务中运行无限循环来实现这一点。它需要创建一个DispatchQueue.concurrent属性。你不能使用它DispatchQueue.main因为那个队列是serial不是并发的,因此无限循环会阻塞主线程,程序将变得无响应。如果你已经有一个并发DispatchQueue如果您出于其他原因而使用,则可以只使用该值,而不必创建一个仅用于轮询的值。

但是,任何实现定期轮询基本目标的代码都可以,所以如果您还没有并发DispatchQueue并且不想创建一个只是为了轮询键盘状态,这将是一个合理的反对意见,这里有一个使用的替代版本DispatchQueue.main使用称为“异步链接”的技术来避免阻塞/睡眠:

fileprivate var keyStates: [CGKeyCode: Bool] =
[
    .kVK_UpArrow: false,
    // populate with other key codes you're interested in
]

fileprivate func pollKeyStates()
{
    for (code, wasPressed) in keyStates
    {
        if code.isPressed 
        {
            if !wasPressed 
            {
                dispatchKeyDown(code)
                keyStates[code] = true
            }
        }
        else if wasPressed 
        {
            dispatchKeyUp(code)
            keyStates[code] = false
        }
    }

    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50))
    {
        // The infinite loop from previous code is replaced by
        // infinite chaining.
        pollKeyStates()
    }
}

// Start up key state polling
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) {
    pollKeyStates()
}

有了代码来检测何时按下按键,您现在需要一种方法将其传达给您的 SwiftUIViews。再说一遍,剥猫皮的方法不止一种。这是一个过于简单的,将更新View每当按下向上箭头时,但您可能想要实现一些更复杂的东西......可能允许视图指定它们有兴趣响应哪些键。

class UpArrowDetector: ObservableObject
{
    @Published var isPressed: Bool = false
}

let upArrowDetector = UpArrowDetector()

func dispatchKeyDown(_ key: CGKeyCode) 
{
    if key == .kVK_UpArrow {
        upArrowDetector.isPressed = true
    }
}

func dispatchKeyUp(_ key: CGKeyCode) {
    if key == .kVK_UpArrow {
        upArrowDetector.isPressed = false
    }
}

// Now we hook it into SwiftUI
struct UpArrowDetectorView: View
{
    @StateObject var detector: UpArrowDetector

    var body: some View
    {
        Text(
            detector.isPressed 
                ? "Up-Arrow is pressed" 
                : "Up-Arrow is NOT pressed"
        )
    }
}

// Use the .environmentObject() method of `View` to inject the
// `upArrowDetector`
struct ContentView: View
{
    var body: some View
    {
        UpArrowDetectorView()
            .environmentObject(upArrowDetector)
    }
}

我在这里提供了一个完整的、可编译的、有效的示例gist https://gist.github.com/chipjarred/e39c48619e591fbf9588af3d3684bed9以您在评论中链接到的代码为图案。它对上面的代码进行了稍微重构,但所有部分都在那里,包括启动轮询代码。

我希望这能为您指明一个有用的方向。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 swiftUI (macOS) 中检测按键按下和释放 的相关文章

  • 使用 iOS 8 自定义键盘发送图像?

    我一直在为 iOS 8 开发自定义键盘 但在尝试使用键盘发送图像时偶然发现了一个问题 我做了一些研究 似乎没有一种简单的方法可以做到这一点UITextDocumentProxy因为只有NSStrings被允许 我是否忽略了使用自定义键盘发送
  • UIButton 导致无法识别的选择器发送到实例

    我正在尝试使用 for 循环创建多个按钮 但在使用 sender 函数时遇到问题 我有以下代码 func setUpButtons for i in 1 3 let btn UIButton UIButton frame CGRect x
  • 在 Cocoa OS X AVPlayer 中播放 HLS (m3u8) - Swift

    基本上我正在尝试在 Cocoa Swift 中使用 AVPlayer 播放 m3u8 HLS Live Stream 我对这门语言比较陌生 所以基本上掌握了一些示例代码 http qiita com ono matope items 23d
  • 如何将 UILabel 的值绑定到实例变量?

    我是 mac objective c 的新手 我的问题是 我想知道是否可以将 UILabel 文本绑定到变量 而不必在值更改时手动设置文本 例如 在 Mac OS 上 当我打开新的 Finder 窗口并删除文件时 任务栏中的全局可用空间就会
  • 如何接收有关与我共享的记录中所做更改的 CloudKit 通知?

    我有两个 iCloud 帐户 A and B 在两个不同的设备上 来自其中之一 A 我将 ckrecord 分享给另一个人 B 像这样 let controller UICloudSharingController controller p
  • Swift:使具有相同“形状”的两种类型符合通用协议

    我有两种不同的类型 它们代表相同的数据 并且具有完全相同的 形状 这两种不同的类型是代码生成的 我被迫处理它们 但是 我想让它们符合一个通用的协议 这样我就可以对这两种类型一视同仁 这是一个例子 假设这是我所坚持的两种代码生成类型 stru
  • 在 Mac 操作系统上使用 ffmpeg 录制视频

    我想在 mac OS 上使用 ffmpeg 以任何格式录制实时网络摄像头视频 我尝试了很多 但无法找到用于重新编码视频的命令 所以请任何人都可以告诉我 ffmpeg 命令用于使用 Mac 操作系统的网络摄像头捕获视频 提前致谢 对于 Mac
  • 根据 iOS 版本使用不同的类实现?

    iOS 11 最近添加了一个我想使用的新功能 但我仍然需要支持旧版本的 iOS 有没有一种方法可以将同一个类编写两次 并让较新版本的 iOS 使用该类的一个版本 而旧版本的 iOS 使用另一个版本 注 最初我用的是if available
  • 如何在 OSX 上安装 LaTeX .sty 文件?

    我设置了一个 LaTeX 项目 tex documents some file tex support todonotes sty where some file tex uses todonotes usepackage colorinl
  • 当 isUserInteractionEnabled false 时,SKSpriteNode 不会让触摸通过

    我正在尝试在 SpriteKit 中创建一个覆盖层 方法是使用SKSpriteNode 但是 我希望触摸穿过覆盖层 所以我设置isUserInteractionEnabled为假 然而 当我这样做时 SKSpriteNode似乎仍然吸收所有
  • 将 UIToolBar 添加到所有键盘(swift)

    我正在尝试以尽可能少的重复次数将自定义 UIToolBar 添加到我的所有键盘中 我目前的做法要求我将代码添加到所有 viewDidLoads 中 并将每个文本字段的委托分配给我正在使用的 viewController 我尝试创建自己的 U
  • Vim 和 Mac:如何在不使用 pbcopy 的情况下复制到剪贴板

    我有一个同时支持剪贴板和 xterm clipboard 的 vim 版本 然而 y or y不要复制到系统剪贴板 我知道我可以使用 w pbcopy 甚至为其创建快捷方式 但我真的想要标准方式 我也看到了 fakeclip 但希望找到一个
  • Swift:设置协议的可选属性

    如何设置协议的可选属性 例如 UITextInputTraits 有许多可选的读 写属性 当我尝试以下操作时 出现编译错误 无法分配给 textInputTraits 中的 keyboardType func initializeTextI
  • 为什么选择选择器选项后我的 SwiftUI 页面标题会发生变化?

    struct SettingsView View let settings Setting Setting name Aperture Increments options 1 3 1 2 1 Setting name Shutter Sp
  • 如何在 iPhone 上使用 SwiftUI 显示表格数据?

    我正在为当地的体育联盟开发一个应用程序 一个视图将是当前排名 每行有几个字段 球队名称 已玩的比赛 得分等 我希望球队列左对齐 其他列右对齐 似乎最好的答案是 SwiftUI 的 Table 但在 iPhone 上 它只显示第一列 我尝试过
  • 动态调度协议扩展不适用于多个目标

    这是我的主要目标中的代码 所以不是测试目标 protocol ProtocolA func dontCrash extension ProtocolA func dontCrash fatalError func tryCrash dont
  • NSPredicate IN 从数组元素查询

    对于一个古怪的标题表示歉意 我有一个 Int 数组 我想定义一个 NSPredicate 来过滤掉 connectionType 等于数组中包含的值的项目 所以基本上是这样的 fetchRequest predicate NSPredica
  • 根据内容自动更改单元格高度 - Swift

    在 Swift 中使用 UITableView 有人可以帮我根据标签 图片和描述自动更改单元格的高度吗 所有信息都正确传递 我只需要帮助格式化它 我尝试使用调整它cell frame size height 但这没有效果 我可以更改故事板中
  • ios8 键盘高度有所不同

    我使用下面的代码来获取键盘高度 该高度在带有 ios8 的 iPhone 5s 设备中与带有 ios7 的 IPhone4s 设备中有所不同 因此 当我在带有 ios8 的 iPhone5s 中点击它时 我的文本字段移动得非常高 而相同的代
  • C# 模拟VolumeMute按下

    我得到以下代码来模拟音量静音按键 DllImport coredll dll SetLastError true static extern void keybd event byte bVk byte bScan int dwFlags

随机推荐

  • Glew+GLFW Win32 无依赖项 Visual Studio

    是否可以在不将文件复制到 C 的情况下构建并链接 Glew 和 GLFW 我找不到任何说明如何在不将 DLL 复制到 C 上的 Visual Studio 目录的情况下使用这些库的文档 我只想包含项目目录中所需的所有 dll 和 lib 文
  • 将数据追加到Python字典中

    我使用以下代码从键列表中初始化字典 z df1 2 value counts keys tolist mydict dict fromkeys z None 此外 我用过 value df2 2 value counts keys toli
  • 如何使用 asyncio/aiohttp 确定最佳缓冲区大小

    在 python 中使用 asyncio 时 我们如何确定 read 的最佳参数 12字节 100 字节 async with self session get url headers headers as response chunk s
  • 如何使 StringGrid 的列适合网格的宽度?

    我已经寻找解决方案很长时间了 但没有任何运气 有谁知道一个简单的方法来做到这一点 例如 我想拉伸网格的第二列以适应网格的宽度 Use the ColWidths财产 像这样 with StringGrid1 do ColWidths 1 C
  • 具有动态警报正文的快速本地通知

    所以我可以创建一个像这样的本地通知 var localNotification UILocalNotification localNotification fireDate NSDate timeIntervalSinceNow 7 loc
  • 为什么 std::string 分配两次?

    我写了一个自定义分配器std string and std vector如下 include
  • 在GDB中的每一行设置断点

    有没有办法用GDB在代码的每一行设置断点 明明我不想打b addr对于每一行 所以我想知道是否有一种快速的方法来做到这一点 Edit请注意 我正在运行由其他人创建的二进制文件 并且我无权访问源代码 不幸的是 该二进制文件尚未使用 g 标志进
  • 覆盖供应商自动加载编辑器

    有没有办法让您创建的自动加载文件在调用供应商自动加载之前运行 我们似乎遇到了 SimpleSAML 的自动加载覆盖我们创建的自动加载文件之一的问题 我是 Composer 的新手 似乎无法在网上找到任何解决方案 我尝试将我们的自动加载文件包
  • Django 中从 sqlite 迁移到 postgresql

    我想迁移自sqlite to PostgreSQL db 我安装了 postgresql 并在其 shell 上创建数据库 然后配置我的 django 设置如下 default ENGINE django db backends postg
  • 在 Chrome 中使用 React 添加新的 DOM 项目不会保持预期的滚动位置

    在 React 中向状态数组添加新项目时 我遇到了一个意外的问题 这会导致更多项目被添加到 DOM 中 在 Safari 和 Firefox 中 这会导致新的 DOM 项目添加到折叠下方 我必须向下滚动才能看到新项目 在 Chrome 中
  • Air for Android:动画导致我的游戏出现滞后

    我正在为 android 平台制作一款 cs6 air 游戏 当我为游戏制作动画时 我使用 3D 软件搅拌器 在 Blender 中 我制作了一个动画 然后将其渲染为一系列 PNG 图像 并将其导入到 Flash CS6 中 因此 如果我要
  • 如何创建不返回任何内容的函数

    我想写一个函数pl pgsql 我在用着Postgres 企业管理器 v3并使用 shell 来创建一个函数 但在 shell 中我必须定义返回类型 如果我不定义返回类型 我将无法创建函数 如何创建一个不返回结果的函数 即创建一个新表的函数
  • 显示即将到来的 Facebook 好友生日

    我仍然得到列表脸书好友以 的形式一月至十二月按升序排列 见下图 但现在我想以以下形式显示 Facebook 好友列表即将到来的生日 Like 最近的热门内容 我使用以下查询来获取好友列表 Log d LOG TAG requestFrien
  • 寻找网站测试自动化的方法

    我们开发定制调查网站 我正在寻找一种方法来自动化这些网站的模式测试 调查通常包含许多复杂的规则和分支 这些规则和分支也会根据项目的响应方式而触发 所有调查在发布给客户之前都经过严格测试 此测试需要大量的手动工作 我想了解一些可以通过回答问题
  • 良好的错误处理实践

    对于 ASP NET 站点来说 什么是良好的错误处理实践 例子 谢谢 与任何 net 项目一样 我发现最好的方法是仅捕获可能发生在给定页面上的特定错误类型 例如 您可以捕获用户给定输入的格式异常 以防 JavaScript 验证失败并且您没
  • 如何重现 Ridge(normalize=True) 的行为?

    这段代码 from sklearn pipeline import make pipeline from sklearn preprocessing import StandardScaler from sklearn linear mod
  • 外键和索引

    我有 2 张桌子 products and 类别 每个类别有很多产品 一个产品可以属于多个类别 products product id int primary auto increment name unique etc 类别 catego
  • 如何检测 swiftui 中是否存在键盘

    我想知道按下按钮时键盘是否存在 我该怎么做 我已经尝试过 但我没有任何运气 谢谢 使用该协议 KeyboardReadable 你可以符合任何View并从中获取键盘更新 KeyboardReadable协议 import Combine i
  • BlueCove 与 Bluez 卡盘“无法打开 SDP 会话。[2] 没有这样的文件或目录”

    我正在尝试编写一个简单的蓝牙服务器 它接受来自我的 HeartRate Device 蓝牙 LE 的连接 但它总是引发异常 javax bluetooth ServiceRegistrationException Can not open
  • 如何在 swiftUI (macOS) 中检测按键按下和释放

    除了标题之外没什么可说的 我希望能够在按下按键和释放按键时 在 macOS 上 在 swiftUI 视图中执行操作 在 swiftUI 中是否有任何好的方法可以做到这一点 如果没有 有什么解决方法吗 不幸的是 键盘事件处理是其中一个令人痛苦