iPhone X 键盘显示额外 space
iPhone X keyboard appear showing extra space
我创建了一个聊天 UI,其中我在屏幕底部为 tableView
添加了一个 constraint
。我正在通过添加键盘高度来更改约束值,这在除 iPhone X.
以外的所有设备中都可以正常工作
UI 当键盘不可见时:
很好。
问题是当键盘显示为空白时 space 在 textView 和键盘之间可见:
我是否必须为此尝试不同的方法,或者可以使用约束来解决?
发泄你对 ViewController
的约束。我现在将其称为 yourConstraint
。然后添加代码以发现何时显示键盘以及何时关闭键盘。您可以在那里相应地更改约束的 constant
。这允许您继续使用约束。
在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
不是必需的(块内部是必需的),但它使过渡看起来不错。
在计算约束值时尝试减去安全区域底部插图的高度。
这是一个处理 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
}
UIKeyboardFrameBeginUserInfoKey报告的键盘值在iPhone X:
这两种情况下是不同的
- 键盘不可见,文本字段成为第一响应者
- 键盘已经可见,不同的文本字段成为第一响应者。
要获得键盘的最终高度(包括安全区域插入)使用 UIKeyboardFrameEndUserInfoKey.
在iOS11(特别是iPhoneX)中,可以考虑减去安全区底部的insets
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
}
}
Swift 4/5 iPhone X
我不得不稍微调整内森的回答才能让它发挥作用。这个是 100%。
注意:确保您已将控制权从故事板的 textView/view 的底部约束拖动到 ViewController 中安全区域的底部,并且您还控制了拖动和创建目标视图控制器的项目导航器中的出口。我在示例中将其命名为 bottomConstraint。我的文本输入字段包含在一个视图 (MessageInputContainerView) 中,以允许额外的发送按钮对齐等。
代码如下:
@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
}
}
}
我的问题是我的视图位于子视图控制器中。转换 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()
})
}
这对我有用
TL;DR: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) 设置class内的全局变量作为约束
var textFieldBottomConstraintKeyboardActive : NSLayoutConstraint?
var textFieldBottomConstraintKeyboardInactive : NSLayoutConstraint?
3) 在 viewDidLoad 调用的函数中设置约束。
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
}
我创建了一个聊天 UI,其中我在屏幕底部为 tableView
添加了一个 constraint
。我正在通过添加键盘高度来更改约束值,这在除 iPhone X.
UI 当键盘不可见时:
很好。
问题是当键盘显示为空白时 space 在 textView 和键盘之间可见:
我是否必须为此尝试不同的方法,或者可以使用约束来解决?
发泄你对 ViewController
的约束。我现在将其称为 yourConstraint
。然后添加代码以发现何时显示键盘以及何时关闭键盘。您可以在那里相应地更改约束的 constant
。这允许您继续使用约束。
在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
不是必需的(块内部是必需的),但它使过渡看起来不错。
在计算约束值时尝试减去安全区域底部插图的高度。
这是一个处理 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
}
UIKeyboardFrameBeginUserInfoKey报告的键盘值在iPhone X:
这两种情况下是不同的- 键盘不可见,文本字段成为第一响应者
- 键盘已经可见,不同的文本字段成为第一响应者。
要获得键盘的最终高度(包括安全区域插入)使用 UIKeyboardFrameEndUserInfoKey.
在iOS11(特别是iPhoneX)中,可以考虑减去安全区底部的insets
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
}
}
Swift 4/5 iPhone X 我不得不稍微调整内森的回答才能让它发挥作用。这个是 100%。
注意:确保您已将控制权从故事板的 textView/view 的底部约束拖动到 ViewController 中安全区域的底部,并且您还控制了拖动和创建目标视图控制器的项目导航器中的出口。我在示例中将其命名为 bottomConstraint。我的文本输入字段包含在一个视图 (MessageInputContainerView) 中,以允许额外的发送按钮对齐等。
代码如下:
@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
}
}
}
我的问题是我的视图位于子视图控制器中。转换 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()
})
}
这对我有用
TL;DR: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) 设置class内的全局变量作为约束
var textFieldBottomConstraintKeyboardActive : NSLayoutConstraint?
var textFieldBottomConstraintKeyboardInactive : NSLayoutConstraint?
3) 在 viewDidLoad 调用的函数中设置约束。
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
}