如何同时获取键盘的大小和选中的 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 解决方案,还尝试了 textFieldDidBeginEditing
和 textFieldDidEndEditing
上的通知,但这两种情况下的问题是 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
委托方法?
这是处理这种情况的正确方法吗?
如果没有,我该如何处理?
谢谢
您需要为文本字段定义标签 属性 并签入
textFieldDidBeginEditing
和 textFieldDidEndEditing
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 events,textFieldShouldBeginEditing
是调用的第一个委托方法。
所以,你可以
- 在委托中实施
textFieldShouldBeginEditing
以设置您的活动文本字段,而不是 textFieldDidBeginEditing
(确保您从 textFieldShouldBeginEditing return true
允许编辑)
- 使用
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
}
}
我在运行时动态地使用许多 UITextField
字段填充垂直 UIScrollView
。我遇到的问题是键盘会隐藏它会出现的区域中的字段,这可能包括我正在编辑的字段。
我尝试了 Apple 文档中的 KeyboardManagement 解决方案,还尝试了 textFieldDidBeginEditing
和 textFieldDidEndEditing
上的通知,但这两种情况下的问题是 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
委托方法?
这是处理这种情况的正确方法吗?
如果没有,我该如何处理?
谢谢
您需要为文本字段定义标签 属性 并签入
textFieldDidBeginEditing
和 textFieldDidEndEditing
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 events,textFieldShouldBeginEditing
是调用的第一个委托方法。
所以,你可以
- 在委托中实施
textFieldShouldBeginEditing
以设置您的活动文本字段,而不是textFieldDidBeginEditing
(确保您从 textFieldShouldBeginEditingreturn true
允许编辑) - 使用
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
}
}