I have a signup screen with three text fields: one for username and other two for passwords (secure text entry). I have set listeners to determine when keyboard appears and disappears to move my view accordingly so that the keyboard does not block the input widgets.
In my viewDidLoad():
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)
The selector functions:
@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)
})
}
And, with the textfield delegates set to the view controller, I have:
extension SignUpViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == textFieldUsername {
textFieldPassword.becomeFirstResponder()
} else if textField == textFieldPassword {
textFieldPasswordConfirm.becomeFirstResponder()
} else {
textFieldPasswordConfirm.resignFirstResponder()
}
return true
}
}
Finally, i also have a tap gesturelistener that determines user taps anywhere on the screen to disappear the keyboard. For this i have used :
view.endEditing(true)
When I tap on a textfield, keyboardWillShow() is called and when I tap anywhere outside the screen, keyboardWillHide() is called. This is as expected.
However, when i use the return key to switch from one textfield to another, the keyboardWillShow() is called although the keyboard has not disappeared from the screen.
Also, as I type in some text into the textfield, keyboardWillShow() is called randomly.
How can I stop these undesired keyboard notifications?
EDIT
After reading answers from @rmaddy and @Adam Eberbach, I have come up with different approach. First I define a protocol for scrollable view and implement it in the view controllers in which I desire to have the scroll effect:
protocol ScrollableProtocol {
var viewScrolled: Bool { get}
func checkScroll()
func performScroll(direction: Int)
}
Then in the view controller:
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
Implementation of protocol:
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)
})
}
}
}
The UITextField delegate methods:
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
}
}
Finally, the viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
registerTapListener()
textFieldUsername.delegate = self
textFieldPassword.delegate = self
textFieldPasswordConfirm.delegate = self
}
Is this way right or is it a complex approach?