嵌ったのでメモしておきます。。
概要
ScrollView上に複数のUITextFieldが縦に並んでいる画面で、キーボードを表示したままフォーカスしているUITextFieldを切り替える
ScrollViewに触れずに、UITextFieldに対するフォーカスを切り替えられるタイプもスコープ。(キーボード上に戻るボタンと進むボタンが乗ってる、↓のようなToolbarがついててそれでフォーカスを切り替えるイメージ)
その際に、フォーカスしているUITextFieldが隠れないようにしたい(当然単にキーボード表示をする際にも隠れて欲しくない)
対応内容
キーボードが表示/非表示になる際に、以下のNotificationが飛ぶので、その中でscrollViewのinsetを切り替える事で対応できました。
- keyboardWillShowNotification
- keyboardWillHideNotification
実装
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShown(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHidden(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
@objc private func keyboardWillShown(_ notification: Notification?) {
guard let userInfo = notification.userInfo as? [String: Any],
let keyboardInfo = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {
return
}
// Keyboardの高さ分、scrollViewのbottomに対してinsetsを指定する
let keyboardSize = keyboardInfo.cgRectValue.size
let scrollInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height: right: 0)
scrollView.contentInset = scrollInsets
scrollView.scrollIndicatorInsets = scrollInsets
}
@objc private func keyboardWillHidden(_ notification: Notification?) {
// insetsを元に戻す
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = .zero
}
keyboardWillShowNotification
は名称的にキーボードが表示される時に通知されるように見えますが、
実際はキーボードを表示したまま別のUITextFieldにフォーカスした際も通知されるようです。
そのため、UITextFieldのフォーカスを変更した際に、キーボードのタイプが異なるためにキーボードの高さが変わってしまった場合も再度その高さを取得、Insetsを設定することが可能なため、上記を実装しておけば
- キーボードが表示されるタイミング
- キーボードを表示したまま、別のUITextFieldにフォーカスを変更
- その結果キーボードの高さが変わる場合も含む
の両方に対して対応できるようでした。
嵌った点
最初、keyboardInfoを keyboardFrameBeginUserInfoKey
から取っていたんですが、
これだとどうもキーボードを表示したままフォーカスを変更した際に、正しくキーボードの高さを取れないようで
フォーカスしているUITextFieldがキーボードに隠れてしまうことがありました。
https://developer.apple.com/documentation/uikit/uikeyboardframebeginuserinfokey
https://developer.apple.com/documentation/uikit/uikeyboardframeenduserinfokey
beginはおそらくキーボードの表示前にキーボードの高さを取っており、
そのためフォーカス変更前のキーボードの高さを取ってしまっていたため、正しくInsetsが更新できていなかったのかなーと思っております😥
キーボードの高さが変わる可能性がある場合はもちろん、そうでない場合もendで取得する方が安全そうですね。。
まとめ
https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
この手法を見つける前にも色々と試したのですが、どれも今ひとつ決まらず、最終的に上記のApple公式ドキュメントのおかげで解決したという...orz
迷ったらまずは公式ドキュメントを当たってみる癖が大事ですね😓