keyboardWillShow recibe llamadas para keyboards de otras aplicaciones

Sé que esto es lo que se supone que debe suceder, pero me está causando problemas que no sé cómo solucionar.

Quiero mover mi vista cuando aparece el keyboard, para que mis campos de text permanezcan visibles.

Mis campos de text tienen keyboards numéricos.

Utilizo notifications y keyboardWillShow/Hide para mover mi vista hacia arriba / hacia abajo cuando se selecciona un campo de text.

Ahora supongo que toco en un campo de text y luego cambio a otra aplicación que esté usando un keyboard diferente (no el keyboard numérico). keyboardWillShow se invoca con el tamaño del keyboard incorrecto (el de la otra aplicación) y mi vista se mueve con la cantidad incorrecta (ni siquiera debería moverse). Entonces, cuando vuelvo a mi aplicación, mi vista está en el lugar equivocado y el keyboard ni siquiera se muestra, y luego se invoca a keyboardWillHide y la vista vuelve a su lugar (de la nada). Pero keyboardWillShow ni siquiera debería ser llamado para la otra aplicación en primer lugar.

Estoy eliminando las notifications en viewWillDisappear , pero esto sigue sucediendo … tal vez se llame a keyboardWillShow para las otras aplicaciones antes de que se llame a viewWillDisappear ?

Aquí está mi código:

 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.shanetworkingApplication() 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 }) } } 

Editar: Como lo señaló @JasonNam en su respuesta, viewWillDisappear no se invoca al cambiar aplicaciones, por lo que tuve que agregar una notificación de applicationWillResignActive para eliminar las notifications del keyboard y una notificación applicationDidBecomeActive para volver a agregarlas.


Edit 2: la solución de @ sahara108 parece más limpia y no veo ningún inconveniente. Solo tuve que search UIApplication.shanetworkingApplication().applicationState == .Active antes de hacer nada en keyboardWillShow.

Te sugiero que verifiques si tu campo de textField es el primero en responder en el método keyboardWillShown . Si no es así, simplemente ignore la notificación.

 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() } } 

ACTUALIZACIÓN: en lugar de search el primer respondedor, es más seguro si UIApplication.shareApplication().applicationSate == .Active

iOS 9+ solamente:

NSNotificación que viene del keyboard contiene los siguientes:

UIKeyboardIsLocalUserInfoKey : la key para un object NSNumber que contiene un valor boolean que identifica si el keyboard pertenece a la aplicación actual.

En mi caso, también hago esto (que probablemente también sea necesario para OP):

 func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { return UIApplication.shanetworking.applicationState == .active } 

De esta forma, el keyboard no se esconderá al cambiar de una aplicación a otra.

Pensaste casi a la derecha: tienes que eliminar las notifications específicas en la vista WillDisappear:

 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) } 

Puede UIApplicationDidEnterBackgroundNotification el logging de la notificación en UIApplicationDidEnterBackgroundNotification y volver a registrarse en UIApplicationDidBecomeActiveNotification . No puedo asegurar que ese tipo de comportamiento sea intencional, pero definitivamente algo inesperado para mí también.

 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) } 

Puede asegurarse de que la vista contiene una primera respuesta antes de hacer cualquier cosa en keyboardWillShow . Usando una extensión (o categoría) UIView como esta, -sorry no pudo encontrar el equivalente rápido pero se te ocurre la idea-, puedes detectar si alguna de las subvistas de la vista es una primera respuesta. Creo que esto también debería funcionar en situaciones como tener 2 aplicaciones en primer plano al mismo time en iOS 9.

Simplemente comtesting si el estado de la aplicación está activo, estará bien:

 - (void)handleKeyboardWillShowNotification:(NSNotification *)notifaction{ if([UIApplication shanetworkingApplication].applicationState != UIApplicationStateActive){ return; } //your code below... }