如何排查多窗口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
相关产品推荐
相关产品推荐

