Add Return key subview to keyboard

That's an old question (this thread https://developer.apple.com/forums/thread/16375 dates back iOS9 or this other thread https://stackoverflow.com/questions/67249956/uisearchbar-warning-uitexteffectswindow-should-not-become-key-please-file-a-bu), but I've not found a comprehensive answer yet.

I need to add a Return key to a numeric keyboard (BTW, how is it it does not exist in standard ?)

I used to do it using

let keyBoardWindow = UIApplication.shared.windows.last 

to get the keyboard window and then adding the subview (self.returnButton) to it

keyBoardWindow?.addSubview(self.returnButton)

Worked great and still works OK with iOS 15.

But we are told to use connectedScenes instead…

So I adapt code:

      var keyBoardWindow: UIWindow? = nil 
      
      if #available(iOS 13.0, *) {    //  use connectedScenes
             let scenes = UIApplication.shared.connectedScenes
             let windowScene = scenes.first as? UIWindowScene
             if let kbWindow = windowScene?.windows.last  {
                  keyBoardWindow = kbWindow
              }
        } else {
             keyBoardWindow = UIApplication.shared.windows.last 
         }

It runs, but subview does not show.

The problem is that keyboardWindow used to be a UIRemoteKeyboardWindow (the true keyboard window) and now is UITextEffectsWindow, which is not the real keyboardWindow. So adding subview to it adds improperly in the hierarchy.

Note: someone detailed the view hierarchy here: https://developer.apple.com/forums/thread/664547

UIWindow
UITextEffectsWindow
    UIInputWindowController
        UIInputSetContainerView
              UIInputSetHostView
              UIEditingOverlayViewController
                     UIEditingOverlayGestureView

I guess I have to add subview to something else than UITextEffectsWindow (keyBoardWindow), but to what ?

I tried

     keyBoardWindow = kbWindow.superview as? UIWindow

to no avail.

I also tried to debug view hierarchy, but keyboard does not show in debugView.

Accepted Reply

I need to add a Return key to a numeric keyboard (BTW, how is it it does not exist in standard ?)

Any chance you can just do this with an inputAccessoryView? That’s a supported API and It Just Works™.

I guess I have to add subview to something else than UITextEffectsWindow (keyBoardWindow), but to what ?

I’m sure you know this already, but (for those in the wider audience who may not know) poking around in a private, undocumented view hierarchy is fragile and unsupported. That way madness lies.

Replies

I need to add a Return key to a numeric keyboard (BTW, how is it it does not exist in standard ?)

Any chance you can just do this with an inputAccessoryView? That’s a supported API and It Just Works™.

I guess I have to add subview to something else than UITextEffectsWindow (keyBoardWindow), but to what ?

I’m sure you know this already, but (for those in the wider audience who may not know) poking around in a private, undocumented view hierarchy is fragile and unsupported. That way madness lies.

Any chance you can just do this with an inputAccessoryView?

Not really, I want the return / Done key to be at the bottom of keyboard, not as a button at the top.

.

poking around in a private, undocumented view hierarchy is fragile

Yes, I know, that's why I'm looking for an official way to do it…

to get the keyboard window and then adding the subview (self.returnButton) to it

keyBoardWindow?.addSubview(self.returnButton)

Please do not do this. This is not your window to add content to as mentioned by Scott. If you'd like to see something offered by UIKit, or something we offer currently modified, please file a feedback. Doing something like this will only cause you pain when we invariably change the implementation and break you.

      var keyBoardWindow: UIWindow? = nil 
      
      if #available(iOS 13.0, *) {    //  use connectedScenes
             let scenes = UIApplication.shared.connectedScenes
             let windowScene = scenes.first as? UIWindowScene
             if let kbWindow = windowScene?.windows.last  {
                  keyBoardWindow = kbWindow
              }
        } else {
             keyBoardWindow = UIApplication.shared.windows.last 
         }

First, looping over connectedScenes or assuming a specific scene is first or a window is last is not future proof in the slightest. Scenes and windows should be primarily obtained through context: view.window.windowScene etc. If you must loop over scenes, you should always always take care to look at the session.role of the scene, as there are session roles you may not care about.

Second, as you've found out, this code is not going to work. Given that you are interacting with undocumented and private windows, I'm not going to go into more private details as to why. Therefore, the actual solutions available to you are:

  1. Use inputAccessoryView as that's API as Scott mentioned
  2. Perhaps make your own inputView that is designed exactly the way you want
  3. Another API related solution…
  4. File a feedback asking for what you'd like to see but that currently does not exist…

The problem is that keyboardWindow used to be a UIRemoteKeyboardWindow (the true keyboard window) and now is UITextEffectsWindow, which is not the real keyboardWindow.

For what it is worth, this is not a true statement. The keyboard window class is unchanged in iOS 15.

I also tried to debug view hierarchy, but keyboard does not show in debugView.

See comments about this being private and undocumented. There's a reason it isn't present and that should indicate to you that you should not be touching it.

I also tried to debug view hierarchy, but keyboard does not show in debugView.

FWIW, I can see the keyboard window in the view debugger, but it’s in a special separate scene rather than the main scene. And in the memory graph I can find no path to access it. So this proposed hack would be not only tricky and fragile, but seemingly impossible.

File a feedback asking for what you'd like to see but that currently does not exist

Run the keyboard out-of-process like SFSafariViewController so people can’t even begin to try to hack it. 😉

Thanks for all the wise advices, I will give up the adding of a button in the keyboard and use inputAccessoryView instead.

But this is a significant loss of screen estate: 25 more at top in addition to 40 pixels or so already unused at the bottom, …

There is a solution to save screen real estate.

Instead of creating a toolbar, just create a button that is positioned over the top-right corner of the keyboard. It is added to the VC view, not to the keyboard's.

Here is the result: