向下滚动 UIScrollView 并交互式拖动小键盘时检测键盘高度

Detect keyboard height while UIScrollView is scrolled down and the keypad is being interactively dragged

我有一个 UIScrollView 和一个 UITextView,就像在任何消息/聊天应用程序中一样,当 UIScrollView 向下滚动时,键盘也会被交互拖动。

我需要在 UIScrollView 滚动时检测键盘高度,我尝试了 UIKeyboardWillChangeFrame 观察器,但是在释放滚动点击后调用了此事件。

在不知道键盘高度的情况下,我无法更新 UITextView 底部约束,而且我在键盘和底部视图 @screenshot 之间有一个间隙。

同时附上来自 Viber 的屏幕截图,当键盘从滚动条中拖动时,它确实对齐底部栏,也可以在 WhatsApp 中看到。

swift 3.0

你可以试试这个方法,我已经在我的项目中实现了。希望对你有帮助。

@IBOutlet weak var constant_ViewBottom: NSLayoutConstraint! // 0

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector:#selector(self.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector:#selector(self.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }

   override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }

func keyboardWillShow(_ notification: NSNotification){
        if let keyboardRectValue = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size {
            let keyboardHeight = keyboardRectValue.height
            print("keyboardHeight:=\(keyboardHeight)")
            constant_ViewBottom.constant = keyboardHeight
            self.view.layoutIfNeeded()
        }
    }
 func keyboardWillHide(_ notification: NSNotification){
        constant_ViewBottom.constant = 0.0
        self.view.layoutIfNeeded()
    }

你的底部约束常数似乎有误。 尝试每次重置底部约束并设置新的高度值

    func keyboardDidChangeFrame(notification: Notification) {
        if let userInfo = notification.userInfo {
            if let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
                let duration: TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
                let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
                let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions().rawValue
                let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
                if endFrame.origin.y >= UIScreen.main.bounds.size.height {
                    self.inputBarBottomSpacing.constant = 0
                } else {
                   //the most important logic branch, reset current bottom constant constraint value
                    if self.inputBarBottomSpacing.constant != 0 {
                        self.inputBarBottomSpacing.constant = 0
                    }
                    self.inputBarBottomSpacing.constant = -endFrame.size.height
                }
                UIView.animate(withDuration: duration, delay: TimeInterval(0), options: animationCurve, animations: {
                    self.view.layoutIfNeeded()
                }, completion: nil)
            }
        }
    }

您可以简单地添加此广告连播:

pod 'IQKeyboardManagerSwift'

然后在你的 AppDelegate.swift 添加:

import IQKeyboardManagerSwift

didFinishLaunchingWithOptions函数中的a

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.

    IQKeyboardManager.sharedManager().enable = true // ADD THIS !!!

    return true
}

就这么简单。

从 iOS 10 开始,Apple 不提供 NSNotification 观察器来检测帧变化 当键盘被 UIScrollView[= 交互拖动时24=]、UIKeyboardWillChangeFrameUIKeyboardDidChangeFrame 仅在释放水龙头后观察到。

无论如何,在查看 DAKeyboardControl 库后,我想到了在 UIViewController 中附加 UIScrollView.UIPanGestureRecognizer,因此产生的任何手势事件都将在 [=16] 中处理=] 还有。搞了几个小时后,我开始工作了,这里是所有必要的代码:

class ViewController: UIViewController, UIGestureRecognizerDelegate {

    fileprivate let collectionView = UICollectionView(frame: .zero)
    private let bottomView = UIView()
    fileprivate var bottomInset: NSLayoutConstraint!

    // This holds height of keypad
    private var maxKeypadHeight: CGFloat = 0 {
        didSet {
            self.updateCollectionViewInsets(maxKeypadHeight + self.bottomView.frame.height)
            self.bottomInset.constant = -maxKeypadHeight
        }
    }

    private var isListeningKeypadChange = false

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        NotificationCenter.default.addObserver(self, selector: #selector(keypadWillChange(_:)), name: .UIKeyboardWillChangeFrame, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keypadWillShow(_:)), name: .UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keypadWillHide(_:)), name: .UIKeyboardWillHide, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keypadDidHide), name: .UIKeyboardDidHide, object: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        NotificationCenter.default.removeObserver(self)
    }

    func keypadWillShow(_ notification: Notification) {
        guard !self.isListeningKeypadChange, let userInfo = notification.userInfo as? [String : Any],
            let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval,
            let animationCurve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? UInt,
            let value = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
            else {
                return
        }

        self.maxKeypadHeight = value.cgRectValue.height

        let options = UIViewAnimationOptions.beginFromCurrentState.union(UIViewAnimationOptions(rawValue: animationCurve))
        UIView.animate(withDuration: animationDuration, delay: 0, options: options, animations: { [weak self] in
            self?.view.layoutIfNeeded()
            }, completion: { finished in
                guard finished else { return }

                // Some delay of about 500MS, before ready to listen other keypad events
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
                    self?.beginListeningKeypadChange()
                }
        })
    }

    func handlePanGestureRecognizer(_ pan: UIPanGestureRecognizer) {
        guard self.isListeningKeypadChange, let windowHeight = self.view.window?.frame.height else { return }

        let barHeight = self.bottomView.frame.height
        let keypadHeight = abs(self.bottomInset.constant)
        let usedHeight = keypadHeight + barHeight

        let dragY = windowHeight - pan.location(in: self.view.window).y
        let newValue = min(dragY < usedHeight ? max(dragY, 0) : dragY, self.maxKeypadHeight)

        print("Old: \(keypadHeight)        New: \(newValue)        Drag: \(dragY)        Used: \(usedHeight)")
        guard keypadHeight != newValue else { return }
        self.updateCollectionViewInsets(newValue + barHeight)
        self.bottomInset.constant = -newValue
    }

    func keypadWillChange(_ notification: Notification) {
        if self.isListeningKeypadChange, let value = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
            self.maxKeypadHeight = value.cgRectValue.height
        }
    }

    func keypadWillHide(_ notification: Notification) {
        guard let userInfo = notification.userInfo as? [String : Any] else { return }

        self.maxKeypadHeight = 0

        var options = UIViewAnimationOptions.beginFromCurrentState
        if let animationCurve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? UInt {
            options = options.union(UIViewAnimationOptions(rawValue: animationCurve))
        }

        let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval
        UIView.animate(withDuration: duration ?? 0, delay: 0, options: options, animations: {
            self.view.layoutIfNeeded()
        }, completion: nil)
    }

    func keypadDidHide() {
        self.collectionView.panGestureRecognizer.removeTarget(self, action: nil)
        self.isListeningKeypadChange = false
        if (self.maxKeypadHeight != 0 || self.bottomInset.constant != 0) {
            self.maxKeypadHeight = 0
        }
    }

    private func beginListeningKeypadChange() {
        self.isListeningKeypadChange = true
        self.collectionView.panGestureRecognizer.addTarget(self, action: #selector(self.handlePanGestureRecognizer(_:)))
    }

    fileprivate func updateCollectionViewInsets(_ value: CGFloat) {
        let insets = UIEdgeInsets(top: 0, left: 0, bottom: value + 8, right: 0)
        self.collectionView.contentInset = insets
        self.collectionView.scrollIndicatorInsets = insets
    }        
}