キーボードと一緒に View を上下に動かす方法 (Swift)

複数の UITextField などが画面にある時、下のほうのテキストフィールドに入力しようとすると、テキストフィールドがキーボードで隠れてしまって困ったことありませんか?

今回は、次のような感じで、Swift で画面の下のほうにある UITextField に入力しようとした時に、キーボードと一緒に View 全体を上下に動かす方法をご説明します。

テスト用のアプリを作る

まずはテスト用に簡単なアプリを作ります。

Xcode で [iOS] の [App] の新規プロジェクトを作成してください。

ストーリーボードでデザインは適当でも良いので、以下のように Text Field を上、真ん中、下あたりに三つと、Button をその下に追加してください。

キーボードと一緒に View を上下に動かす方法 1


入力しようとした時に View を上に動かしたい UITextField のアウトレットを作っておいてください。

今回は下の UITextField に対して、textField3 という名前でアウトレットを作りました。

キーボードと一緒に View を上下に動かす方法 2

Text Field などの追加の方法や、アウトレットの作り方がわからない方は「基本的な iOS アプリの作り方」をご覧ください。


他の場所をタップした時にキーボードが閉じるようにする

Text Field 以外の場所をタップした時にキーボードが閉じるようにしたいので、ViewController.swift に以下のコードを追加してください。

こちらのコードの詳しい説明は「他の場所をタップした時にキーボードを閉じる方法」をご覧ください。

class ViewController: UIViewController {
    
    @IBOutlet weak var textField3: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tapGR: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
        tapGR.cancelsTouchesInView = false
        self.view.addGestureRecognizer(tapGR)
    }
    
    @objc func dismissKeyboard() {
        self.view.endEditing(true)
    }
}

キーボードと一緒に View を上下に動かす方法

それでは、Swift で画面の下のほうにある UITextField に入力しようとした時に、キーボードと一緒に View 全体を上下に動かす方法をご説明します。

ViewController.swift に次のコードを追加してください。

続けて、コードを順を追って説明していきます。

class ViewController: UIViewController {
    
    @IBOutlet weak var textField3: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tapGR: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
        tapGR.cancelsTouchesInView = false
        self.view.addGestureRecognizer(tapGR)
        
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
    }
    
    @objc func dismissKeyboard() {
        self.view.endEditing(true)
    }
    
    @objc func keyboardWillShow(notification: NSNotification) {
        if !textField3.isFirstResponder {
            return
        }
    
        if self.view.frame.origin.y == 0 {
            if let keyboardRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
                self.view.frame.origin.y -= keyboardRect.height
            }
        }
    }
    
    @objc func keyboardWillHide(notification: NSNotification) {
        if self.view.frame.origin.y != 0 {
            self.view.frame.origin.y = 0
        }
    }
}

12 行目では、NotificationCenter.default.addObserver() で、キーボードが開いた時の keyboardWillShowNotification を受け取って、その時に keyboardWillShow 関数を実行するようにオブザーバーを登録しています。

13 行目では、同様にNotificationCenter.default.addObserver() で、キーボードが閉じた時の keyboardWillHideNotification を受け取って、その時に keyboardWillHide 関数を実行するようにオブザーバーを登録しています。


20 ~ 30 行目では、キーボードが開いた時に呼ばれる、keyboardWillShow 関数を定義しています。

まず、textField3 以外の UITextField 用にキーボードが開いた時は、view が動いてほしくないので、textField3.isFirstResponder で textField3 の為にキーボードが開いているのかをチェックし、そうでない時は return しています。

そして、ViewController の view のフレームの y 座標が 0 の時(view が上にあがっていない時)は、26 行目のコードでキーボードのサイズを取得し、27 行目で view の y 座標からキーボードの高さを引くことによって、view 全体をキーボードの高さ分上に動かしています。


32 ~ 36 行目では、キーボードが閉じた時に呼ばれる、keyboardWillHide 関数を定義しています。

ここでは、ViewController の view のフレームの y 座標が 0 以外の時(view が上にあがっている時)は y 座標を 0 に戻すことによって、 view を元の位置に戻しています。

Navigation コントローラを使っていて、ナビゲーションバーの設定によっては、この ViewController の view のフレームの y 座標の起点を 0 ではなく、ナビゲーションバーを考慮した位置に指定する必要あります。そのような時は 0 の代わりに self.navigationController!.navigationBar.frame.maxY で取得した値を使用するとうまくいきます。

@objc func keyboardWillShow(notification: NSNotification) {
    if !textField3.isFirstResponder {
        return
    }

    let originY = self.navigationController!.navigationBar.frame.maxY
    if self.view.frame.origin.y == originY {
        if let keyboardRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            self.view.frame.origin.y -= keyboardRect.height
        }
    }
}

@objc func keyboardWillHide(notification: NSNotification) {
    let originY = self.navigationController!.navigationBar.frame.maxY
    if self.view.frame.origin.y != originY {
        self.view.frame.origin.y = originY
    }
}

これで、最初の動画のように、画面の下にある UITextField に入力しようとした時に、キーボードと一緒に View 全体が上下に動くようになります。

ちなみに、Apple の公式ドキュメントによると、ターゲットが iOS 9.0 以降で、今回のように addObserver(_:selector:name:object:) を使って登録した場合は、removeObserver() でオブザーバーの登録を削除する必要はありません。

キーボードと一緒に View を上下に動かす方法 5


View が上下した時の Button のイベントについて

少し気をつけないといけないのは、ボタンをタップしてキーボードが閉じ View が下がった時のボタンのイベントについてです。

ボタンのアクションをつける時、デフォルトで選択されている TouchUpInside を使う方が多いかもしれません。

このケースでは、ボタンをタップした瞬間にボタンも動くので TouchUpInside が起こりません。


OK ボタンに TouchUpInside, TouchUpOutside, TouchDown の三つのアクションを追加して試してみましょう。

キーボードと一緒に View を上下に動かす方法 3


キーボードが開いていない状態で OK ボタンをクリックすると次のようにプリントされます。

TouchDown
TouchUpInside

textField3 をタップしてキーボードを表示し、OK ボタンをクリックすると次のようにプリントされます。

TouchDown
TouchUpOutside

これは、View と共に OK ボタンが動くため、タップの手を離した時に場所がボタン外にいってしまうので、TouchUpInside の変わりに、TouchUpOutside が実行されています。

キーボードと共にボタンも上下する画面でボタンのタップで処理を行たい時は、タイミングがタップした瞬間でもよければ、TouchDown のイベントを使うと良いと思います。


以上、Swift でキーボードと一緒に View 全体を上下に動かす方法についてご説明しました。

© 2024 iOS 開発入門