键盘弹出通知UIKeyboardDidShowNotification被多次调用,有时候弹出的键盘尺寸也不正确

18

我正在尝试在键盘出现/更改时将UITextView移动到键盘上方。假设我现在使用英文键盘,然后直接切换到中文键盘(比标准的英文键盘高)。在这种情况下,我的文本视图总是显示得太高了。我在应用程序接收到UIKeyboardDidShowNotification时调整文本视图的位置(使用UIKeyboardFrameEndUserInfoKey来获取高度)。但是经过一些研究,我发现UIKeyboardDidShowNotification会被多次调用,并且往往伴随着不正确的键盘尺寸(我已经使用NSLog记录了userInfo字典)。我在ViewWillAppear中注册键盘通知,在ViewWillDisappear中取消注册。我无法确定是什么原因导致该通知多次触发;我理解这个通知应该只在键盘最终显示后触发一次。另外需要说明的是:我已经在响应UIKeyboardDidShowNotification的方法中记录了“self”,它实际上总是相同的视图控制器对象。

即使该通知多次触发,我仍然不明白为什么有些通知的键盘高度会有所不同。其中一个通知始终具有正确的高度,但当它不是最后一个触发的通知时,文本视图就会出错。如果您能提供任何进一步排除故障的提示,我将不胜感激!

编辑:经过更多测试,似乎这是特定于中文键盘的问题。每当我从英语切换到中文时,我会收到三个UIKeyboardWillShowNotifications:

2014-12-24 22:49:29.385 Example[1055:421943] info dictionary: {
UIKeyboardAnimationCurveUserInfoKey = 0;
UIKeyboardAnimationDurationUserInfoKey = 0;
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 252}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 460}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 442}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 352}, {320, 216}}";
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 316}, {320, 252}}";
}
2014-12-24 22:49:29.408 Example[1055:421943] info dictionary: {
UIKeyboardAnimationCurveUserInfoKey = 0;
UIKeyboardAnimationDurationUserInfoKey = 0;
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 442}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 460}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 316}, {320, 252}}";
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 352}, {320, 216}}";
}
2014-12-24 22:49:29.420 Example[1055:421943] info dictionary: {
UIKeyboardAnimationCurveUserInfoKey = 0;
UIKeyboardAnimationDurationUserInfoKey = 0;
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 288}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 442}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 424}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 316}, {320, 252}}";
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 280}, {320, 288}}";
}

第一个高度正确为252,但接下来的两个高度为216和288是错误的。这种情况经常发生。

以下是几个片段,演示我如何管理通知订阅:

-(void)viewWillAppear:(BOOL)animated {

       [super viewWillAppear:animated];


       [self registerForKeyboardNotifications];


}

