You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI虚拟键盘如何通过CGEvent实现按键持续按住效果?

实现macOS虚拟键盘持续按住按键效果

要让macOS识别按键处于持续按住状态,不能只发送单次keyDown或用定时器重复触发事件,得模拟实体键盘的**自动重复(Auto Repeat)**机制,通过正确配置CGEvent属性并配合系统重复逻辑实现。

核心实现步骤

  1. 发送带自动重复标记的初始keyDown事件
    创建事件时需标记为自动重复序列的起始事件,让系统识别为持续按住的触发信号:

    private func startKeyHold(_ keyCode: UInt16) {
        let eventSource = CGEventSource(stateID: .hidSystemState)
        let keyDownEvent = CGEvent(keyboardEventSource: eventSource,
                                  virtualKey: keyCode,
                                  keyDown: true)
        // 标记事件为自动重复序列的一部分
        keyDownEvent?.flags.insert(.maskAutorepeat)
        keyDownEvent?.post(tap: .cghidEventTap)
        
        // 模拟系统初始延迟(约500ms)后启动自动重复
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
            self?.startAutoRepeat(for: keyCode)
        }
    }
    
  2. 启动自动重复事件循环
    初始延迟后,以固定间隔发送带自动重复标记的keyDown事件,匹配macOS原生重复频率(约30ms/次):

    private var repeatTimer: Timer?
    
    private func startAutoRepeat(for keyCode: UInt16) {
        repeatTimer = Timer.scheduledTimer(withTimeInterval: 0.03, repeats: true) { [weak self] _ in
            guard let self = self else { return }
            let eventSource = CGEventSource(stateID: .hidSystemState)
            let repeatEvent = CGEvent(keyboardEventSource: eventSource,
                                     virtualKey: keyCode,
                                     keyDown: true)
            repeatEvent?.flags.insert(.maskAutorepeat)
            repeatEvent?.post(tap: .cghidEventTap)
        }
    }
    
  3. 结束按住状态:发送keyUp并停止定时器
    用户释放虚拟按键时,必须发送keyUp事件终止持续状态:

    private func endKeyHold(_ keyCode: UInt16) {
        repeatTimer?.invalidate()
        repeatTimer = nil
        
        let eventSource = CGEventSource(stateID: .hidSystemState)
        let keyUpEvent = CGEvent(keyboardEventSource: eventSource,
                                virtualKey: keyCode,
                                keyDown: false)
        keyUpEvent?.post(tap: .cghidEventTap)
    }
    

关键细节说明

  • maskAutorepeat标记:这是让系统识别持续按住的核心,它告诉系统当前keyDown属于自动重复序列,而非单次按键。
  • 事件源选择:使用.hidSystemState作为事件源,确保虚拟键盘事件和实体键盘事件同源,被系统正确处理。
  • 定时器参数:初始延迟500ms、重复间隔30ms是macOS默认值,可根据需求微调以贴近原生体验。

SwiftUI按键状态绑定示例

在自定义按键视图中管理按住状态,关联上述方法:

struct KeyButton: View {
    let keyCode: UInt16
    @State private var isHeld = false
    
    var body: some View {
        Button(action: {
            // 单次点击逻辑
            let eventSource = CGEventSource(stateID: .hidSystemState)
            let downEvent = CGEvent(keyboardEventSource: eventSource, virtualKey: keyCode, keyDown: true)
            let upEvent = CGEvent(keyboardEventSource: eventSource, virtualKey: keyCode, keyDown: false)
            downEvent?.post(tap: .cghidEventTap)
            upEvent?.post(tap: .cghidEventTap)
        }) {
            Text(/* 按键显示文本 */)
                .frame(minWidth: 40, minHeight: 40)
                .background(isHeld ? Color.gray : Color.white)
                .cornerRadius(4)
        }
        .onLongPressGesture(minimumDuration: 0.1, pressing: { pressing in
            isHeld = pressing
            pressing ? startKeyHold(keyCode) : endKeyHold(keyCode)
        })
    }
    
    // 嵌入上述startKeyHold、startAutoRepeat、endKeyHold方法
}

内容的提问来源于stack exchange,提问作者angryip

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 23:00:41