UITextField, UITextView がキーボードで隠れないようにする - Swift 版
環境:
Swift 4.2.1
Deployment Target: 11.0
以前にも同じ内容の記事を書いたが久しぶりに Swift でも同じような実装をしたのでメモ。
前提として
- UIViewController.view に UIScrollView を貼り付け
- UIScrollView 上に UITextField, UITextView を貼り付け
- UIScrollView の bottom は view.safeAreaInsets.bottom に合わせる
という条件になっている。
FirstResponder となっている UITextField, UITextView を探す UIView Extension
extension UIView { func findFirstResponder() -> UIView? { if isFirstResponder { return self } for v in subviews { if let responder = v.findFirstResponder() { return responder } } return nil } }
Keyboard の Notification.userInfo をマッピングする Struct
struct UIKeyboardInfo { let frame: CGRect let animationDuration: TimeInterval let animationCurve: UIView.AnimationOptions init?(info: [AnyHashable : Any]) { guard let frame = (info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let duration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval, let curve = info[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt else { return nil } self.frame = frame animationDuration = duration animationCurve = UIView.AnimationOptions(rawValue: curve) } }
UIScrollView を探す UIView Extension
extension UIView { func findSuperView<T>(ofType: T.Type) -> T? { if let superView = self.superview { switch superView { case let superView as T: return superView default: return superView.findSuperView(ofType: ofType) } } return nil } }
ViewController
class MyViewController: UIViewController { //@IBOutlet weak var scrollView: UIScrollView! override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) } override func viewWillDisappear(_ animated: Bool) { NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) super.viewWillDisappear(animated) } @objc private func onKeyboardWillShow(_ notification: Notification) { guard let userInfo = notification.userInfo, let keyboardInfo = UIKeyboardInfo(info: userInfo), let inputView = view.findFirstResponder(), let scrollView = inputView.findSuperView(ofType: UIScrollView.self) else { return } let inputRect = inputView.convert(inputView.bounds, to: scrollView) let keyboardRect = scrollView.convert(keyboardInfo.frame, from: nil) let offsetY = inputRect.maxY - keyboardRect.minY if offsetY > 0 { let contentOffset = CGPoint(x: scrollView.contentOffset.x, y: scrollView.contentOffset.y + offsetY) scrollView.contentOffset = contentOffset } // 例えば iPhoneX の Portrait 表示だと bottom に34ptほど隙間ができるのでその分を差し引く let contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardInfo.frame.height - view.safeAreaInsets.bottom, right: 0) scrollView.contentInset = contentInset scrollView.scrollIndicatorInsets = contentInset } @objc private func onKeyboardWillHide(_ notification: Notification) { guard let inputView = view.findFirstResponder(), let scrollView = inputView.findSuperView(ofType: UIScrollView.self) else { return } scrollView.contentInset = .zero scrollView.scrollIndicatorInsets = .zero } }