-(void)viewWillDisappear:(BOOL)animated {
       [super viewWillDisappear:animated];

       [[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:UIKeyboardWillHideNotification
                                                object:nil];
       [[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:UIKeyboardDidShowNotification
                                                object:nil];

}

- (void)registerForKeyboardNotifications {



      [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardDidHide:)
                                             name:UIKeyboardWillHideNotification object:nil];

      [[NSNotificationCenter defaultCenter] addObserver:self      
                                         selector:@selector(keyboardDidShow:)                                       
                                             name:UIKeyboardDidShowNotification object:nil];
}

2
我已经找到了问题并且有一个解决方法。在我的方法中,响应UIKeyboardWillShowNotification时,我在动画块内调用[self.view layoutSubviews]。当我动画layoutSubviews时,会触发额外的UIKeyboardWillShowNotifications,并且发布在我的问题中的奇怪值。如果我在动画块之外调用[self.view layoutSubviews],通知只会被触发一次,而且值始终正确。所以,没有动画时这个方法可以正常工作,但我仍然不明白为什么它不能正确地使用动画。 - Benny B
我放弃尝试使用动画使其工作。但是,它在没有动画的情况下可以始终正常工作,因此我对解决方案相对满意。 - Benny B
你能发布 keyboardDidHide()keyboardDidShow() 的代码吗? - Anirudha Mahale
5个回答

8

如果你在模拟器上使用Cmd+K快捷键来显示/隐藏键盘,由于没有将textfield放弃第一响应者的身份,它可能会被多次调用。

如果你改用键盘的Return按钮,那么它将做正确的操作并放弃第一响应者的身份。


5

主要原因是您的通知被多次调用。例如,在Swift中,如果您在viewWillAppear方法中添加了NSNotificationCenter.defaultCenter().addObserver(xx"keyboardWillShow"xx),并且如果您来回切换视图,则会导致同一“keyboardWillShow”的多个观察者存在。相反,您应该考虑将addObserver调用移动到“viewDidLoad()”方法中。或者,您可以在视图出现/消失时注册/注销观察者。


7
这是一个很好的答案,但它与Swift无关。 - Kyle Clegg

0
- (void)keyboardDidAppear:(NSNotification *)note {
    CGSize keyboardSize = [note.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height, 0.0);
    textView.contentInset = contentInsets;
    textView.scrollIndicatorInsets = contentInsets;
}

- (void)keyboardWillBeHidden:(NSNotification *)note {
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    textView.contentInset = contentInsets;
    textView.scrollIndicatorInsets = contentInsets;
}

解决方案来自于苹果官方的这里,就像曹所说的那样。


0
在我的情况下,我在ViewWillAppear中注册键盘通知并在ViewWillDisappear中注销。但是,这会导致UIKeyboardDidShowNotification处理程序被多次触发。似乎注销方法不起作用,因此每次视图出现时,UIKeyboardDidShowNotification的观察者计数加1。然后,您在UITextField内部触摸时,多个观察者被通知,处理程序一遍又一遍地触发。
因此,您必须在ViewDidLoad中注册键盘通知,并且不要注销。就像苹果在page中提到的那样,
“//在您的视图控制器设置代码中的某个位置调用此方法。”
我认为“设置”意味着ViewDidLoad。在处理键盘通知的代码列表中,没有ViewWillDisappear。
这是我的键盘通知处理程序,它可以正常工作。
   func keyboardWillShow(notification: NSNotification) {
    let userInfo = notification.userInfo!
    let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue().height
    debugPrint("Before",NotifyView.frame)
    NotifyView.frame.offsetInPlace(dx: 0, dy: -keyboardHeight)
    debugPrint("After",NotifyView.frame)
}
func keyboardWillHide(notification: NSNotification) {//Do Nothing
}

0

我注意到当你长按UITextField以使放大镜出现并释放时,有时会调用两次keyboardWillShowNotification。在这种情况下,第一个keyboardWillShowNotification显示的键盘高度是不正确的,而第二个通知中的高度是正确的。

为了避免基于键盘高度进行UI动画的问题,我使用了约束,因此即使多次调用动画块,它仍将产生有效的动画。我们只需要在键盘将显示和将隐藏处理程序方法中调用以下方法,即可获得正确的动画:

func animateWithKeyboard(for notification: NSNotification, animation: ((CGRect) -> Void)?) {
    guard let frame = notification.keyboardEndFrame,
          let duration = notification.keyboardAnimationDuration,
          let curve = notification.keyboardAnimationCurve else {
      return
    }
    UIViewPropertyAnimator(duration: duration, curve: curve) {
      animation?(frame)
      self.view?.layoutIfNeeded()
    }.startAnimation()
  }

这里还有上面代码中使用的NSNotification的扩展:

extension NSNotification {
  var keyboardAnimationDuration: Double? {
    userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double
  }
  
  var keyboardAnimationCurve: UIView.AnimationCurve? {
    guard let curveValue = userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int else { return nil }
    return UIView.AnimationCurve(rawValue: curveValue)
  }

  var keyboardEndFrame: CGRect? {
    (userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接