11

So I basically have a form, consisting of several text fields. The user types into the fields as usual. But the user also has the option of double-tapping a text field, which presents a modal view controller, allowing the user to choose from a number of options relating to that field.

Can I somehow present the modal "over" the keyboard, such that when it is dismissed, the keyboard is still active for the field that had been first responder before I presented the modal?

Right now, the keyboard dismisses while the modal appears, and reappears as the modal is dismissed. It looks clunky to me, and distracting. Would love to streamline it, and reduce the amount of animation onscreen.

DanM
  • 7,037
  • 11
  • 51
  • 86
  • `It looks clunky to me, and distracting.` Well that's how iOS handles modals. – Andrew Aug 07 '14 at 04:01
  • please refer to this: http://stackoverflow.com/questions/3372333/ipad-keyboard-will-not-dismiss-if-modal-viewcontroller-presentation-style-is-uim – Vinaykrishnan Aug 07 '14 at 14:03
  • Sounding like there really is no way to do this. – DanM Aug 07 '14 at 14:37
  • Why didn't you select the answer by Rob B? Its excellent and he went to a lot of trouble to respond to you... You could do it now! – David H Jun 16 '16 at 15:36
  • another working example working with a uiwindow is here http://stackoverflow.com/questions/32647890/show-uialertcontroller-over-keyboard – user1709076 Jun 20 '16 at 01:47

6 Answers6

17

Edit: I've updated this answer for iOS 12 and Swift. The revised example project (containing new Swift and updated Objective-C implementations) is here.


You can create a new UIWindow and place that over the default window while hiding the keyboard's window.


Animated example of overlaying a new UIWindow over the existing one


I have an example project on Github here, but the basic process is below.

  • Create a new UIViewController class for your modal view. I called mine OverlayViewController. Set up the corresponding view as you wish. Per your question you need to pass back some options, so I made a delegate protocol OverlayViewController and will make the primary window's root view controller (class ViewController) our delegate.
protocol OverlayViewControllerDelegate: class {
  func optionChosen(option: YourOptionsEnum)
}
  • Add some supporting properties to our original view controller.
class ViewController: UIViewController {
  /// The text field that responds to a double-tap.
  @IBOutlet private weak var firstField: UITextField!
  /// A simple label that shows we received a message back from the overlay.
  @IBOutlet private weak var label: UILabel!
  /// The window that will appear over our existing one.
  private var overlayWindow: UIWindow?
  • Add a UITapGestureRecognizer to your UITextField.
override func viewDidLoad() {
  super.viewDidLoad()

  // Set up gesture recognizer
  let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
  doubleTapRecognizer.numberOfTapsRequired = 2
  doubleTapRecognizer.delegate = self

  firstField.addGestureRecognizer(doubleTapRecognizer)

  firstField.becomeFirstResponder()
}
  • UITextField has a built-in gesture recognizer, so we need to allow multiple UIGestureRecognizers to operate simultaneously.
extension ViewController: UIGestureRecognizerDelegate {
  // Our gesture recognizer clashes with UITextField's.
  // Need to allow both to work simultaneously.
  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                         shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
  }
}
  • This is the interesting part. When the gesture recognizer is triggered, create the new UIWindow, assign your OverlayViewController as the root view controller, and show it. Note that we set the window level to UIWindowLevelAlert so it will appear in front. However, the keyboard will still be in front despite the alert window level, so we have to manually hide its window, too.

It is important to not set the new UIWindow as key or to change the first responder from the UITextField or the keyboard will be dismissed.

Previously (before iOS 10?) we could get away with overlayWindow.makeKeyAndVisible(), but now setting it as key will dismiss the keyboard. Also, the keyboard's window now has a non-standard UIWindow.Level value that is in front of every publicly defined value. I've worked around that by finding the keyboard's window in the hierarchy and hiding it instead.

@objc func handleDoubleTap() {
    // Prepare the overlay window
    guard let overlayFrame = view?.window?.frame else { return }
    overlayWindow = UIWindow(frame: overlayFrame)
    overlayWindow?.windowLevel = .alert
    let overlayVC = OverlayViewController.init(nibName: "OverlayViewController", bundle: nil)
    overlayWindow?.rootViewController = overlayVC
    overlayVC.delegate = self

    // The keyboard's window always appears to be the last in the hierarchy.
    let keyboardWindow = UIApplication.shared.windows.last
    keyboardWindow?.isHidden = true
}
  • The overlay window is now the original window. The user can now select whatever options you built into the overlay view. After your user selects an option, your delegate should take whatever action you intend and then dismiss the overlay window and show the keyboard again.
func optionChosen(option: YourOptionsEnum) {
  // Your code goes here. Take action based on the option chosen.
  // ...

  // Dismiss the overlay and show the keyboard
  overlayWindow = nil;
  UIApplication.shared.windows.last?.isHidden = false
}
  • The overlay window should disappear, and your original window should appear with the keyboard in the same position as before.
