Autolayoutでキーボードの高さによってViewを調整する方法

つみきでアプリエンジニアをしている野田です。
前回のブログでAutoLayoutについて書いたんですが、実装していて詰まったところがありました。

それは、AutoLayoutを使ってUITextViewを配置しても、そのままだとキーボードが表示されてもUITextViewの高さがキーボードの高さに応じてリサイズされず、編集中の内容などが隠れてしまうという現象です。(Fig.1参照)

AutoLayoutを使わない場合はUITextViewのFrameをキーボードの高さによっていい感じに変更するコードを書いて対応すればFig.2のように実装できるのですが、折角AutoLayoutを使っているのにそこだけ手動で設定するのはなんかもやっとする!

そこで、AutoLayoutは使ったままでキーボード表示時にUITextViewをいい感じに調整できないかと思い、色々やった結果その方法があったので紹介します。

NSLayoutConstraintをIBOutletとして接続する

まずは下記のソースコードのIBOutletとUITextViewのBottomのVertical Space Constraintを画像のように接続します。
これで、UITextViewのBottomのVertical Space Constraintの値をコードから変更できるようになります。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var bottomLayoutConstraint: NSLayoutConstraint!

}

autoLayout_Keyboard_sample.png

キーボードをNotificationで監視する

あとは例のごとくキーボードの値をNotificationで監視して、キーボードのFrameを取得した後に、先ほどのconstraintの値を変更します。
コードは以下のようになります。

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        NSNotificationCenter.defaultCenter().addObserver(self,
                                                         selector: "keyboardWillChangeFrame:",
                                                         name: UIKeyboardWillChangeFrameNotification,
                                                         object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self,
                                                         selector: "keyboardWillHide:",
                                                         name: UIKeyboardWillHideNotification,
                                                         object: nil)
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)

        NSNotificationCenter.defaultCenter().removeObserver(self)
    }

   func keyboardWillChangeFrame(notification: NSNotification) {

        if let userInfo = notification.userInfo {

            let keyBoardValue : NSValue = userInfo[UIKeyboardFrameEndUserInfoKey]! as NSValue
            var keyBoardFrame : CGRect = keyBoardValue.CGRectValue()
            let duration : NSTimeInterval = userInfo[UIKeyboardAnimationDurationUserInfoKey]! as NSTimeInterval

            self.bottomLayoutConstraint.constant = keyBoardFrame.height

            UIView.animateWithDuration(duration, animations: { () -> Void in
                self.view.layoutIfNeeded()
            })

        }
    }

   func keyboardWillHide(notification: NSNotification) {
        if let userInfo = notification.userInfo {

            let duration : NSTimeInterval = userInfo[UIKeyboardAnimationDurationUserInfoKey]! as NSTimeInterval

            self.bottomLayoutConstraint.constant = 0

            UIView.animateWithDuration(duration, animations: { () -> Void in
                self.view.layoutIfNeeded()
            })

        }
    }

キーボード監視して表示される際にその高さを習得します。
取得した高さをbottomLayoutConstraintのconstantに設定します。
設定するconstantの値はlayoutの付け方によってキーボードの高さのマイナス値に変わる場合もあります。
非表示にする際は元のlayoutのconstantの値に戻してあげます。今回は0なので直接0を設定しました。
やることは以上です。

これだけで、キーボードが表示されるとUITextViewの高さが表示されたキーボードを考慮されたものになります。

デバイスの回転に対応する

上記のコードでPortrait限定の場合は問題なく動くようですが、回転を考慮した場合にiOS7以下では取得できるキーボードの高さがPortrait基準?になっているためFrameの変換必要になります。
そのため、さきほどのkeyboardWillChangeFrameを少し書き換えてあげます。

    func keyboardWillChangeFrame(notification: NSNotification) {

        if let userInfo = notification.userInfo {

            let keyBoardValue : NSValue = userInfo[UIKeyboardFrameEndUserInfoKey]! as NSValue
            var keyBoardFrame : CGRect = keyBoardValue.CGRectValue()
            let duration : NSTimeInterval = userInfo[UIKeyboardAnimationDurationUserInfoKey]! as NSTimeInterval

           //適当にOS判定
            if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
                keyBoardFrame = self.view.convertRect(keyBoardFrame, fromView: nil)
            }

            self.bottomLayoutConstraint.constant = keyBoardFrame.height

            UIView.animateWithDuration(duration, animations: { () -> Void in
                self.view.layoutIfNeeded()
            })

        }
    }

これでデバイスを回転してもいい感じにTextViewの高さを変更してあげることができます。
以下にViewControllerのソース全体を記載しておきます。


//  ViewController.swift

import UIKit

class ViewController: UIViewController {


    @IBOutlet weak var textView: UITextView!
    @IBOutlet weak var bottomLayoutConstraint: NSLayoutConstraint!


    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        NSNotificationCenter.defaultCenter().addObserver(self,
                                                         selector: "keyboardWillChangeFrame:",
                                                         name: UIKeyboardWillChangeFrameNotification,
                                                         object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self,
                                                         selector: "keyboardWillHide:",
                                                         name: UIKeyboardWillHideNotification,
                                                         object: nil)
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)

        NSNotificationCenter.defaultCenter().removeObserver(self)
    }

    @IBAction func closeKeyboard(sender: UIBarButtonItem) {
        self.textView.resignFirstResponder()
    }

    func keyboardWillChangeFrame(notification: NSNotification) {

        if let userInfo = notification.userInfo {

            let keyBoardValue : NSValue = userInfo[UIKeyboardFrameEndUserInfoKey]! as NSValue
            var keyBoardFrame : CGRect = keyBoardValue.CGRectValue()
            let duration : NSTimeInterval = userInfo[UIKeyboardAnimationDurationUserInfoKey]! as NSTimeInterval

           //適当にOS判定
            if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
                keyBoardFrame = self.view.convertRect(keyBoardFrame, fromView: nil)
            }

            self.bottomLayoutConstraint.constant = keyBoardFrame.height

            UIView.animateWithDuration(duration, animations: { () -> Void in
                self.view.layoutIfNeeded()
            })

        }
    }

    func keyboardWillHide(notification: NSNotification) {
        if let userInfo = notification.userInfo {

            let duration : NSTimeInterval = userInfo[UIKeyboardAnimationDurationUserInfoKey]! as NSTimeInterval

            self.bottomLayoutConstraint.constant = 0

            UIView.animateWithDuration(duration, animations: { () -> Void in
                self.view.layoutIfNeeded()
            })

        }
    }
}

まとめ

今回は、Autolayoutでキーボードの高さによってVIewを調整する方法を紹介しました。ソースコードベースで実装した場合に比べ非常に簡単に管理することができるようになったと思います。
まだまだAutolayoutについては勉強不足ですが、詳しく知っておけば実装の強力な手助けになるのでガンガン知識を深めていきたいです。

スマホアプリ制作、Web制作、UIコンサルなどのご依頼はこちら

お問い合わせ