其他应用程序的键盘调用keyboardWillShow

Posted

技术标签:

【中文标题】其他应用程序的键盘调用keyboardWillShow【英文标题】:keyboardWillShow gets called for other app's keyboards 【发布时间】: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中删除观察者,然后我忘记了当我在这里发布代码时,将那部分改回来。不过,问题仍然存在。环顾四周,我发现这显然应该发生......但我不希望它发生,我不知道该怎么做。 【参考方案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)
        
    

【讨论】:

使用这个答案的方法,没有代码,没有代码返工,它可能最好作为评论而不是答案。 更新了!感谢您的反馈

以上是关于其他应用程序的键盘调用keyboardWillShow的主要内容,如果未能解决你的问题,请参考以下文章

golang调用sdl2,键盘和鼠标事件

想用VC++做个全局键盘改键,键盘钩子代码,详细怎么做。跪求代码。

有些键盘比其他键盘更健谈吗?

.NET 应用程序中未调用低级键盘挂钩

iOS 9.0 - 获取键盘将显示/隐藏来自其他应用程序的通知

山狮中没有调用键盘事件