画面の下部にconstraint
のtableView
を追加したチャットUIを作成しました。 iPhone Xを除くすべてのデバイスで正常に動作するキーボードの高さを追加することにより、制約値を変更しています。
キーボードが表示されていない場合のUI:
大丈夫です。
問題は、キーボードが表示されるとき、textViewとキーボードの間に空白スペースが見える場合です:
このために別のアプローチを試す必要がありますか、それとも制約を使用して解決できますか?
制約の値を計算するときは、セーフエリアの下部のインセットの高さを引いてみてください。
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
}
私の問題は、自分のビューが子ビューコントローラーにあることでした。 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()
})
}
Swift 4 iPhone X動作させるには、Nathanの答えを少し微調整する必要がありました。これは100%です。
注:テキストビュー/ビューの下部の制約からコントロールをストーリーボードからView Controllerの安全な領域の下部にドラッグし、ターゲットのView Controller Project Navigatorにドラッグしてアウトレットを作成したことを確認してください。私の例では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
}
}
}
UIKeyboardFrameによって報告されるキーボード値Begin UserInfoKeyは、iPhone Xの次の2つのケースで異なります。
キーボードのキーボードの最終的な高さ(セーフエリアのインセットを含む)を取得するには、UIKeyboardFrame End UserInfoKeyを使用します。
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
}
}
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
は必要ありません(ブロックの内部は必要です)が、遷移が見栄えよくなります。