はじめに
iOSで入力ボックス(UITextField
, UITextView
など)をおいて、実際入力しようとしてみるとキーボードが重複して次の画面に進めなくなった!
みたいなこと、ありますよね。
とくにiPhone SEとか。iPadとか。
そういうとき、なんかviewの座標移動させてみたり、スクロールビューおいたり、いろいろしたりするかと思います。
個人的にはいつも
- ScrollView
- UIView
- UIStackView
- その他のView1
- その他のView2
という階層構造を持ったコンテナを作っていて、大体のレイアウトが割と柔軟に作れて、困ったことはあまりないので、紹介します。
UIStackViewは必ずしも必要ではないですが、
あるとかなり便利です。
UIStackViewに関しては別の記事に書きましたので、こちらを参照ください!
普通の画面をUIStackViewでよりロバストに
Content Insets
キーボードのために座標を動かすよりは、UIScrollViewのContentInsetsを調節するのが良いと思います。
実装上のイメージとしては、スクロールビューのコンテンツに、になにもない領域をそれだけ追加するような感じです。
navigationBar等があるときは自動的に上に付いてると思いますが、それと同じです。
キーボードが表示されている間、キーボードの高さ分のinsetsを追加することで、それだけスクロールできるようにすることができます。
contentInsetAdjustmentBehavior
contentInsetはデフォルトでは自動でsafeAreaとかに合わせて調整されるようになっています。
が、我々はキーボードに合わせて変化させたいので、これを無効化します。
iOS11より前はUIViewControllerのautomaticallyAdjustsScrollViewInsets
で設定していましたが、iOS 11以降は各scrollViewに対して設定できるようになりました。
containerScrollView
は上の構造でいう一番親になっているscrollViewです。
if #available(iOS 11.0, *) {
containerScrollView.contentInsetAdjustmentBehavior = .never
} else {
automaticallyAdjustsScrollViewInsets = false
}
(viewDidLoadで設定します)
scrollIndicatorInsets
右とか下とかにあるindicator用のインセットです。
これも同じように設定しておかないと、indicatorがコンテントの外にはみ出します。
なぜ別々に設定できるのかわからないですが、基本的にcontentInsetと同じ値をセットしておけばOKです。
KeyboardSizeObserver
insetsをキーボードの高さに合わせるためには、キーボードのサイズを監視する必要があります。
AppleのManaging the Keyboardが参考になります。
以下の4つの通知があり、observeするとKeyboardの変化を監視することができます。
- UIKeyboardWillShowNotification
- UIKeyboardDidShowNotification
- UIKeyboardWillHideNotification
- UIKeyboardDidHideNotification
この中で、RxKeyboardの実装を見てみると、
- UIKeyboardWillShowNotification
- UIKeyboardWillHideNotification
の2つだけをobserveしてるみたいなので、参考にして、これだけ監視するコードを作りました。
Gistはこちらに置きましたのでご参照ください。
https://gist.github.com/ha1f/d4fbacab1166a0c31907a0b6999fa58b
通知からはフレーム(CGRect)が渡されるんですが、上に貼ったAppleのdocを見てみると、サイズ以外意味ないみたいなことが書いてあるので、
サイズのみを監視できるようにしています。
SafeArea × Keyboard
2つともを考慮して設定する必要があります。
結論としては、各エッジに対して、大きい方をセットすれば良いとわかります。
scrollViewにたいして全画面になるように制約をつけている場合は、以下のようになります。
let newInsets = UIEdgeInsets(
top: view.safeAreaInsets.top,
left: 0,
bottom: max(keyboardSize.height, view.safeAreaInsets.bottom),
right: 0
)
scrollView.contentInset = newInsets
scrollView.scrollIndicatorInsets = newInsets
キーボードは下にしかないので、下だけ考慮しています。
左右はinsetをつけても良いですが、例えば横に突き抜けて背景色をつけたい場合などに障害になることがあって、
自分は左右に関してはcontentInsetはつけずに、コンテンツのViewごとにSafeAreaを考えることが多いです。
これをキーボードもしくはsafeAreaが変わるたびに呼び出して更新します。
組み合わせのソースはこちらに置きましたのでご参考ください。
https://github.com/ha1f/KeyboardSizeObserver/blob/de407d2b25ea2aa8fd4bae9449b8c5c1e225143a/KeyboardFrameObserver/ViewController.swift