⌨️
Swift で画面スクロールした時にキーボードを閉じるようにする実装
今回は画面スクロールした時にキーボードが閉じられるようにする実装方法を備忘録として残しておこうと思います。(LINE や Slack の UI がイメージに近く、キーボードが出ている状態で下に画面をスクロールしようとすると表示されている キーボードを閉じることができる実装方法になります)
どのような View か
View のコードのイメージとしては以下のような状況です。
TextInputView.swift
final class TextInputView: UIView {
@IBOutlet weak var textView: UITextView!
// ...
}
ScrollableViewController.swift
final class ScrollableViewController: UIViewController {
@IBOutlet private weak var scrollView: UIScrollView!
private var textInputView = TextInputView()
// ...
override func viewDidLoad() {
super.viewDidLoad()
// ...
}
override var inputAccessoryView: UIView? {
return textInputView
}
override var canBecomeFirstResponder: Bool {
return true
}
// ...
}
かなり省略してしまっていますが、ViewController が ScrollView で覆われていて、inputAccessoryView
として TextInputView
という独自の View を持っている形になります。
inputAccessoryView
はキーボードの上にくっつく View のようなもので、今回の場合はカスタム TextView のようなものを inputAccessoryView
として指定しています。
どうやってスクロールできるようにするか
keyboardDismissMode
を使った簡単な実装方法
実装方法は以下のようなイメージです。
ScrollableViewController.swift
final class ScrollableViewController: UIViewController {
@IBOutlet private weak var scrollView: UIScrollView!
private var textInputView = TextInputView()
// ...
override func viewDidLoad() {
super.viewDidLoad()
// これを追加するだけで終わり
scrollView.keyboardDismissMode = .interactive
// ...
}
override var inputAccessoryView: UIView? {
return textInputView
}
override var canBecomeFirstResponder: Bool {
return true
}
// ...
}
追加したのは ScrollView の keyboardDismissMode
を .interactive
にしているコードだけです。
これだけで、ScrollView をスクロールした時に指が inputAccessoryView
にかかったあたりでキーボードが閉じられるようになります。
UITapGestureRecognizer
を使った少し面倒な実装方法
異なる実装方法として UITapGestureRecognizer
を使ったパターンもありそうです。
実装方法は以下のようなイメージです。
ScrollableViewController.swift
final class ScrollableViewController: UIViewController {
@IBOutlet private weak var scrollView: UIScrollView!
private var textInputView = TextInputView()
// ...
override func viewDidLoad() {
super.viewDidLoad()
configureGesture()
// ...
}
override var inputAccessoryView: UIView? {
return textInputView
}
override var canBecomeFirstResponder: Bool {
return true
}
// ...
}
private extension ScrollableViewController {
func configureGesture() {
// onPan という function を panGesture として登録する
scrollView.panGestureRecognizer.addTarget(self, action: #selector(onPan(gesture:)))
// keyboardDismissMode は .none にしておいて誤動作を起こさないようにする
scrollView.keyboardDismissMode = .none
}
@objc
func onPan(gesture: UIPanGestureRecognizer) {
guard gesture.state == .changed else { return }
// view 中の gesture の位置を取得
let location = gesture.location(in: view)
// inputAccessoryView の場合は superView に対する位置を取得してあげないとうまくいきませんでした(良い方法はあるかもしれないです🙏)
guard let textInputViewRectForSuperView = textInputView.superView?.convert(textInputView.bounds, to: nil)
// もし指が TextInputView にかかり始めたら resignFirstResponder でキーボードを閉じる
if textInputViewRectForSuperView.contains(location) {
textInputView.textView.resignFirstResponder()
}
}
}
おわりに
他にも実装方法はあるかもしれませんが、とりあえず「keyboardDismissMode
を使った簡単な実装方法」を使っておくのが無難そうです🙏
参考
Discussion