More than 1 year has passed since last update.

画面下部のテキストボックス(UITextField)をタップしてキーボードが出てくる際、viewをスクロールさせてあげないとテキストボックスがキーボードの裏で隠れてしまう。iOS開発を自分でやってみて初めて知った事実。

UITextFieldを載っけているViewをスクロールさせて、これを防ぐ。

準備

まずはViewをStoryboardで普通に作っていく。Xcode7.2で試しています

スクロールさせなきゃいけないので、UIViewControllerを置いた後、UIScrollView => UIViewの順にViewを重ねていく。

AutoLayoutの設定は詳しく書かないが、上記2つのviewとも親viewの上下左右の端にピッタリくっつける。一番最後に置いたUIViewは、UIViewControllerについていたUIView(親の親view)と高さ・幅を等しくする。この最後に置いたUIViewの上にUITextFieldをセット。

↓ こんな感じのStoryboardになる。

スクリーンショット 2015-12-17 23.49.38.png

UITextFieldをセットしたUIViewのAutolayout設定は以下。

スクリーンショット 2015-12-17 23.42.41.png


基本的なviewが出来たのでシミュレータで実行。UITextFieldは隠れてしまい、何が入力されているか分からない。

Simulator Screen Shot 2015.12.17 23.51.22.png

キーボードで隠れないように

というわけで、これを防ぐ。

キーボードの表示/非表示の通知

キーボードが表示される/隠れる直前に、UIKeyboardWillShowNotification, UIKeyboardWillHideNotificationというNotificationが飛んでいるので、それをオブザーバでキャッチできるようにする。

UIScrollViewUITextFieldをOutletでコード側で利用できるようにしておく。
また、UITextFieldのデリゲートを設定し、returnキーを押したらキーボードを隠す。

ViewController.swift
import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var textField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()

        textField.delegate = self
    }

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

        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: "keyboardWillBeShown:",
            name: UIKeyboardWillShowNotification,
            object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: "keyboardWillBeHidden:",
            name: UIKeyboardWillHideNotification,
            object: nil)
    }

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

        NSNotificationCenter.defaultCenter().removeObserver(self,
            name: UIKeyboardWillShowNotification,
            object: nil)
        NSNotificationCenter.defaultCenter().removeObserver(self,
            name: UIKeyboardWillHideNotification,
            object: nil)
    }

    func keyboardWillBeShown(notification: NSNotification) {
    }

    func keyboardWillBeHidden(notification: NSNotification) {
    }

    // MARK: - UITextFieldDelegate

    func textFieldShouldReturn(textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

}

キーボードの表示 <=> 非表示の切替が可能になり、そのイベントが起こる際にkeyboardWillBeShown:, keyboardWillBeHidden:を実行するようになった。

画面のスクロール処理

続いて、本題の画面スクロール処理を書いていく。以下の様な方針。

  • UIKeyboardWillShowNotificationuserInfoにキーボードの位置情報が入っているので利用
  • キーボードの位置とUITextFieldの位置から、スクロールサイズを計算しスクロール
  • UIKeyboardWillShowNotificationには、キーボード表示時のアニメーション時間も含まれるため、その時間で画面をスクロールさせる
ViewController.swift
func keyboardWillBeShown(notification: NSNotification) {
    if let userInfo = notification.userInfo {
        if let keyboardFrame = userInfo[UIKeyboardFrameEndUserInfoKey]?.CGRectValue, animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue {
            restoreScrollViewSize()

            let convertedKeyboardFrame = scrollView.convertRect(keyboardFrame, fromView: nil)
            let offsetY: CGFloat = CGRectGetMaxY(textField.frame) - CGRectGetMinY(convertedKeyboardFrame)
            if offsetY < 0 { return }
            updateScrollViewSize(offsetY, duration: animationDuration)
        }
    }
}

func keyboardWillBeHidden(notification: NSNotification) {
    restoreScrollViewSize()
}

func updateScrollViewSize(moveSize: CGFloat, duration: NSTimeInterval) {
    UIView.beginAnimations("ResizeForKeyboard", context: nil)
    UIView.setAnimationDuration(duration)

    let contentInsets = UIEdgeInsetsMake(0, 0, moveSize, 0)
    scrollView.contentInset = contentInsets
    scrollView.scrollIndicatorInsets = contentInsets
    scrollView.contentOffset = CGPointMake(0, moveSize)

    UIView.commitAnimations()
}

func restoreScrollViewSize() {
    scrollView.contentInset = UIEdgeInsetsZero
    scrollView.scrollIndicatorInsets = UIEdgeInsetsZero
}

上記の方針にしたがって何を行っているかというと、次の通り。

  • UIKeyboardWillShowNotificationには、以下の情報が含まれているので、それを取得
    • キーボードのframeの情報(UIKeyboardFrameEndUserInfoKey)
    • キーボードのアニメーション時間(UIKeyboardAnimationDurationUserInfoKey)
  • キーボードのframeの情報は、デバイスのウインドウ全体の中での座標になっているので、convertRect(_:fromView:)scrollView内での座標に変換
  • textFieldの下端とキーボードの上端のy座標(変換後)を比較
    • 前者のほうが大きかったら、キーボードに隠れないということなので処理終了
    • 後者が大きければキーボードから隠れないように、両者の差分だけスクロール処理
  • updateScrollViewSize(_:duration:)でスクロール処理を実行
    • scrollViewcontentInset, scrollIndicatorInsetsを調整し、先ほど計算した差分の大きさだけ下方向に拡大
    • scrollViewcontentOffsetを調整し、y方向に同様の大きさだけスクロール
    • これらの処理を、取得済のアニメーション時間で実行

これで、キーボードが表示されるときにUIScrollViewが上方向にスクロールするようになった。(※UITextFieldが隠れる場合のみ)

キーボードが隠れるときにはこれの逆の操作をrestoreScrollViewSize:で行い、scrollViewcontentInset, scrollIndicatorInsetsをデフォルトの値に戻す。

完成

以上で、キーボードが隠れなくなり、自分が何を入力してるのか分かるようになった:smile:

scroll-sample.gif

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.