iPhone X键盘出现额外空间的问题

26
我已经在聊天界面中添加了一个 tableViewconstraint,使其保持屏幕底部。我通过添加键盘高度的方式来更改约束值,在除 iPhone X 之外的所有设备上都可以正常工作。
当键盘不可见时,UI 如下:

进入图像描述

这很好。
问题出现在键盘出现时,textView 和键盘之间出现了空白:

进入图像描述

我需要尝试不同的方法来解决问题吗?还是可以使用约束来解决?
6个回答

48

在计算约束值时,尝试减去安全区域底部插图的高度。

这里是一个示例实现,用于处理UIKeyboardWillChangeFrame通知。

@objc private func keyboardWillChange(_ notification: Notification) {
    guard let userInfo = (notification as Notification).userInfo, let value = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue else { return }
    let newHeight: CGFloat
    if #available(iOS 11.0, *) {
        newHeight = value.cgRectValue.height - view.safeAreaInsets.bottom
    } else {
        newHeight = value.cgRectValue.height
    }
    myConstraint.value = newHeight
}

1
谢谢兄弟。我本来就要实现这个逻辑,但你回答得很好。我们需要检查操作系统是否有点奇怪吗?UIKeyboardFrameEndUserInfoKey应该返回减去安全区域插图的高度。 - Amit
1
@Amit 如果你的部署目标小于11.0,那么你需要执行版本检查,因为在iOS 11之前safeAreaInsets不存在。 - nathangitter
3
view.safeAreaInsets.bottom 返回值为 0,但是对我来说空间仍然存在。 - Sayalee Pote
刚发现这个问题在iOS12中已经不存在了。 - Ning
它确实存在 @Ning - Joshua Wolff
问题在iOS 15上仍然存在,底部仍为0。即使调用了safeAreaInsetsDidChange()(正如其他人所建议的那样)。 - user11638666

2
我的问题在于我的视图在一个子视图控制器中。转换CGRect就解决了问题。
@objc private func keyboardWillChangeFrame(notification: NSNotification) {
    guard let userInfo = notification.userInfo, let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let duration: TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue else {
        return
    }

    let convertedFrame = view.convert(endFrame, from: nil)
    let endFrameY = endFrame.origin.y

    let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
    let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue
    let animationCurve: UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)

    if endFrameY >= UIScreen.main.bounds.size.height {
        inputContainerViewBottomLayoutConstraint.constant = 0.0
    }  else {
        var newHeight = view.bounds.size.height - convertedFrame.origin.y
        if #available(iOS 11.0, *) {
            newHeight = newHeight - view.safeAreaInsets.bottom
        }
        inputContainerViewBottomLayoutConstraint.constant = newHeight
    }

    UIView.animate(withDuration: duration, delay: TimeInterval(0), options: animationCurve, animations: {
        self.view.layoutIfNeeded()
    },completion: { _ in
        self.scrollView.scrollToBottom()
    })
}

1

Swift 4/5 iPhone X

我不得不稍微调整了Nathan的答案才能让它正常工作。这个方法百分之百有效。

注意:确保你已经从storyboard中将textView/view的底部约束拖动到ViewController的安全区域的底部,并且你还在目标视图控制器的项目导航器中控制拖动并创建了一个outlet。我在我的示例中将其命名为bottomConstraint。我的文本输入字段包含在一个视图(MessageInputContainerView)中,以允许进行额外的发送按钮对齐等操作。

Constraint Demo

这里是代码:

@objc private func keyboardWillChange(_ notification: Notification) {
    guard let userInfo = (notification as Notification).userInfo, let value = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue else {
        return
    }
    
    if ((userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue) != nil {
        let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
        var newHeight: CGFloat
        if isKeyboardShowing {
            if #available(iOS 11.0, *) {
                newHeight = value.cgRectValue.height - view.safeAreaInsets.bottom
                bottomConstraint.constant = newHeight
                
            }
        }
        else {
            newHeight = value.cgRectValue.height
            bottomConstraint?.constant = view.safeAreaInsets.bottom + messageInputContainerView.bounds.height

        }
    }
}

1
这不是代码示例的一部分,而是为了我的自用目的。该代码在if和else中都覆盖了底部约束的值。 - elarcoiris

0

在 iPhone X 中,UIKeyboardFrameBeginUserInfoKey 报告的键盘值在以下两种情况下不同:

  • 键盘未显示,文本字段成为第一响应者
  • 键盘已经显示,另一个文本字段成为第一响应者。

要获取键盘的最终高度(包括安全区域插图),请使用 UIKeyboardFrameEndUserInfoKey。

在 iOS 11(特别是 iPhone X)中,您可以考虑减去安全区域底部插图。

    NSValue *keyboardEndFrameValue = notification.userInfo[UIKeyboardFrameEndUserInfoKey];
    if (keyboardEndFrameValue != nil) {
        CGRect keyboardSize = [keyboardEndFrameValue CGRectValue];
        _keyboardHeight = keyboardSize.size.height;
        if (@available(iOS 11.0, *)) {
            CGFloat bottomSafeAreaInset = self.view.safeAreaInsets.bottom;
            _keyboardHeight -= bottomSafeAreaInset;
        } else {
            // Fallback on earlier versions
        }
    }

