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

如何排查多窗口iPadOS应用中多余外接键盘快捷键的来源?

排查多余Edit快捷键来源的方法

1. 全局Hook UIKeyCommand的创建

通过Method Swizzling替换UIKeyCommand的初始化方法,打印调用栈定位来源:

import UIKit

extension UIKeyCommand {
    static func swizzleKeyCommandInit() {
        let originalSelector = #selector(init(title:image:action:input:modifierFlags:propertyList:))
        let swizzledSelector = #selector(swizzled_init(title:image:action:input:modifierFlags:propertyList:))
        
        guard let originalMethod = class_getInstanceMethod(self, originalSelector),
              let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else { return }
        
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
    
    @objc private func swizzled_init(title: String?, image: UIImage?, action: Selector, input: String?, modifierFlags: UIKeyModifierFlags, propertyList: Any?) -> UIKeyCommand {
        if let title = title, title.contains("Edit") {
            print("===== 发现Edit快捷键来源 =====")
            Thread.callStackSymbols.forEach { print($0) }
            print("===========================")
        }
        return swizzled_init(title: title, image: image, action: action, input: input, modifierFlags: modifierFlags, propertyList: propertyList)
    }
}

在AppDelegate的application(_:didFinishLaunchingWithOptions:)中调用UIKeyCommand.swizzleKeyCommandInit()启动Hook。

2. 监控firstResponder的变化

通过Swizzling替换UIResponder的becomeFirstResponder()方法,记录第一响应者的变化:

extension UIResponder {
    @objc dynamic func swizzled_becomeFirstResponder() -> Bool {
        let result = swizzled_becomeFirstResponder()
        if result {
            print("当前第一响应者:\(self)")
            if self is UITextField || self is UITextView || self is UITextInput {
                print("文本控件成为第一响应者,可能提供Edit快捷键")
                Thread.callStackSymbols.forEach { print($0) }
            }
        }
        return result
    }
    
    static func swizzleBecomeFirstResponder() {
        let originalSelector = #selector(becomeFirstResponder)
        let swizzledSelector = #selector(swizzled_becomeFirstResponder)
        
        guard let originalMethod = class_getInstanceMethod(self, originalSelector),
              let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else { return }
        
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

同样在App启动时调用UIResponder.swizzleBecomeFirstResponder()。

3. 检查SwiftUI场景的隐含键盘命令

SwiftUI的文本类视图(如TextField、TextEditor)会自动注入Edit相关快捷键,即使子场景关闭也可能残留响应者链。可以在SwiftUI视图中显式禁用默认命令:

struct YourSwiftUISceneView: View {
    var body: some View {
        TextEditor(text: .constant(""))
            .keyboardShortcuts(.disabled) // 禁用默认键盘快捷键
    }
}

4. 遍历响应者链排查

在问题复现后,调用以下方法遍历主窗口的响应者链,检查每个响应者的keyCommands:

func printResponderChainKeyCommands() {
    guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else { return }
    var responder: UIResponder? = window.firstResponder
    while responder != nil {
        if let commands = responder?.keyCommands {
            print("响应者\(responder!)的keyCommands:\(commands)")
            for command in commands {
                if let title = command.title, title.contains("Edit") {
                    print("找到Edit命令:\(command)")
                }
            }
        }
        responder = responder?.next
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 23:47:04