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

时间:2017-08-16 08:08:24

标签: ios swift events keyboard keypad

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

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

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

enter image description here

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

enter image description here

4 个答案:

答案 0 :(得分:1)

从iOS 10开始,当NSNotificationUIScrollView以交互方式拖动键盘时,Apple不提供UIKeyboardWillChangeFrame观察员来检测帧更改只有一次释放水龙头才会发现UIKeyboardDidChangeFrame

无论如何,在查看DAKeyboardControl库后,我有意在UIScrollView.UIPanGestureRecognizer附加UIViewController,因此生成的所有手势事件都将在UIViewController中处理同样。拧了好几个小时后,我开始工作,这里是所有必需的代码:

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
    }        
}

答案 1 :(得分:0)

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()
    }

答案 2 :(得分:0)

看起来你有一个底部约束的错误常数。 尝试每次重置底部约束并设置新的高度值

    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)
            }
        }
    }

答案 3 :(得分:0)

您只需添加此广告连结:

   IOTEvent.State    .attrs.EventTime DataEvent.Model DataEvent.DataType DataEvent.DataValue
1:        PowerOn 2017-02-27T18:33:58              NA                 NA                  NA
2:             NA 2017-02-28T08:59:03               1                  1                0301
3:             NA 2017-02-28T08:59:13               1                  1                0401

然后在 AppDelegate.swift 中添加:

pod 'IQKeyboardManagerSwift'

import IQKeyboardManagerSwift 函数中的a

didFinishLaunchingWithOptions

这很简单。