2
Help us understand the problem. What are the problem?

posted at

updated at

【Swift】UITextFieldがキーボードに隠れる場合のみ画面をずらす(UIScrolleViewなし)

はじめに

Swiftでは何もしなければ、UITextFieldがキーボードで隠れてしまいます。
それを改善するために、UIScrollViewを使わずに画面をずらす方法を実装しました。
UITextFieldがキーボードに隠れる場合のみ画面をずらすように実装しています。

これに関する情報や記事は多くあると思うのですが、ずらす幅の調整などで少し時間がかかってしまったので、記事として残しておきます。

コメントなどは、自分の分かりやすいように書いているので、細かい認識などは間違っている場合がありますが、ご容赦下さい。

1.キーボード開閉のタイミングを取得

まず、viewWillAppearNotificationを用いてキーボードが開閉するタイミングを取得する処理を記載。

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // キーボード開閉のタイミングを取得
        let notification = NotificationCenter.default
        notification.addObserver(self, selector: #selector(self.keyboardWillShow(_:)),
                                 name: UIResponder.keyboardWillShowNotification,
                                 object: nil)
        notification.addObserver(self, selector: #selector(self.keyboardWillHide(_:)),
                                 name: UIResponder.keyboardWillHideNotification,
                                 object: nil)
    }

2.キーボード表示通知の際の処理(keyboardWillShow)

    @objc func keyboardWillShow(_ notification: Notification) {

        // 編集中のtextFieldを取得
        guard let textField = _activeTextField else { return }
        // キーボード、画面全体、textFieldのsizeを取得
        let rect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
        guard let keyboardHeight = rect?.size.height else { return }
        let mainBoundsSize = UIScreen.main.bounds.size
        let textFieldHeight = textField.frame.height

        // ①
        let textFieldPositionY = textField.frame.origin.y + textFieldHeight + 10.0
        // ②
        let keyboardPositionY = mainBoundsSize.height - keyboardHeight

        // ③キーボードをずらす
        if keyboardPositionY <= textFieldPositionY {
            let duration: TimeInterval? =
                notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double
            UIView.animate(withDuration: duration!) {
                // viewをy座標方向にtransformする
                self.view.transform = CGAffineTransform(translationX: 0, y: keyboardTopPositionY - textFieldTopPositionY)
            }
        }
    }

y座標の0は、上部であることを意識すると分かりやすいと思います。

textField.frame.origin.y + textFieldHeight + 10.0で、テキストフィールドの底辺より10.0下のy座標をtextFieldPositionYとして取得。(テキストフィールドとキーボードの間に隙間ができるように+10.0としています。
mainBoundsSize.height - keyboardHeightで、キーボードの底辺のy座標をkeyboardPositionYとして取得。
③ ①が②より大きい(①が②の下にある)時に、viewをずらす。keyboardTopPositionY - textFieldTopPositionYで、テキストフィールドが隠れている分だけずらす。
※ viewをキーボードの高さ分ずらしたい時は、-keyboardHeightにします。

3.キーボード非表示通知の際の処理(keyboardWillHide)

    @objc func keyboardWillHide(_ notification: Notification) {
        let duration: TimeInterval? = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? Double
        UIView.animate(withDuration: duration!) {
            self.view.transform = CGAffineTransform.identity
        }
    }

その他、TextFieldに関する処理

    // textFieldがタップされた際に呼ばれる
    func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
        // 編集中のtextFieldを保持する
        _activeTextField = textField
        return true
    }
    
    // リターンがタップされた時にキーボードを閉じる
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    
    // 画面をタップした時にキーボードを閉じる
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.endEditing(true)
    }

コード全体

以下、コードの全文です。

ViewController.swift
import UIKit

class ViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet private var textField: UITextField!

    // 編集中のtextFieldを保持する変数
    private var _activeTextField: UITextField? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        
        textField.delegate = self
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // キーボード開閉のタイミングを取得
        let notification = NotificationCenter.default
        notification.addObserver(self, selector: #selector(self.keyboardWillShow(_:)),
                                 name: UIResponder.keyboardWillShowNotification,
                                 object: nil)
        notification.addObserver(self, selector: #selector(self.keyboardWillHide(_:)),
                                 name: UIResponder.keyboardWillHideNotification,
                                 object: nil)
    }

    // キーボード表示通知の際の処理
    @objc func keyboardWillShow(_ notification: Notification) {
        // 編集中のtextFieldを取得
        guard let textField = _activeTextField else { return }
        // キーボード、画面全体、textFieldのsizeを取得
        let rect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
        guard let keyboardHeight = rect?.size.height else { return }
        let mainBoundsSize = UIScreen.main.bounds.size
        let textFieldHeight = textField.frame.height

        let textFieldPositionY = textField.frame.origin.y + textFieldHeight + 10.0
        let keyboardPositionY = mainBoundsSize.height - keyboardHeight
        
        if keyboardPositionY <= textFieldPositionY {
            let duration: TimeInterval? =
                notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double
            UIView.animate(withDuration: duration!) {
                self.view.transform = CGAffineTransform(translationX: 0, y: keyboardTopPositionY - textFieldTopPositionY)
            }
        }
    }

    // キーボード非表示通知の際の処理
    @objc func keyboardWillHide(_ notification: Notification) {
        let duration: TimeInterval? = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? Double
        UIView.animate(withDuration: duration!) {
            self.view.transform = CGAffineTransform.identity
        }
    }

    // textFieldがタップされた際に呼ばれる
    func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
        // 編集中のtextFieldを保持する
        _activeTextField = textField
        return true
    }
    
    // リターンがタップされた時にキーボードを閉じる
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    
    // 画面をタップした時にキーボードを閉じる
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.endEditing(true)
    }
}

参考URL

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
2
Help us understand the problem. What are the problem?