I know this is what's supposed to happen, but it's causing me problems that I don't know how to fix.
I want to move my view up when the keyboard shows, so that my text fields remain visible.
My text fields have numeric keypads.
I use notifications and keyboardWillShow/Hide
to move my view up/down when a text field is selected.
Now suppose I tap on a text field and then switch to another app that's using a different keyboard (not the numeric keypad). keyboardWillShow
is called with the size of the wrong keyboard (the one from the other app) and my view is moved the wrong amount (it shouldn't even move at all). So when I go back to my app my view is at the wrong place and the keyboard isn't even showing, and then keyboardWillHide
gets called and the view is moved back into place (out of nowhere). But keyboardWillShow
shouldn't even be called for the other app in the first place.
I'm removing the notifications on viewWillDisappear
, but this still happens… maybe keyboardWillShow
is called for the other apps before viewWillDisappear
is called for mine?
Here's my code:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
for subview in self.view.subviews {
if subview.isKindOfClass(UITextField) {
let textField = subview as! UITextField
textField.addTarget(self, action: "textFieldDidReturn:", forControlEvents: UIControlEvents.EditingDidEndOnExit)
textField.addTarget(self, action: "textFieldDidBeginEditing:", forControlEvents: UIControlEvents.EditingDidBegin)
}
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardWillShow(notification: NSNotification) {
self.keyboardIsShowing = true
if let info = notification.userInfo {
self.keyboardFrame = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
self.arrangeViewOffsetFromKeyboard()
}
}
func keyboardWillHide(notification: NSNotification) {
self.keyboardIsShowing = false
self.returnViewToInitialFrame()
}
func arrangeViewOffsetFromKeyboard() {
if let textField = activeTextField {
let theApp: UIApplication = UIApplication.sharedApplication()
let windowView: UIView? = theApp.delegate!.window!
let textFieldLowerPoint = CGPoint(x: textField.frame.origin.x, y: textField.frame.origin.y + textField.frame.size.height)
let convertedTextFieldLowerPoint = textField.superview!.convertPoint(textFieldLowerPoint, toView: windowView)
let targetTextFieldLowerPoint = CGPoint(x: textField.frame.origin.x, y: self.keyboardFrame.origin.y)
let targetPointOffset = targetTextFieldLowerPoint.y - convertedTextFieldLowerPoint.y
let adjustedViewFrameCenter = CGPoint(x: self.view.center.x, y: self.view.center.y + targetPointOffset)
print(targetPointOffset) // When I change to a different app this prints the wrong value… but none of this should even get called.
if targetPointOffset < 0 {
UIView.animateWithDuration(0.3, animations: {
self.view.center = adjustedViewFrameCenter
})
}
}
}
func returnViewToInitialFrame() {
let initialViewRect = CGRect(x: 0.0, y: 0.0, width: self.view.frame.size.width, height: self.view.frame.size.height)
if !CGRectEqualToRect(initialViewRect, self.view.frame) {
UIView.animateWithDuration(0.2, animations: {
self.view.frame = initialViewRect
})
}
}
Edit: As @JasonNam pointed out in his answer, viewWillDisappear doesn't get called when switching apps, so I had to add an applicationWillResignActive
notification to remove the keyboard notifications and an applicationDidBecomeActive
notification to add them back.
Edit 2: @sahara108's solution seems cleaner and I can't see any drawbacks. I just had to check for UIApplication.sharedApplication().applicationState == .Active
before doing anything in keyboardWillShow.
I suggest you to check if your textField
is first responder in keyboardWillShown
method. If it is not, just ignore the notification.
func keyboardWillShow(notification: NSNotification) {
if !myTextField.isFirstResponder() {
return
}
self.keyboardIsShowing = true
if let info = notification.userInfo {
self.keyboardFrame = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
self.arrangeViewOffsetFromKeyboard()
}
}
UPDATE:
Instead of checking for the firstResponder, it is safer if you check UIApplication.shareApplication().applicationSate == .Active
iOS 9+ only:
NSNotification that comes from keyboard contains following:
UIKeyboardIsLocalUserInfoKey - The key for an NSNumber object containing a Boolean that identifies whether the keyboard belongs to the current app.
In my case i also do this (which is probably needed for OP too):
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
return UIApplication.shared.applicationState == .active
}
This way keyboard won't hide when switching between applications.
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