Properly detect appearance and disappearance of Keyboard
我有一个包含三个文本字段的注册屏幕:一个用于用户名,另外两个用于密码(安全文本输入)。我已设置侦听器以确定键盘何时出现并消失以相应地移动我的视图,以便键盘不会阻止输入窗口小部件。
在我的viewDidLoad()中:
1 2 3 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardDidHide, object: nil) |
选择器功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @objc func keyboardWillShow(notification: NSNotification) { let userInfo = notification.userInfo var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue keyboardFrame = self.view.convert(keyboardFrame, from: nil) movement = (keyboardFrame.size.height/2 + 20) print("Keyboard Appeared with a height of \(movement) including 20 offset") UIView.animate(withDuration: 0.3, animations: { self.view.frame = self.view.frame.offsetBy(dx: 0, dy: self.movement * -1.0) //dy is negative for upward movement }) } @objc func keyboardWillHide(notification: NSNotification) print("Keyboard Disappeared with a height of \(movement) offset included") UIView.animate(withDuration: 0.3, animations: { self.view.frame = self.view.frame.offsetBy(dx: 0, dy: self.movement) }) } |
并且,将textfield委托设置为视图控制器,我有:
1 2 3 4 5 6 7 8 9 10 11 12 13 | extension SignUpViewController: UITextFieldDelegate { func textFieldShouldReturn(_ textField: UITextField) -> Bool { if textField == textFieldUsername { textFieldPassword.becomeFirstResponder() } else if textField == textFieldPassword { textFieldPasswordConfirm.becomeFirstResponder() } else { textFieldPasswordConfirm.resignFirstResponder() } return true } } |
最后,我还有一个tap taplistener,用于确定用户点击屏幕上的任何位置以消除键盘。为此我用过:
1 | view.endEditing(true) |
当我点击文本字段时,会调用keyboardWillShow(),当我点击屏幕外的任何地方时,会调用keyboardWillHide()。这是预期的。
但是,当我使用返回键从一个文本字段切换到另一个文本字段时,虽然键盘没有从屏幕上消失,但仍会调用keyboardWillShow()。
此外,当我在文本字段中输入一些文本时,随机调用keyboardWillShow()。
如何阻止这些不受欢迎的键盘通知?
编辑
在阅读了@rmaddy和@Adam Eberbach的答案之后,我想出了不同的方法。首先,我为可滚动视图定义一个协议,并在我想要具有滚动效果的视图控制器中实现它:
1 2 3 4 5 | protocol ScrollableProtocol { var viewScrolled: Bool { get} func checkScroll() func performScroll(direction: Int) } |
然后在视图控制器中:
1 2 3 | private var activeTextField: UITextField? var isScrolled: Bool = false let offset: CGFloat = 155.00 //for now I'm defining this value as a constant which is supposed to be less than the height of the keyboard |
协议的实施:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | extension SignUpViewController: ScrollableProtocol { var viewScrolled: Bool { get { return isScrolled } } func checkScroll() { if !viewScrolled { performScroll(direction: 0) isScrolled = !isScrolled } } func registerTapListener() { let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(clearKeyboard)) view.addGestureRecognizer(tap) } @objc func clearKeyboard() { activeTextField?.resignFirstResponder() if viewScrolled { performScroll(direction: 1) isScrolled = !isScrolled } } func performScroll(direction: Int) { //0 -> scroll up, 1 -> scroll down if direction == 0 { UIView.animate(withDuration: 0.3, animations: { self.view.frame = self.view.frame.offsetBy(dx: 0, dy: self.offset * -1) }) } else if direction == 1 { UIView.animate(withDuration: 0.3, animations: { self.view.frame = self.view.frame.offsetBy(dx: 0, dy: self.offset) }) } } } |
UITextField委托方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | extension SignUpViewController: UITextFieldDelegate { func textFieldDidBeginEditing(_ textField: UITextField) { activeTextField = textField checkScroll() } func textFieldShouldReturn(_ textField: UITextField) -> Bool { if textField == textFieldUsername { textFieldPassword.becomeFirstResponder() } else if textField == textFieldPassword { textFieldPasswordConfirm.becomeFirstResponder() } else { textFieldPasswordConfirm.resignFirstResponder() performScroll(direction: 1) isScrolled = !isScrolled } return true } } |
最后,viewDidLoad方法:
1 2 3 4 5 6 7 | override func viewDidLoad() { super.viewDidLoad() registerTapListener() textFieldUsername.delegate = self textFieldPassword.delegate = self textFieldPasswordConfirm.delegate = self } |
这种方式是对的还是一种复杂的方法?
我不相信你能;每当iOS感觉要发送给您时,您都会收到通知。每次收到视图通知时,都应使用绝对计算,而不是将键盘高度添加或减去视图偏移。
通常的过程是找出通知指示可见区域底部的位置,然后找出编辑后的控件必须与之相关的位置,并相应地设置一些约束到动画视图位置,通常具有相同的持续时间在显示/隐藏通知中收到。由于您进行的计算(包括控制底边和可见区域底部之间的任何边距)每次对于这些重复通知都会产生相同的数字,因此您不会移动任何内容。因此,您的视图始终处于可见位置,并且用户看不到四处移动,因为您应用的约束常量值大部分时间等于现有约束常量值 - 因此不会发生移动。
当收到键盘显示通知并且之前未显示,或者收到隐藏通知且之前可见,或键盘高度不同时,则常量值会发生变化,您可以设置视图的动画以适应新的展示安排。
即使您实际上看不到键盘消失并重新出现,更改第一响应者也会导致键盘事件发生。每个文本字段可以具有不同的
您应该在