At the time of writing, I’m developing with Xcode 14.2 and iPadOS 16.3.1.
I am updating an existing app to support multiple windows/scenes for iPadOS. The main scene uses UIKit and the child scenes use mainly SwiftUI. I turned my attention to testing external keyboard shortcuts, which were already implemented from previous code in the app, i.e: holding down the CMD key on the external keyboard or keyboard of computer running the device simulator.
These shortcut options are implemented from a view in the main scene that returns values via:
override var keyCommands: [UIKeyCommand]?
However, sometimes I see unwanted/unnecessary Edit options that I have not implemented explicitly in the project:
Occasionally, this unwanted Edit option seems to be present after opening the app. It always seems to be present after opening a child scene. Even when completely closing a child scene (no longer present in UIApplication.shared.openSessions / connectedScenes), the Edit aspect is still shown.
There’s nothing obvious in the code of the other scenes to which I can attribute the unwanted options. Any ideas how to add tracing to determine the source of the Edit option?
Update: I have ruled out opening child scenes as being the cause. It happens also when the window resizes from rotating the device. Hence, something in the main scene must be a cause. Adding test code to recursively inspect all subviews did not find anything odd such as two views set to being firstResponder. The Edit options look like something a text control might return but I haven't spotted anything yet that might provide such a source.
I found that the UIApplication was responding true to an internal _handleLegacyEmojiKeyboardShortcut: selector when its canPerformAction(...) method was invoked. I don't know why this occurs but it seems to start responding true be after the window has resized (e.g. rotating the device).
I discovered this with the following trace code to the view where the keyCommands are overridden:
func processResponders(_ action: Selector, withSender sender: Any?, next: UIResponder?) {
if let next {
let result = next.canPerformAction(action, withSender: sender)
print("******** canPerformAction \(next) \(action) \(String(describing: sender)) \(result)")
processResponders(action, withSender: sender, next: next.next)
}
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
let result = super.canPerformAction(action, withSender: sender)
print("******** canPerformAction \(action) \(String(describing: sender)) \(result)")
processResponders(action, withSender: sender, next: next)
return result
}
The solution I chose was to define a custom UIApplication with the following to stop the application class returning true:
import UIKit
class CustomApplication: UIApplication {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
guard sender is UIKeyCommand == false else { return false }
return super.canPerformAction(action, withSender: sender)
}
}