【问题标题】:keyboardWillShow gets called for other app's keyboards其他应用程序的键盘调用keyboardWillShow
【发布时间】:2015-12-22 06:16:00
【问题描述】:

我知道这是应该发生的,但它给我带来了我不知道如何解决的问题。

我想在键盘显示时向上移动视图,以便我的文本字段保持可见。

我的文本字段有数字键盘。

我使用通知和keyboardWillShow/Hide在选择文本字段时向上/向下移动我的视图。

现在假设我点击一个文本字段,然后切换到另一个使用不同键盘(不是数字键盘)的应用程序。 keyboardWillShow 使用错误键盘的大小(来自另一个应用程序的键盘)调用,并且我的视图移动了错误的数量(它甚至根本不应该移动)。因此,当我回到我的应用程序时,我的视图位于错误的位置,甚至没有显示键盘,然后调用 keyboardWillHide 并将视图移回原位(不知从何而来)。但是一开始就不应该为其他应用调用keyboardWillShow

我正在删除viewWillDisappear 上的通知,但这种情况仍然会发生……也许在为我调用viewWillDisappear 之前,其他应用程序调用了keyboardWillShow

这是我的代码:

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

编辑:正如@JasonNam 在他的回答中指出的那样,切换应用程序时不会调用 viewWillDisappear,因此我必须添加一个 applicationWillResignActive 通知来删除键盘通知,并添加一个 applicationDidBecomeActive 通知来添加它们。


编辑 2:@sahara108 的解决方案看起来更干净,我看不出有任何缺点。在keyboardWillShow中做任何事情之前,我只需要检查UIApplication.sharedApplication().applicationState == .Active

【问题讨论】:

  • 代码中的一个大问题。您将self 添加为通知观察者,但您尝试删除self.view。您需要删除self
  • 糟糕,我最初是删除self,但我注意到了这个问题并开始尝试各种疯狂的事情,包括从self.view而不是self中删除观察者,然后我忘记了当我在这里发布代码时,将那部分改回来。不过,问题仍然存在。环顾四周,我发现这显然应该发生......但我不希望它发生,我不知道该怎么做。

标签: ios swift nsnotificationcenter


【解决方案1】:

我建议您检查您的textField 是否是keyboardWillShown 方法中的第一响应者。如果不是,请忽略通知。

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

更新: 与其检查firstResponder,不如检查UIApplication.shareApplication().applicationSate == .Active更安全

【讨论】:

  • 它不起作用。检查我对艾哈迈德回答的评论。 ://
  • 哦,我很惊讶它不起作用。所以请检查应用程序状态。请参阅我的更新答案。
【解决方案2】:

仅限 iOS 9+:

来自键盘的 NSNotification 包含以下内容:

UIKeyboardIsLocalUserInfoKey - NSNumber 对象的键,其中包含一个标识键盘是否属于当前应用的布尔值。

在我的情况下,我也这样做(这可能也是 OP 所需要的):

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

这样在应用程序之间切换时键盘不会隐藏。

【讨论】:

    【解决方案3】:

    只需检查应用状态是否处于活动状态即可:

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

    【讨论】:

      【解决方案4】:

      您的想法几乎是对的:您必须删除 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)
      }
      

      【讨论】:

      • 这不是真的。 notificationCenter.removeObserver(self) 将从所有已注册的通知中删除 self。 OP 代码的问题在于他们试图删除 self.view 而不是 self
      • 是的,可能有效,但不是最佳实践:在 dealloc 方法中这样做是安全的,但不应该使用其他方式——使用 removeObserver:name:object: 代替。 link
      • @jorn 刚试过,没解决我的问题。 ://
      【解决方案5】:

      您可以在UIApplicationDidEnterBackgroundNotification 上取消注册通知 并再次注册UIApplicationDidBecomeActiveNotification。我不能确定这种行为是故意的,但对我来说绝对是出乎意料的。

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

      【讨论】:

      • 嘿,你能再检查一下我的答案吗?实际上切换应用时不会调用 viewDidDisappear :(
      • 这正是我所做的,它没有改变任何事情。我的视图仍然根据其他应用的键盘移动。
      • 你说得对,不过,我尝试在 viewDidDisappear 打印一些东西,但在切换应用程序时它不会被调用。但是,ApplicationDidEnterBackground 被调用太晚了,只有在另一个应用程序调用了 keyboardWillAppear 并且我的视图被移动之后。
      • 啊哈! applicationWillResignActive 及时调用,然后一切正常!
      • 哦,听起来不错!祝你的编码好运:)
      【解决方案6】:

      keyboardWillShow 中执行任何操作之前,您可以确保视图包含第一响应者。使用像 this one 这样的 UIView 扩展(或类别) - 抱歉找不到 swift 等价物,但你明白了 - 你可以检测视图的任何子视图是否是第一响应者。我相信这也应该适用于在 iOS 9 上同时在前台有 2 个应用程序的情况。

      【讨论】:

      • 那将是一个更清洁的解决方案,但我刚刚尝试过,但它不起作用。这很奇怪,但显然 keyboardWillAppear 在所选文本字段退出第一响应者之前被另一个应用程序调用。如果我尝试打印textField.isFirstResponder(),然后是视图的偏移量,然后是textField.isFirstResponder(),我会很快得到true,偏移量和false
      【解决方案7】:

      我无法使用应用程序生命周期方法,但我能够通过检查当前视图中的任何文本字段是否为 firstResponder 来为自己解决此问题。

      已更新代码:

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

      【讨论】:

      • 使用这个答案的方法,没有代码,没有代码返工,它可能最好作为评论而不是答案。
      • 更新了!感谢您的反馈
      猜你喜欢
      • 2015-07-20
      • 2012-03-13
      • 2020-06-11
      • 1970-01-01
      • 2018-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-01
      相关资源
      最近更新 更多