20

iOS 11 中引入的新安全区域布局指南非常适合防止内容显示在栏下方,但它不包括键盘。这意味着当显示键盘时,内容仍然隐藏在它后面,这就是我要解决的问题。

我的方法是基于收听键盘通知,然后通过additionalSafeAreaInsets.

这是我的代码:

override func viewDidLoad() {
    let notificationCenter = NotificationCenter.default
    notificationCenter.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    notificationCenter.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    notificationCenter.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}

//MARK: - Keyboard
extension MyViewController {
    @objc func keyboardWillShow(notification: NSNotification) {
        let userInfo = notification.userInfo!
        let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height

        additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
        UIView.animate(withDuration: 0.3) {
            self.view.layoutIfNeeded();
        }
    }

    @objc func keyboardWillHide(notification: NSNotification) {
        additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        UIView.animate(withDuration: 0.3) {
            self.view.layoutIfNeeded();
        }
    }

    @objc func keyboardWillChange(notification: NSNotification) {
        let userInfo = notification.userInfo!
        let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height

        additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)

        UIView.animate(withDuration: 0.3) {
            self.view.layoutIfNeeded();
        }
    }
}

这很有效,因为MyControlleris aUIViewController延伸UITableView到整个安全区域。现在当键盘出现时,底部被向上推,这样键盘后面就没有单元格了。

问题在于底栏。我在底部还有一个工具栏,它已经包含在安全区域中。因此,将全键盘高度设置为额外的安全区域插入会将表格视图的底部向上推太多,正好是底部栏的高度。为了使这种方法正常工作,我必须将 设置additionalSafeAreaInsets.bottom为等于键盘高度减去底部栏的高度。

问题1:获得底部当前安全区域间隙的最佳方法是什么?手动获取工具栏的框架并使用它的高度?或者是否可以直接从安全区域布局指南中获取间隙?

问题2:据推测,底栏应该可以在不改变键盘大小的情况下改变大小,所以我还应该实现一些方法来监听栏的框架变化。这是最好的viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)吗?还是别处?

谢谢

4

5 回答 5

38

似乎对我有用的是计算与view.safeAreaLayoutGuide.layoutFrame键盘框架之间的交点,然后将其高度设置为additionalSafeAreaInsets.bottom,而不是整个键盘框架高度。我的视图控制器中没有工具栏,但我确实有一个标签栏,并且它被正确解释了。

完整代码:

import UIKit

public extension UIViewController 
{
    func startAvoidingKeyboard() 
    {    
        NotificationCenter.default
            .addObserver(self,
                         selector: #selector(onKeyboardFrameWillChangeNotificationReceived(_:)),
                         name: UIResponder.keyboardWillChangeFrameNotification,
                         object: nil)
    }

    func stopAvoidingKeyboard() 
    {
        NotificationCenter.default
            .removeObserver(self,
                            name: UIResponder.keyboardWillChangeFrameNotification,
                            object: nil)
    }

    @objc
    private func onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification) 
    {
        guard 
            let userInfo = notification.userInfo,
            let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue 
        else {
            return
        }

        let keyboardFrameInView = view.convert(keyboardFrame, from: nil)
        let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom)
        let intersection = safeAreaFrame.intersection(keyboardFrameInView)

        let keyboardAnimationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey]
        let animationDuration: TimeInterval = (keyboardAnimationDuration as? NSNumber)?.doubleValue ?? 0
        let animationCurveRawNSN = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
        let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
        let animationCurve = UIView.AnimationOptions(rawValue: animationCurveRaw)

        UIView.animate(withDuration: animationDuration,
                       delay: 0,
                       options: animationCurve,
                       animations: {
            self.additionalSafeAreaInsets.bottom = intersection.height
            self.view.layoutIfNeeded()
        }, completion: nil)
    }
}
于 2017-09-08T12:55:16.183 回答
6

如果您需要支持回到 IOS11 之前的版本,您可以使用 Fabio 的功能并添加:

if #available(iOS 11.0, *) { }

最终解决方案:

extension UIViewController {

