SwiftUI虚拟键盘如何通过CGEvent实现按键持续按住效果?
实现macOS虚拟键盘持续按住按键效果
要让macOS识别按键处于持续按住状态,不能只发送单次keyDown或用定时器重复触发事件,得模拟实体键盘的**自动重复(Auto Repeat)**机制,通过正确配置CGEvent属性并配合系统重复逻辑实现。
核心实现步骤
发送带自动重复标记的初始
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) } }启动自动重复事件循环
初始延迟后,以固定间隔发送带自动重复标记的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) } }结束按住状态:发送
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
相关产品推荐
相关产品推荐