Rob Bajorek
  • 6,382
  • 7
  • 44
  • 51
  • 2
    This is an excellent answer. Does precisely what I was looking for! – DanM Aug 12 '14 at 14:43
  • @DanM you thinks its an excellent answer (so do I) - so why not accept it as the correct answer? Is this my issue? I just don't see the checkmark? – David H Jun 16 '16 at 15:38
  • Oops! I apparently somehow awarded the bounty without marking as correct... Thanks for pointing that out! – DanM Jun 16 '16 at 17:32
  • does this work on iOS 11? I'm seeing it as not working – tdios Jan 12 '18 at 13:21
  • I've updated the answer and example project. Let me know if you find any problems! – Rob Bajorek Dec 22 '18 at 21:02
  • Awesome method Rob. Do you - or anyone else - know how to make this work when it's called from a modal? So basically my UITextField, whose keyboard I want to keep open, is itself already on a modal (modalPresentationStyle = .overFullScreen). I have tried all sorts of combinations but couldn't make it work. I can make the keyboardWindow disappear but the overlayWindow won't appear no matter what. – Jan Jan 08 '21 at 13:58
3

I can't try this right now, but have implemented similar for other purposes. In the action for presenting the modal controller, I assume gesture recognizer or delegate method, first take a screenshot and place it in an imageView over the current subviews. Later when returning, simply remove the imageView.

Might sound crazy but I remember having done this for a transition where the keyboard moving during the transition caused similar clunky behavior. It was not difficult to implement at all.

If you have trouble trying it, perhaps someone will provide some code. I can reference my own work later and add an example, but not now.

Dean Davids
  • 4,174
  • 2
  • 30
  • 44
3

@Rob Bajorek's answer is excellent. For iOS 9,10 there are small changes.
Instead of the code:

[self.overlayWindow setWindowLevel:UIWindowLevelAlert];
[self.overlayWindow makeKeyAndVisible];

Put the following code:

NSArray *windows = [[UIApplication sharedApplication] windows];
UIWindow *lastWindow = (UIWindow *)[windows lastObject];
[self.overlayWindow setWindowLevel:lastWindow.windowLevel + 1];
[self.overlayWindow setHidden:NO];
Harman
  • 426
  • 8
  • 14
1

In order to keyboard to visible any of text accepting fields such UITextField or UITextView or UISearchBar should be the first responder and they should be visible in the view. Meaning responding view should be in the top level hierarchy in the window.

If you don't need this effect, Instead of presenting a ViewController you can add ViewController.view as a subview of your self.view with animation.

Rajesh
  • 10,318
  • 16
  • 44
  • 64
1

You have access to the frame of the keyboard in iOS.

You need to implement code to listen to the keyboard notifications (like UIKeyboardWillShowNotification and UIKeyboardWillChangeFrameNotification). The notification will send you informations about the frame of the keyboard.

Giva a look to the description of the "Keyboard Notification User Info Keys" in the windows reference.

You'll find useful for you purpose:

UIKeyboardBoundsUserInfoKey The key for an NSValue object containing a CGRect that identifies the bounds rectangle of the keyboard in window coordinates. This value is sufficient for obtaining the size of the keyboard. If you want to get the origin of the keyboard on the screen (before or after animation) use the values obtained from the user info dictionary through the UIKeyboardCenterBeginUserInfoKey or UIKeyboardCenterEndUserInfoKey constants.

With the information of the keyboard frame you can show there you modal view.

Luca Bartoletti
  • 2,435
  • 1
  • 24
  • 46
  • Don't quite understand how that helps me cover the keyboard w/ the modal? – DanM Aug 11 '14 at 11:08
  • If you have the frame of the keyboard in the window coordinate you can place there the view you need to show as modal – Luca Bartoletti Aug 11 '14 at 11:11
  • Ah, so you're saying add the modal's view directly to the window? – DanM Aug 11 '14 at 11:11
  • If you need you can use the "convertRect:" methods of the UIView/UIWindow classes to translate the coordinate in the view/windows space of your preference – Luca Bartoletti Aug 11 '14 at 11:12
  • No, i'm saying that you have the frame in windows coordinate as i a wrote above you can translate it in what are the coordinate of the view of your choice – Luca Bartoletti Aug 11 '14 at 11:13
  • Oh... OK, I think I wasn't clear enough in my question. I want the modal to appear so it is *covering* the keyboard, I.e. The keyboard is not visible through the modal, but as the modal is dismissed it reveals the keyboard already in place. – DanM Aug 11 '14 at 11:14
  • If you want an advice on that, you can write a custom controller that is able to provide that type of modal view. IF you attach views to window you don't get the rotations for free. – Luca Bartoletti Aug 11 '14 at 11:15
  • You can still implement notifications to implement rotations in a view attached to the window, but it requires more work. – Luca Bartoletti Aug 11 '14 at 11:21
1

Just add an tap gesture in your textfield and a UITextfield *flagTextfield;

UITapGestureRecognizer* doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(DoubleTapMethod:)];
doubleTap.numberOfTapsRequired = 2;
[self.txtTest addGestureRecognizer:doubleTap];


-(void)DoubleTapMethod:(UITapGestureRecognizer *)gesture
{

     [flagTextfield resignFirstResponder];
     NSLog(@"DoubleTap detected");
     //Set your logic on double tap of Textfield...
     //presents a modal view controller
}

- (void)textFieldDidBeginEditing:(UITextField *)textField{

        flagTextfield = textfield;
}
BHASKAR
  • 1,201
  • 1
  • 9
  • 20