How to move bottom view to top of UIKeyboard

前端 未结 1 729
长发绾君心
长发绾君心 2020-12-22 11:54

Hi I am trying to make a view\'s bottom align with the top of UIKeyboard.

Update 1: I have created a github project if you would like to give it a try: https://gith

相关标签:
1条回答
  • 2020-12-22 12:50

    You can achieve it using following Autolayout solution.

    First you need UILayoutGuide that will be used simulate Keyboard aware bottom anchor, and a NSLayoutConstraint that will control this layout guide:

    fileprivate let keyboardAwareBottomLayoutGuide: UILayoutGuide = UILayoutGuide()
    fileprivate var keyboardTopAnchorConstraint: NSLayoutConstraint!
    

    In the viewDidLoad add the keyboardAwareBottomLayoutGuide to the view and setup the appropriate contraints:

    self.view.addLayoutGuide(self.keyboardAwareBottomLayoutGuide)
    // this will control keyboardAwareBottomLayoutGuide.topAnchor to be so far from bottom of the bottom as is the height of the presented keyboard
    self.keyboardTopAnchorConstraint = self.view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: keyboardAwareBottomLayoutGuide.topAnchor, constant: 0)
    self.keyboardTopAnchorConstraint.isActive = true
    self.keyboardAwareBottomLayoutGuide.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor).isActive = true
    

    Then use following lines to start listening to keyboard showing and hiding:

    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowNotification(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideNotification(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    

    And finally, use following methods to control the keyboardAwareBottomLayoutGuide to mimic the keyboard:

    @objc fileprivate func keyboardWillShowNotification(notification: NSNotification) {
        updateKeyboardAwareBottomLayoutGuide(with: notification, hiding: false)
    }
    
    @objc fileprivate func keyboardWillHideNotification(notification: NSNotification) {
        updateKeyboardAwareBottomLayoutGuide(with: notification, hiding: true)
    }
    
    fileprivate func updateKeyboardAwareBottomLayoutGuide(with notification: NSNotification, hiding: Bool) {
        let userInfo = notification.userInfo
    
        let animationDuration = (userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue
        let keyboardEndFrame = (userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
    
        let rawAnimationCurve = (userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uint32Value
    
        guard let animDuration = animationDuration,
            let keybrdEndFrame = keyboardEndFrame,
            let rawAnimCurve = rawAnimationCurve else {
                return
        }
    
        let convertedKeyboardEndFrame = view.convert(keybrdEndFrame, from: view.window)
    
        let rawAnimCurveAdjusted = UInt(rawAnimCurve << 16)
        let animationCurve = UIViewAnimationOptions(rawValue: rawAnimCurveAdjusted)
    
        // this will move the topAnchor of the keyboardAwareBottomLayoutGuide to height of the keyboard
        self.keyboardTopAnchorConstraint.constant = hiding ? 0 : convertedKeyboardEndFrame.size.height
    
        self.view.setNeedsLayout()
    
        UIView.animate(withDuration: animDuration, delay: 0.0, options: [.beginFromCurrentState, animationCurve], animations: {
            self.view.layoutIfNeeded()
        }, completion: { success in
            //
        })
    }
    

    Now with all this set up, you can use Autolayout to constraint your views to keyboardAwareBottomLayoutGuide.topAnchor instead of self.view.layoutMarginsGuide.bottomAnchor (or self.view.bottomAnchor, whichever you use). keyboardAwareBottomLayoutGuide will automatically adjust to the keyboard showed or hidden.

    Example:

    uiTextField.bottomAnchor.constraint(equalTo: keyboardAwareBottomLayoutGuide.topAnchor).isActive = true
    

    EDIT: Directly setting frames

    While I strongly recommend using Autolayout, in cases when you cannot go with this, directly setting frames can be also a solution. You can use the same principle. In this approach you don't need layout guide, so you don't need any additional instance properties. Just use viewDidLoad to register for listening notifications:

    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowNotification(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideNotification(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    

    Then implement methods that will react to these notifications:

    @objc fileprivate func keyboardWillShowNotification(notification: NSNotification) {
        adjustToKeyboard(with: notification, hiding: false)
    }
    
    @objc fileprivate func keyboardWillHideNotification(notification: NSNotification) {
        adjustToKeyboard(with: notification, hiding: true)
    }
    
    fileprivate func adjustToKeyboard(with notification: NSNotification, hiding: Bool) {
        let userInfo = notification.userInfo
    
        let animationDuration = (userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue
        let keyboardEndFrame = (userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
    
        let rawAnimationCurve = (userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uint32Value
    
        guard let animDuration = animationDuration,
            let keybrdEndFrame = keyboardEndFrame,
            let rawAnimCurve = rawAnimationCurve else {
                return
        }
    
        let convertedKeyboardEndFrame = view.convert(keybrdEndFrame, from: view.window)
    
        let rawAnimCurveAdjusted = UInt(rawAnimCurve << 16)
        let animationCurve = UIViewAnimationOptions(rawValue: rawAnimCurveAdjusted)
    
        // we will go either up or down depending on whether the keyboard is being hidden or shown
        let diffInHeight = hiding ? convertedKeyboardEndFrame.size.height : -convertedKeyboardEndFrame.size.height
    
        UIView.animate(withDuration: animDuration, delay: 0.0, options: [.beginFromCurrentState, animationCurve], animations: {
            // this will move the frame of the aView according to the diffInHeight calculated above
            // of course here you need to set all the frames that would be affected by the keyboard (this is why I prefer using autolayout)
            self.aView?.frame = (self.aView?.frame.offsetBy(dx: 0, dy: diff))!
    
            // of course, you can do anything more complex than just moving the aView up..
        })
    }
    

    In both cases, don't forget to unregister observing the notifications once the viewController is deinitialized to prevent retain cycles:

    deinit {
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }
    
    0 讨论(0)
提交回复
热议问题