【Swift】TextViewがキーボードに隠れてしまう問題を解決する
TextViewにテキストを入力していたら、テキストがキーボードの下にまで侵攻して何を入力しているのか分からないという問題が発生しました。
この問題を解決するべく、キーボードが出ている時はTextViewがキーボードに隠れない実装を行うことにしました。
完成形
今回はこの完成形を目指し進めていきます。
環境
- Xcode 12.5
- Swift 5.4
LayoutConstraintの紐付け
まずはtextView
のconstraints
を上下左右とも0
に設定しておきます。
そして、textView
とTextViewのBottomのLayoutConstraint
をviewController
に紐付けます。
対象のconstraint
をドラッグ&ドロップすることでviewController
と紐付けることが出来ます。
@IBOutlet weak private var textView: UITextView! @IBOutlet weak private var textViewButtonConstraint: NSLayoutConstraint!
紐付けが出来たので次に進みましょう。
キーボードの表示非表示の通知を受け取る
まずはviewDidLoad
内でNotificationCenter
のaddObserver
を行い、キーボードのShow
/ Hide
の通知を受け取り、同時にセレクターの処理を呼べるようにします。
override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) }
addObserver
の引数name
でどんな通知を受け取るかを指定することができるのですが、今回はキーボードが「これから出る時」、「これから閉じる」の通知を受け取りたいので、UIResponder.keyboardWillShowNotification
とUIResponder.keyboardWillHideNotification
をそれぞれ指定しました。
selector
で指定しているメソッドも追加しておきます。
@objc private func keyboardWillShow(_ notification: Notification) { // キーボードが現れる時の処理 } @objc private func keyboardWillHide(_ notification: Notification) { // キーボードが閉じる時の処理 }
キーボードが出る時の処理を実装する
今回はtextView
に入力した文字がキーボードが隠れないようにする為に、キーボードが出ている時はtextView
をキーボードの高さ分だけtextView
のBottomを上げるという処理で対応することにしました。
キーボードに関する値を取得する
まずは受け取ったnotification
から今回はキーボードの表示非表示に関する値を取り出すことができるので、Notification
のextension
を作成し、キーボードの高さ、キーボードのアニメーション時間、キーボードのアニメーション曲線の値を持つ変数を作成します。
extension Notification { // キーボードの高さ var keyboardHeight: CGFloat? { return (self.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height } // キーボードのアニメーション時間 var keybaordAnimationDuration: TimeInterval? { return self.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval } // キーボードのアニメーション曲線 var keyboardAnimationCurve: UInt? { return self.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt } }
keyboardFrameEndUserInfoKey
Notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey]
はNSValue
でプロパティとしてキーボードの長方形のCGRect
を持っています。今回はそのCGRect
からキーボードの高さだけを取り出して変数に渡しています。
keyboardAnimationDurationUserInfoKey
Notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey]
はキーボードのアニメーション時間を持っているので、TimeInterval
にダウンキャストして変数に渡しています。
keyboardAnimationCurveUserInfoKey
Notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey]
はキーボードのUIView.AnimationCurve
を持っており、これはキーボードがどのようなアニメーションカーブでアニメーションするかを定義しています。キーボードの動きに合わせてtextView
の高さを変更したいのでこの値も変数に渡しています。
textViewのBottomをキーボードの高さ分だけ上げる
@objc private func keyboardWillShow(_ notification: Notification) { guard let keyboardHeight = notification.keyboardHeight, let keyboardAnimationDuration = notification.keybaordAnimationDuration, let KeyboardAnimationCurve = notification.keyboardAnimationCurve else { return } UIView.animate(withDuration: keyboardAnimationDuration, delay: 0, options: UIView.AnimationOptions(rawValue: KeyboardAnimationCurve)) { // アニメーションさせたい実装を行う self.textViewButtonConstraint.constant = keyboardHeight } }
Notification
から受け取ったキーボードに関する各値をUIView.animate
の引数に渡しています。
- withDuration: アニメーションの持続時間
- delay: アニメーションの遅延時間
- options: アニメーションオプション
- animations: アニメーションするviewの実装
animations
のコードブロック内に、textViewButtonConstraint.constant
にkeyboardHeight
の値を代入することでTextView
のBottomがキーボードの高さ分だけ上がってくれます。
キーボードが隠れる時の処理を実装する
基本的にはキーボードが出る処理とほとんど変わりませんが、キーボードが閉じた時、いわばTextView
を全面に表示したい時なので、UIView.animate
のanimations
のコードブロック内で、textViewButtonConstraint.constant
の値を0
にしてあげるだけで全体に表示されるようになります。
@objc private func keyboardWillHide(_ notification: Notification) { guard let keyboardAnimationDuration = notification.keybaordAnimationDuration, let KeyboardAnimationCurve = notification.keyboardAnimationCurve else { return } UIView.animate(withDuration: keyboardAnimationDuration, delay: 0, options: UIView.AnimationOptions(rawValue: KeyboardAnimationCurve)) { self.textViewButtonConstraint.constant = 0 } }
おまけ
今回の実装では、キーボードの高さ分だけtextView
を上げているつもりですが、実はBottomのsafeAreaInset
分も考慮されたまま上がっています。その為、キーボードとtextView
の間にやや隙間が出来てしまいます。
この問題は、キーボードの高さからBottomのsafeAreaInset
の高さを引いてあげると解消出来ます。
UIView.animate(withDuration: keyboardAnimationDuration, delay: 0, options: UIView.AnimationOptions(rawValue: KeyboardAnimationCurve)) { self.textViewBottomConstraint.constant = keyboardHeight - self.view.safeAreaInsets.bottom }
コード
今回のコードはGitHubに載せております。
おわりに
希望通りの動きを実装することができました。また、keyboardWillShow
とkeyboardWillHide
の中の実装があまり違いがないのでリファクタリングするのも良さそうですね。
この記事が誰かの助けになれれば幸いです☺︎