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.
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.
I have an example project on Github here, but the basic process is below.
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)
}
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?
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 UIGestureRecognizer
s 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
}
}
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
}
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
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With