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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 23:18:17