macOS SwiftUI开发:检测键盘快捷键是否与系统快捷键冲突
实现方案
SwiftUI 本身没有提供直接查询指定快捷键是否被系统占用的官方接口,你可以通过 macOS 内置的 Carbon 框架读取系统已注册的符号快捷键表,完成组合键冲突检测,实现逻辑完全覆盖目标函数的能力要求。
核心实现逻辑
- 完成 SwiftUI 类型到 Carbon 类型的映射:将
KeyEquivalent转换为对应键盘布局下的虚拟键码,将EventModifiers转换为 Carbon 识别的修饰键掩码 - 调用 Carbon 提供的系统接口拉取当前所有生效的系统快捷键配置,遍历比对键值和修饰键组合,匹配到则说明该组合已被系统占用
- 该方案不需要申请特殊权限,可直接读取用户自定义修改后的系统快捷键配置,不是硬编码的默认快捷键列表
完整代码实现
首先需要在文件顶部导入依赖框架:
import Carbon import SwiftUI import AppKit
先实现两个用于类型转换的私有扩展:
private extension EventModifiers { var carbonModifiers: UInt32 { var flags: UInt32 = 0 if contains(.command) { flags |= UInt32(cmdKey) } if contains(.option) { flags |= UInt32(optionKey) } if contains(.control) { flags |= UInt32(controlKey) } if contains(.shift) { flags |= UInt32(shiftKey) } if contains(.capsLock) { flags |= UInt32(alphaLock) } return flags } } private extension KeyEquivalent { func toVirtualKeyCode() -> UInt32? { let inputChar = Character(self.character.lowercased()) // 特殊功能键单独映射 switch inputChar { case "\r", "\n": return UInt32(kVK_Return) case "\u{1b}": return UInt32(kVK_Escape) case "\u{7f}": return UInt32(kVK_Delete) case " ": return UInt32(kVK_Space) case "\t": return UInt32(kVK_Tab) case Character(UnicodeScalar(NSUpArrowFunctionKey)!): return UInt32(kVK_UpArrow) case Character(UnicodeScalar(NSDownArrowFunctionKey)!): return UInt32(kVK_DownArrow) case Character(UnicodeScalar(NSLeftArrowFunctionKey)!): return UInt32(kVK_LeftArrow) case Character(UnicodeScalar(NSRightArrowFunctionKey)!): return UInt32(kVK_RightArrow) default: break } // 普通字符基于当前键盘布局转换为虚拟键码 guard let keyboardSource = TISGetInputSourceProperty(TISCopyCurrentKeyboardInputSource().takeRetainedValue(), kTISPropertyUnicodeKeyLayoutData) else { return nil } let layoutData = unsafeBitCast(keyboardSource, to: CFData.self) as Data let keyLayout = layoutData.withUnsafeBytes { ptr -> UnsafePointer<UCKeyboardLayout> in ptr.bindMemory(to: UCKeyboardLayout.self).baseAddress! } var deadKeyState: UInt32 = 0 let maxCharLength = 4 var charLength = 0 var charBuffer = [UniChar](repeating: 0, count: maxCharLength) for keyCode in 0..<128 { let translateStatus = UCKeyTranslate( keyLayout, UInt16(keyCode), UInt16(kUCKeyActionDisplay), 0, UInt32(LMGetKbdType()), OptionBits(kUCKeyTranslateNoDeadKeysBit), &deadKeyState, maxCharLength, &charLength, &charBuffer ) guard translateStatus == noErr, charLength > 0 else { continue } let matchedStr = String(utf16CodeUnits: charBuffer, count: charLength).lowercased() if matchedStr == String(inputChar) { return UInt32(keyCode) } } return nil } }
实现目标功能函数:
func isKeyboardShortcutReservedForSystemShortcut(key: KeyEquivalent, modifiers: EventModifiers) -> Bool { guard let keyCode = key.toVirtualKeyCode() else { // 无法识别的按键默认返回false,可根据业务需求调整默认值 return false } let targetModifiers = modifiers.carbonModifiers guard let systemHotkeyList = CopySymbolicHotKeys()?.takeRetainedValue() as? [[String: Any]] else { return false } for hotkey in systemHotkeyList { // 跳过未启用的系统快捷键 guard let isEnabled = hotkey[kHISymbolicHotKeyEnabled] as? Bool, isEnabled else { continue } guard let sysKeyCode = hotkey[kHISymbolicHotKeyCode] as? UInt32, let sysModifiers = hotkey[kHISymbolicHotKeyModifiers] as? UInt32 else { continue } // 过滤无关修饰键标记,仅比对核心功能修饰键 let filteredSysMods = sysModifiers & (UInt32(cmdKey | optionKey | controlKey | shiftKey | alphaLock)) if sysKeyCode == keyCode && filteredSysMods == targetModifiers { return true } } return false }
典型场景使用示例
- 按需为Button添加
keyboardShortcut修饰符
Button("保存文档") { // 业务逻辑实现 } // 仅当快捷键未被系统占用时才绑定快捷键 .modifier { content in if !isKeyboardShortcutReservedForSystemShortcut(key: "s", modifiers: .command) { content.keyboardShortcut("s", modifiers: .command) } else { content } }
- 快捷键配置页冲突提示
// 用户完成快捷键选择后执行校验 if isKeyboardShortcutReservedForSystemShortcut(key: selectedKey, modifiers: selectedModifiers) { warningMessage = "当前快捷键已被系统功能占用,请选择其他组合" showWarning = true }
注意事项
- 该实现可覆盖「系统设置-键盘-快捷键」面板中所有启用的快捷键,包括用户手动修改过的自定义规则,不是写死的默认快捷键列表
- 仅能检测系统级保留的快捷键,无法检测其他第三方应用注册的全局快捷键,这类冲突需要监听全局事件分发实现,不属于系统快捷键占用的检测范畴
- 键码转换逻辑兼容不同键盘布局,不会因为用户使用非QWERTY布局出现检测错误
内容的提问来源于stack exchange,提问作者bzyr
相关产品推荐
相关产品推荐