    func startAvoidingKeyboard() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(_onKeyboardFrameWillChangeNotificationReceived(_:)),
                                               name: NSNotification.Name.UIKeyboardWillChangeFrame,
                                               object: nil)
    }

    func stopAvoidingKeyboard() {
        NotificationCenter.default.removeObserver(self,
                                                  name: NSNotification.Name.UIKeyboardWillChangeFrame,
                                                  object: nil)
    }

    @objc private func _onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification) {
        if #available(iOS 11.0, *) {

            guard let userInfo = notification.userInfo,
                let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
                    return
            }

            let keyboardFrameInView = view.convert(keyboardFrame, from: nil)
            let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom)
            let intersection = safeAreaFrame.intersection(keyboardFrameInView)

            let animationDuration: TimeInterval = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
            let animationCurveRawNSN = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
            let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue
            let animationCurve = UIViewAnimationOptions(rawValue: animationCurveRaw)

            UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: {
                self.additionalSafeAreaInsets.bottom = intersection.height
                self.view.layoutIfNeeded()
            }, completion: nil)
        }
    }
}
于 2017-10-04T13:26:01.873 回答
1

我使用不同的方法。我有一个添加到视图层次结构中的视图 (KeyboardProxyView)。我把它固定在主视图的底部,并用键盘调整它的高度。这意味着我们可以把keyboardProxy当作键盘视图——除了它是一个普通视图,所以你可以在它上面使用约束。

这使我可以手动约束相对于 keyboardProxy 的其他视图。

例如 - 我的工具栏根本不受限制,但我可能有 inputField.bottom >= keyboardProxy.top

下面的代码(注意——我使用 HSObserver 和 PureLayout 来进行通知和自动布局——但如果你想避免它们,你可以轻松地重写该代码)

import Foundation
import UIKit
import PureLayout
import HSObserver

/// Keyboard Proxy view will mimic the height of the keyboard
/// You can then constrain other views to move up when the KeyboardProxy expands using AutoLayout
class KeyboardProxyView: UIView, HSHasObservers {
    weak var keyboardProxyHeight: NSLayoutConstraint!

    override func didMoveToSuperview() {
        keyboardProxyHeight = self.autoSetDimension(.height, toSize: 0)

        let names = [UIResponder.keyboardWillShowNotification,UIResponder.keyboardWillHideNotification]
        HSObserver.init(forNames: names) { [weak self](notif) in
            self?.updateKeyboardProxy(notification: notif)
        }.add(to: self)

        activateObservers()
    }

    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder!.next
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }

    func updateKeyboardProxy(notification:Notification){
        let userInfo = notification.userInfo!

        let animationDuration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
        let keyboardEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
        let convertedKeyboardEndFrame = self.superview!.convert(keyboardEndFrame, from: self.window)
        let rawAnimationCurve = (notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).uint32Value << 16
        let animationCurve = UIView.AnimationOptions(rawValue:UInt(rawAnimationCurve))

        keyboardProxyHeight.constant = self.superview!.bounds.maxY - convertedKeyboardEndFrame.minY
        //keyboardProxyHeight.constant = keyboardEndFrame.height

        UIView.animate(withDuration: animationDuration, delay: 0.0, options: [.beginFromCurrentState, animationCurve], animations: {
            self.parentViewController?.view.layoutIfNeeded()
        }, completion: nil)
    }
}
于 2019-11-05T10:55:13.150 回答
1

bottom inset:

    var safeAreaBottomInset: CGFloat = 0
    if #available(iOS 11.0, *) {
        safeAreaBottomInset = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
    }

whole keyboard function:

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

    NotificationCenter.default.addObserver(self, selector: #selector(self.onKeyboardWillChangeFrame), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}

@objc private func onKeyboardWillChangeFrame(_ notification: NSNotification) {
        guard let window = self.view.window,
            let info = notification.userInfo,
            let keyboardFrame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
            let duration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval,
            let animationCurve = info[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
        else {
            return
        }

        var safeAreaInset: CGFloat = 0
        if #available(iOS 11.0, *) {
            safeAreaInset = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
        }
        self.keyboardConstraint.constant = max(0, self.view.frame.height - window.convert(keyboardFrame, to: self.view).minY - safeAreaInset)

        UIView.animate(
            withDuration: duration,
            delay: 0,
            options: [UIView.AnimationOptions(rawValue: animationCurve.uintValue), .beginFromCurrentState],
            animations: { self.view.layoutIfNeeded() },
            completion: nil
        )
    }
于 2020-05-11T12:37:05.823 回答
0

排除底部安全区域对我有用:

 NSValue keyboardBounds = (NSValue)notification.UserInfo.ObjectForKey(UIKeyboard.FrameEndUserInfoKey);

_bottomViewBottomConstraint.Constant = keyboardBounds.RectangleFValue.Height - UIApplication.SharedApplication.KeyWindow.SafeAreaInsets.Bottom;
                        View.LayoutIfNeeded();
于 2018-11-14T05:15:53.363 回答