0
在你的ViewController中为你的约束创建一个插座。现在我将暂时称其为yourConstraint。然后添加代码以发现键盘何时显示和何时关闭。然后相应地更改约束的常量。这样可以让你继续使用约束。
viewWillAppear中:
NotificationCenter.default.addObserver(self, selector: #selector(YourViewController.keyboardWillShow), name:        NSNotification.Name.UIKeyboardWillShow, object: nil) // <=== Replace YourViewController with name of your view controller
NotificationCenter.default.addObserver(self, selector: #selector(YourViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil) // <=== Replace YourViewController with name of your view controller

viewWillDisappear方法中:
NotificationCenter.default.removeObserver(self)

在你的UIViewController中。
@objc private func keyboardWillShow(notification: Notification) {
    guard let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
        yourConstraint.isActive = false // <=== 
        view.layoutIfNeeded()
        return
    }
    let duration: TimeInterval = ((notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue) ?? 0.4
    yourConstraint.constant = newValue // <===
    UIView.animate(withDuration: duration) {
        self.view.layoutIfNeeded()
    }
}

@objc private func keyboardWillHide(notification: Notification) {
    let duration: TimeInterval = ((notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue) ?? 0.4
    yourConstraint.constant = oldValue
    UIView.animate(withDuration: duration) {
        self.view.layoutIfNeeded()
    }
}

UIView.animate 不是必需的(块内部是必需的),但它会使过渡效果更加流畅。


我也遇到了同样的问题。问题出在newValue上。我已将newValue添加为keyboardSize.height,这在除iPhone X以外的所有设备上都能完美运行。正如您在第一张图片中所看到的,下方新功能关闭应用程序的那一行有白色空间。我认为当键盘出现时会添加该空间。 - Amit
我明白了。很抱歉重复了你的努力... 你能否在storyboard中发布你视图层次结构的截图?你是否尝试为iPhone X设置不同的约束?你可以使用类似这样的语句来区分设备 https://dev59.com/g18e5IYBdhLWcg3wJnyl#26962452 - erik_m_martens

0

这对我有用

简而言之:self.view.safeAreaInsets.bottom 返回了0。关键是要使用UIApplication.shared.keyWindow.safeAreaInsets.bottom,而不是[源代码]

让replyField成为您感兴趣的UITextField。

1)在viewDidLoad()中添加观察者。

NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)

2) 在类中设置全局变量作为约束条件。

var textFieldBottomConstraintKeyboardActive : NSLayoutConstraint?
var textFieldBottomConstraintKeyboardInactive : NSLayoutConstraint?

3) 在viewDidLoad中调用一个名为“setConstraints”的函数来设置约束。

let replyFieldKeyboardActiveConstraint = self.replyField.bottomAnchor.constraint(
    equalTo: self.view.safeAreaLayoutGuide.bottomAnchor,
    constant: -1 * Constants.DEFAULT_KEYBOARD_HEIGHT /* whatever you want, we will change with actual height later */ + UITabBar.appearance().frame.size.height
)

let replyFieldKeyboardInactiveConstraint = self.replyField.bottomAnchor.constraint(
    equalTo: self.view.safeAreaLayoutGuide.bottomAnchor
)

self.textFieldBottomConstraintKeyboardActive = replyFieldKeyboardActiveConstraint
self.textFieldBottomConstraintKeyboardInactive = replyFieldKeyboardInactiveConstraint

self.textFieldBottomConstraintKeyboardActive?.isActive = false
self.textFieldBottomConstraintKeyboardInactive?.isActive = true

4) 定义 keyboardWillShow 和 keyboardWillHide 方法。关键在于我们如何在 keyboardWillShow 方法中定义 heightOffset。

@objc func keyboardWillShow(notification: NSNotification) {
    guard let userinfo = notification.userInfo else {
        return
    }
    guard let keyboardSize = userinfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {
        return
    }
    let keyboardFrame = keyboardSize.cgRectValue

    self.view.layoutIfNeeded()

    let heightOffset = keyboardFrame.height - UITabBar.appearance().frame.height - (UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0)

    self.textFieldBottomConstraintKeyboardActive?.constant = -1 * heightOffset

    self.textFieldBottomConstraintKeyboardActive?.isActive = true
    self.textFieldBottomConstraintKeyboardInactive?.isActive = false

    self.view.setNeedsLayout()
}


@objc func keyboardWillHide(notification: NSNotification) {
    self.textFieldBottomConstraintKeyboardActive?.isActive = false
    self.textFieldBottomConstraintKeyboardInactive?.isActive = true
}

这确实是正确的答案。我使用了view.window.safeAreaInsets.bottom,但本质上是一样的。 - user11638666

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接