0

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.

Desired shortcuts

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:

unwanted Edit options

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.

Kpalser
  • 843
  • 7
  • 8
  • Are you searching for programatic keywords, or the displayed strings? If neither works, I would start looking for image assets. – benc Mar 14 '23 at 22:50
  • @benc I've been doing a bit of both. I wouldn't have much to go on in terms of icon assets because it is all textual, i.e. "Edit", "Emoji fn E". My gut feeling is that there is a field control in the SwiftUI child scenes that triggers this. I could try substituting a child scene with an empty view to see if the unwanted menu aspects are still present. But that wouldn't explain why they persist when the child scene is completely dismissed. – Kpalser Mar 15 '23 at 17:32
  • So, you can't find any reference to the entry in the code? Any idea if the problem is global, like if you graft in a new set of views and fields into the app, does this still happen? – benc Mar 16 '23 at 19:30
  • @benc see my update. Something in my main view is being treated like it has text control functionality. I only have one programatic first responder but its keyCommands are explicitly set. Interestingly, I noticed that when an alert view is displayed and my firstResponder no longer has focus, then the CMD key press results in just the unwanted Edit options being shown in the HUD. – Kpalser Mar 22 '23 at 06:54

1 Answers1

0

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)
    }

}
Kpalser
  • 843
  • 7
  • 8