3

before iOS 16 I could present a UIViewController over the keyboard without having it close, by using this code:

if let window = UIApplication.shared.windows.last, 
    String(describing: type(of: window)).equals("UIRemoteKeyboardWindow") {

    let presentingVC = window.rootViewController

    presentingVC.present(self.myViewController, animated: animated)
}

I tried running this code in the emulator, emulating iOS 16 on an iPhone 13.

Unfortunately, running this code with the keyboard open (as before), "UIRemoteKeyboardWindow" is no longer present among the windows.

I only found "UITextEffectsWindow", but presenting "myViewController" from there it is displayed under the keyboard.

Has anyone experienced this problem and knows how to start a UIViewController over the keyboard without having it close?

I need this code mainly because I have custom pickers that allow the user to enter values that update the UI without the keyboard being closed.

UPDATE:

I built and run the app from the current stable Xcode Version (13.4.1) on iPhone 13 simulator with iOS 16 and now I'm able to find UIRemoteKeyboardWindow. I used to start the app from Xcode versions 14 beta. Can not finding UIRemoteKeyboardWindow be an error due to Xcode beta versions?

Meroelyth
  • 5,310
  • 9
  • 42
  • 52
  • It sounds like you were always doing something illegal and hacky and now Apple is trying to close the loophole. Go Apple. – matt Sep 03 '22 at 13:27
  • Do you know a "legal" way to present a UIViewController on the top of keyboard @matt? Why would apple want the keyboard to be closed if I have to show a picker that insert something inside the text? Unfortunately "legal" UIColorPickerViewController (for example) is larger than the remain portion of the space that is not occupied by the keyboard. – Meroelyth Sep 05 '22 at 07:58

3 Answers3

1

In iOS16 UIRemoteKeyboardWindow is not seen in windows array of UIApplication.

It is possible to get access to UIInputWindowController as a parent of any inputViewController.

Then you can present any view controller over UIInputWindowController without hiding a keyboard.

If you use custom input view controller (instead of the keyboard) you should observe UIResponder.keyboardWillChangeFrameNotification to get

inputViewController.parent

If you use only the keyboard input you should make a fake input view controller, make your view controller the first responder on start, catch UIResponder.keyboardWillChangeFrameNotification to store UIInputWindowController object and then resign first responder to hide fake input view immediately.

Code example

class SomeViewController: UIViewController {

    private var currentInputViewController: UIInputViewController?
    private var keyboardWindowController: UIViewController?

    override var inputViewController: UIInputViewController?
       isFirstResponder ? currentInputViewController : nil
    }

    init() {
        super.init()

        // some code

        NotificationCenter.default.addObserver(self, selector: #selector(storeKeyboardWindowController), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    }

    @objc private func storeKeyboardWindowController() {
        if let vc = currentInputViewController?.parent {
            keyboardWindowController = vc
        }
    }

    @objc func inputViewButtonClicked() {
        let vc = AnotherViewControoler()
        keyboardWindowController?.present(vc, animated: true)
    }
}
malex
  • 9,874
  • 3
  • 56
  • 77
  • Hi @malex, Thank you for your suggestion. Could you provide a working example that allows you to present a viewcontroller over of the keyboard? I tried to follow your advice but failed in any way. – Meroelyth Sep 03 '22 at 08:13
  • Update: I build and run the app with the current stable Xcode Version (13.4.1) on iPhone 13 simulator with iOS 16 and now I'm able to find UIRemoteKeyboardWindow. I used to start the app from Xcode versions 14 beta. Can not finding UIRemoteKeyboardWindow be an error due to Xcode beta versions? – Meroelyth Sep 03 '22 at 12:55
  • @Meroelyth did you find a solution for this? I am also trying to add a view over the keyboard. – Rakesha Shastri Sep 08 '22 at 05:15
  • @Meroelyth I've just added the code example – malex Sep 09 '22 at 01:05
  • @Meroelyth Now I have Xcode 14 RC and iOS16 RC. 'UIApplication.sharedApplication.windows' array still has no UIRemoteKeyboardWindow – malex Sep 09 '22 at 01:06
  • Hi @malex, as I wrote before, I simply launched the app from xcode 13.4.1 stable version on the emulator (iPhone 13 with iOS16) and in this way I was able to get UIRemoteKeyboardWindow like in previous iOS versions. I submitted the app to Testflight and users with iOS16 confirmed that the UIViewControllers were being launched over the keyboard. Thanks for your code, I'll try it asap !!! Have you already tried it? Works? – Meroelyth Sep 10 '22 at 07:40
  • Hi again @malex, I tried with the new stable XCode 14 and UIApplication.sharedApplication.windows' array has no UIRemoteKeyboardWindow. I tried your solution but I don't understand how to use currentInputViewController because is never initialized. Thank you – Meroelyth Sep 14 '22 at 16:04
  • @Meroelyth, currentInputViewController is your own VC which you want to use instead of the system keyboard. For example, you want to make image preview gallery collection picker instead of the keyboard. You set this VC as InputViewController for UIResponder, store in currentInputViewController variable and then use it in UIResponder.keyboardWillChangeFrameNotification handler. I didn't find any solution to find keyboardWindowController for the system keyboard. – malex Sep 14 '22 at 19:26
  • @malex UIRemoteKeyboardWindow is now completely gone on Xcode 14. Are you able to get it? – gran_profaci Jun 10 '23 at 21:19
0

You could find UIRemoteKeyboardWindow by subscribing to UIWindow.didBecomeVisibleNotification notification. It will be triggered as soon as keyboard appears.

Code example:

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeVisible(_:)), name: UIWindow.didBecomeVisibleNotification, object: nil)
}

@objc private func windowDidBecomeVisible(_ info: Notification) {
    let type = String(describing: info.object)
    if type.range(of: "UIRemoteKeyboardWindow") != nil {
        print("That's UIRemoteKeyboardWindow")
    }
}
vitalytimofeev
  • 261
  • 2
  • 9
0

Can not finding UIRemoteKeyboardWindow be an error due to Xcode beta versions

It's not an error. Starting in iOS 17, the keyboard is out-of-process. That means it belongs to the system; it is no longer part of your app at all.

This is probably to prevent exactly the kind of hanky-panky you were doing. Basically what you were doing was always wrong, and now you're busted.

matt
  • 515,959
  • 87
  • 875
  • 1,141