如何同时获取键盘的大小和选中的 UITextField 的 CGFrame?

How do I get the size of the keyboard and the CGFrame of the selected UITextField at the same time?

我在运行时动态地使用许多 UITextField 字段填充垂直 UIScrollView。我遇到的问题是键盘会隐藏它会出现的区域中的字段,这可能包括我正在编辑的字段。

我尝试了 Apple 文档中的 KeyboardManagement 解决方案,还尝试了 textFieldDidBeginEditingtextFieldDidEndEditing 上的通知,但这两种情况下的问题是 keyboardWillShow 通知有时排在第一位,在那种情况下,它不会让我知道正在编辑的是哪个字段。

我在实现 UITextFieldDelegate 协议的 class 中有这段代码,这个 class 的每个对象都持有对这些字段之一的引用,并且作为委托

func textFieldDidBeginEditing(_ textField: UITextField) {
    self.activeTextfield = self.valueTextField
}

func textFieldDidEndEditing(_ textField: UITextField) {
    self.activeTextfield = nil
}

activeTextfield 变量是对 UIViewController 中变量的弱引用,所有这些都发生在该变量中。在那个视图控制器中,我有以下代码

class MyClass: UIViewController {
    var activeTextfield: CustomTextField! // This is the variable I was talking about on the previous paragraph

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        NotificationCenter.default.removeObserver(self)
    }

    @objc func keyboardWillShow(_ notification: Notification) {
        if self.view.frame.origin.y == 0 {
            guard let userInfo = notification.userInfo else { return }
            guard let keyboardSize = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }

            let keyboardFrame = keyboardSize.cgRectValue
            let textFieldFrame = activeTextfield!.frame // activeTextfield sometimes is nil because this notification happens before the previous code block

            if textFieldFrame.origin.y + textFieldFrame.size.height > keyboardFrame.origin.y {
                self.view.frame.origin.y -= keyboardFrame.height
            }
        }
    }

    @objc func keyboardWillHide(_ notification: Notification) {
        if self.view.frame.origin.y != 0 {
            guard let userInfo = notification.userInfo else { return }
            guard let keyboardSize = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }

            let keyboardFrame = keyboardSize.cgRectValue

            self.view.frame.origin.y += keyboardFrame.height
        }
    }
}

有什么方法可以强制在键盘通知之前调用 UITextField 委托方法?

这是处理这种情况的正确方法吗?

如果没有,我该如何处理?

谢谢

您需要为文本字段定义标签 属性 并签入 textFieldDidBeginEditingtextFieldDidEndEditing UITextField 的名称。

如您的问题所述:

the problem in both cases is that the keyboardWillShow notification comes first sometimes, and in that case it doesn't let me know which field is the one being edited

根据 apple 文档中描述的 sequence of eventstextFieldShouldBeginEditing 是调用的第一个委托方法。

所以,你可以

  1. 在委托中实施 textFieldShouldBeginEditing 以设置您的活动文本字段,而不是 textFieldDidBeginEditing(确保您从 textFieldShouldBeginEditing return true 允许编辑)
  2. 使用 keyboardDidShowNotification 而不是 keyboardWillShowNotification

这将确保您在获取键盘框架/详细信息之前标记 UITextField

您只需执行以下操作即可相当简单地完成此操作。首先在你的视图中添加通知观察者会出现。

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)        
    // Keyboard notification
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}

然后在你的选择器函数中你可以有这样的东西

@objc func keyboardWillShow(notification: NSNotification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
            let currentTextField = view.getSelectedTextField() {

        let keyboardHeight = keyboardSize.height
        let textFieldFrame = currentTextField.superview?.convert(currentTextField.frame, to: nil)            
        }
    }
}

你的 getSelectedTextField() 扩展看起来像这样

// Inside UIView Extension 
// Get currently active textfield
func getSelectedTextField() -> UITextField? {

    let totalTextFields = getTextFieldsInView(view: self)

    for textField in totalTextFields{
        if textField.isFirstResponder{
            return textField
        }
    }
    return nil
}

    func getTextFieldsInView(view: UIView) -> [UITextField] {

        var totalTextFields = [UITextField]()

        for subview in view.subviews as [UIView] {
            if let textField = subview as? UITextField {
                totalTextFields += [textField]
            } else {
                totalTextFields += getTextFieldsInView(view: subview)
            }
        }
        return totalTextFields
    }
}