Properly detect appearance and disappearance of Keyboard

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?


Changing the first responder should result in the keyboard event to happen even if you can't actually see the keyboard disappear and reappear. Each text field can have a different inputView or a different keyboard type. And each text field can have a different inputAccessoryView . So the hide and show event should happen as each different text field is made first responder. This gives you a chance to deal with differently sized keyboards and accessory views.

You should change the animation duration in your keyboardWillShow and keyboardWillHide to match the animation duration of the keyboard. You can obtain this value from the notification's userInfo using UIKeyboardAnimationDurationUserInfoKey and UIKeyboardAnimationCurveUserInfoKey .


I don't believe you can; you get notifications whenever iOS feels like sending them to you. You should use absolute calculations rather than adding or subtracting keyboard height to a view offset each time a view notification is received.

The usual process is to figure out where the notification indicates the bottom of the visible area will be, then figure out where the edited control has to be in relation to that and to set some constraint accordingly to animate view position, usually with the same duration received in the show/hide notification. Because the calculation you make (including any margin between control bottom edge and bottom of visible area) results in the same figure every time for these repeated notifications you don't move anything. Thus your views are always in a visible position and the user sees no moving around because you are applying a constraint constant value that most of the time equals the existing constraint constant value - so no movement occurs.

When the keyboard show notification is received and it was not previously visible, or when the hide notification is received and it was previously visible, or if the keyboard height is different, then the constant value does change and you animate the view to suit the new display arrangement.

链接地址: http://www.djcxy.com/p/66732.html

上一篇: 当使用not2时,struct vs class为STL函数

下一篇: 正确检测键盘的外观和消失