Reputation: 1461
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.
Upvotes: 7
Views: 3780
Reputation: 1
I wasn't able to use the Application life Cycle methods but I was able to fix this problem for myself by checking if any of my textfields in the current view are firstResponder.
Updated With Code:
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: UIResponder.keyboardWillShowNotification,object: nil)
@objc func keyboardWillShow(_ notification: Notification) {
if storeNameTextField.isFirstResponder || methodTextField.isFirstResponder || amountTextField.isFirstResponder || dateTextField.isFirstResponder || categoryTextField.isFirstResponder {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
self.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight - 22, right: 0)
}
}
}
Upvotes: 0
Reputation: 31
Just check whether the app state is active will be fine:
- (void)handleKeyboardWillShowNotification:(NSNotification *)notifaction{
if([UIApplication sharedApplication].applicationState != UIApplicationStateActive){
return;
}
//your code below...
}
Upvotes: 2
Reputation: 931
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.
Upvotes: 9
Reputation: 2859
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
Upvotes: 10
Reputation: 504
You can make sure that the view contains a first responder before doing any thing in keyboardWillShow
. Using a UIView extension (or category) like this one -sorry couldn't find swift equivalent but you get the idea-, you can detect if any of the view's subviews is a first responder. I believe this should also work in situations like having 2 apps in the foreground at the same time on iOS 9.
Upvotes: 0
Reputation: 2011
You can deregister the notification on UIApplicationDidEnterBackgroundNotification
and register again in UIApplicationDidBecomeActiveNotification
. I cannot sure that that kind of behaviour is intentional but definitely something unexpected for me too.
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationBecomeActive", name: UIApplicationDidBecomeActiveNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationDidEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
}
func applicationBecomeActive()
{
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
}
func applicationDidEnterBackground()
{
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
Upvotes: 0
Reputation: 2916
You thought almost right right: You have to remove the specific notifications in viewWillDisappear:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
notificationCenter.removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
Upvotes: 0