【Swift】制約を変更してテキストフィールドやテキストビューがキーボードと被らないようにする方法。(Swift 2.1、XCode 7.2)
UITextViewとキーボードが被るとどうなるか
前回までの記事でUITextView(以下、テキストビュー)の使い方と設定項目について説明した。本記事の手順はその記事の続きからになるので、実装を試す場合は「UITextViewの使い方」の記事を読んでから行うことをお勧めする。
キーボードを使ってフィールドに文字列を入力することができるようになったが、もし、キーボードと被りそうな位置にテキストビューが配置されていたらどうなると思うか。
以下の動画のように、キーボードが邪魔でテキストビューの内容が全く見えなくなるので、誤入力の無いこと祈りながら入力を続けることになる。
「最初からキーボードと被らない位置にテキストビューを配置しておけばいいじゃないか。」と思うかも知れないが、デザインの都合上どうしても画面下部にテキストビューやテキストフィールドを置きたいことがある。
そんなときはキーボードに押し出されるようにテキストビューの位置を上にずらすことで対応する。
制約とソースコードのコネクションを確立する
テキストビューのずらしを実際にやってみよう。今回はソースコードから制約を変更してテキストビューをずらす動きを実装する。
まず、テキストビューをキーボードと被りそうな場所に移動する(下図赤矢印)。続いて、紫枠のPINボタンを押して吹き出しの設定画面を表示させる。黄緑枠の2つのマークをクリック、WidthとHeightにチェックを入れて「Add 4 Constraints」ボタンを押す。これで、サイズと距離の制約が追加された。
次に、下図赤枠のアシスタントエディタボタンを押してViewController.swiftを表示する。画面下端からの制約(Bottom Layout…)をドラッグ&ドロップでソースコードまで運んで吹き出しの設定画面を表示させる(下図青矢印)。
Connectionに「Outlet」、Nameに「testConstraint」を入力し、Connectボタンを押す。これで、ソースコードから先ほど追加した制約を操作できるようになった。
テキストビューとキーボードの被りを判定する方法
キーボードが現れるときに無条件でテキストビューをずらすのでは無く、キーボードがテキストビューと被る場合のみ、必要な長さだけ画面上部に向かってずらしたい。
ではどのように被る被らないを判定するのか。それは画面上端から部品までのY軸方向の距離をもとに判定する。
Y軸方向の距離として考えられるのは、下図のように画面上端から部品上端までの距離(最小距離)と画面上端から部品下端までの距離(最大距離)である。その中で、被りが確定するのはテキストビューの最大距離がキーボードの最小距離よりも大きい場合である。
ソースコードを実装する
上記の方法で判定するにはキーボードの座標を知る必要がある。しかし、テキストビューのデリゲートメソッドではキーボードの座標を取得することはできない。
そこで利用するのがNSNotificationCenterクラスの通知機能である。このクラスについては別の記事で説明するが、指定したイベントが発生したときに指定したメソッドを呼び出してくれるクラスである。呼び出しメソッドの引数からキーボードの座標を取得できる。
具体的には、ViewController.swiftを開き、viewDidLoadメソッドに以下のコードを追加する。
1 2 3 |
//キーボードが現れるときに通知するメソッドを登録する。 NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeShown:", name: UIKeyboardWillShowNotification, object: nil) |
続いて、以下のメソッドを追加する。
テキストビューとキーボードが被る場合は被った長さを制約に加算する処理をしている。また、キーボードがテキストビューを押し出す感じを表現するために、UIViewのanimatedWithDurationメソッドを利用してアニメーションを行った。
変数allYは被った長さの変数で、キーボードを閉じるときのメソッドでも利用するのでクラス変数としてメソッドの外で定義しておくこと。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//キーボードが開くときの呼び出しメソッド func keyboardWillBeShown(notification:NSNotification) { //キーボードのフレームを取得する。 if let keyboardFrame = notification.userInfo![UIKeyboardFrameEndUserInfoKey]?.CGRectValue { //テキストビューの最大Y座標と、キーボードの最小Y座標の差を計算する。 let zureY = CGRectGetMaxY(testTextView.frame) - CGRectGetMinY(keyboardFrame) if (zureY > 0) { //テキストビューの垂直方向の位置の制約に差分をプラスする。 testConstraint.constant += zureY allY += zureY //アニメーションさせる。 UIView.animateWithDuration(0.5, animations:{ self.view.layoutIfNeeded()}) } } } |
前回の記事で追加した「閉じるボタンで呼び出されるメソッド」に、テキストビューの位置を元に戻す処理を追加する。
allYの値が0より大きければテキストビューは移動されていると判断し、制約から被った長さをマイナスしている。
1 2 3 4 5 6 7 8 9 10 11 12 |
//閉じるボタンで呼び出されるメソッド func onClickCloseButton(sender: UIButton) { testTextView.resignFirstResponder() //テキストビューの位置を元に戻す。 if(allY > 0) { testConstraint.constant -= allY UIView.animateWithDuration(0.5, animations:{ self.view.layoutIfNeeded()}) } allY = 0.0 } |
結果、ViewController.swiftは以下のようになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
// // ViewController.swift // import UIKit class ViewController: UIViewController, UITextViewDelegate { @IBOutlet weak var testConstraint: NSLayoutConstraint! @IBOutlet weak var testTextView: UITextView! var allY:CGFloat = 0.0 override func viewDidLoad() { super.viewDidLoad() //ビューを作成する。 let testView = UIView() testView.frame.size.height = 60 testView.backgroundColor = UIColor.blueColor() //閉じるボタンを作成する。 let closeButton = UIButton() closeButton.setTitle("閉じる", forState:UIControlState.Normal) closeButton.backgroundColor = UIColor.redColor() closeButton.addTarget(self,action:"onClickCloseButton:", forControlEvents: .TouchUpInside) //ビューに閉じるボタンを追加する。 testView.addSubview(closeButton) //Autoresizingの変換をオフにする。 closeButton.translatesAutoresizingMaskIntoConstraints = false //ボタンの幅の制約を追加する。 testView.addConstraint(NSLayoutConstraint( item: closeButton, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .Width, multiplier: 0.0, constant: 70)) //ボタンの高さの制約を追加する。 testView.addConstraint(NSLayoutConstraint( item: closeButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 0.0, constant: 50)) //ボタンの右端とビューの右端を揃える制約を追加する。 testView.addConstraint(NSLayoutConstraint( item: closeButton, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: testView, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0)) //ボタンの上端とビューの上端を揃える制約を追加する。 testView.addConstraint(NSLayoutConstraint(item: closeButton, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: testView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0)) //キーボードにビューを追加する。 testTextView.inputAccessoryView = testView //テキストビューのデリゲート先にこのインスタンスを設定する。 testTextView.delegate = self //キーボードが現れるときに通知するメソッドを登録する。 NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeShown:", name: UIKeyboardWillShowNotification, object: nil) } //閉じるボタンで呼び出されるメソッド func onClickCloseButton(sender: UIButton) { testTextView.resignFirstResponder() //テキストビューの位置を元に戻す。 if(allY > 0) { testConstraint.constant -= allY UIView.animateWithDuration(0.5, animations:{ self.view.layoutIfNeeded()}) } allY = 0.0 } //キーボードが開くときの呼び出しメソッド func keyboardWillBeShown(notification:NSNotification) { //キーボードのフレームを取得する。 if let keyboardFrame = notification.userInfo![UIKeyboardFrameEndUserInfoKey]?.CGRectValue { //テキストビューの最大Y座標と、キーボードの最小Y座標の差を計算する。 let zureY = CGRectGetMaxY(testTextView.frame) - CGRectGetMinY(keyboardFrame) if (zureY > 0) { //テキストビューの垂直方向の位置の制約に差分をプラスする。 testConstraint.constant += zureY allY += zureY //アニメーションさせる。 UIView.animateWithDuration(0.5, animations:{ self.view.layoutIfNeeded()}) } } } } |
以下は実際のプレイ動画
次回から、Scroll Viewを用いて大きい部品をスクロールする方法について説明するが、その中でScroll VIewを用いてテキストビューを移動させる方法についても説明する。