【Swift】Scroll Viewを使って入力欄とキーボードが被らないようにする方法。(Swift 2.1、XCode 7.2)

2020年6月16日

この記事で説明すること

過去の記事で、キーボードが出現したときに、部品の制約を変更してテキストビューをキーボードと被らないように移動する方法について説明した。⇒「記事

本記事ではScroll View(以下、スクロールビュー)を用いてキーボードと被らないようにする方法を説明する。制約を変更する方法と考え方が同じところもあるので、まだ読んでない方は先に読んでおくことをお勧めする。

 

これから作るもの

下図のように320×1000ピクセルの縦長の画像を配置して、スクロールビューでその画像を縦方向に移動できるようにする。

画像をアップしておくので、実装を試す人はご利用されたし。⇒「テスト用画像

スクロールビューの中に1つのText Field(以下、テキストフィールド)と2つのテキストビューを配置する。以降はこの3つの部品を「入力欄」と表現する。キーボードが出現したときに入力欄と被ってしまう場合、被っている長さぶん部品が上にずれる仕組みを実装する。画面が全体的にスクロールされるイメージだ。

完成イメージ

部品を準備する

デバイス画面にスクロールビューを配置する(下図赤枠)。黄緑枠のPinボタンを押して吹き出しの設定画面を表示させて、Viewに対する上下左右の距離に0を入力する(紫枠)。このとき、Constrain to margins のチェックを外すのと、Viewからの距離になるように「▼」ボタンを押してViewにチェックを入れること。

入力したら「Add 4 Constraints」ボタンを押す。これで、スクロールビューの大きさが画面サイズと一致する制約が追加された。

デバイス画面にScroll Viewを配置

 

次に画像ファイルをプロジェクトに追加する(下図赤枠)。追加する方法は次の記事を参照されたし。⇒「プロジェクトにファイルを追加する

イメージビューをスクロールビューの中に配置する(下図青矢印)。イメージビューが選択された状態のまま黄緑枠のアトリビュートインスペクタボタンを押して設定画面を表示し、Imageに画像ファイル名を入力する(sample_img.png)。

紫枠のPinボタンを押して吹き出しの設定画面を表示し、スクロールビューに対する上下左右の距離をすべて0に設定する。WidthとHeightにチェックを入れ、Widthに320、Heightに1000を入力し、「Add 6 Constraints」ボタンを押す。これでイメージビューをスクロールして見るための制約が追加された。

イメージビューを配置する

 

今回はスクロール範囲の下のほうにテキストビューを配置するので、画面を見やすくするためにストーリーボード上の画面サイズを変更しよう。

下図赤枠のView Controllerをクリック、黄緑枠のサイズインスペクタボタンを押して設定画面を表示する。Simulated Sizeを「Freedom」、Heightを「1000」に変更する。これで、ストリーボード上の画面サイズが縦長になった。なお、この変更はXcodeで縦長に見えるだけで、実機の表示には影響しない。

デバイス画面の表示サイズを変更

 

下図紫枠の「View」をクリック、黄緑枠のResolveボタンを押して吹き出しの設定画面を表示する。設定項目の「Update Frames」を選択する。これで、画像が制約どおりの表示になった。

表示を更新

 

次に、テキストフィールドをスクロールビューの中に配置する(下図赤矢印)。テキストフィールドを選択したまま、紫枠のPinボタンを押して吹き出しの設定画面を表示する。黄緑枠の距離の制約に上「520」、左「30」、右「30」を入力する(下の制約は追加しない)。

Heightにチェックを入れ、「30」であることを確認して、「Add 4 Constraints」ボタンを押す。これでテキストフィールドの配置完了。

テキストフィールドをデバイス画面に配置する

 

次にテキストビューをスクロールビューの中に配置する(下図紫矢印)。テキストビューを選択したまま、赤枠のアトリビュートインスペクタボタンを押して設定画面を表示しTextのサンプル文章を削除する。

黄緑枠のPinボタンを押して吹き出しの設定画面を表示し、水色枠の距離の制約に上「40」、左「30」、右「30」を入力する(下の制約は追加しない)。Heightにチェックを入れ、「270」を入力して「Add 4 Constraints」ボタンを押す。

1つ目のTextViewを追加

 

次に2個目のテキストビューをスクロールビューの中に配置する(下図黄緑矢印)。テキストビューを選択したまま、紫枠のアトリビュートインスペクタボタンを押して設定画面を表示しTextのサンプル文章を削除する。

赤枠のPinボタンを押して吹き出しの設定画面を表示し、水色枠の距離の制約に上「40」、左「30」、右「30」を入力する(下の制約は追加しない)。Heightにチェックを入れ、「80」を入力して「Add 4 Constraints」ボタンを押す。

TextViewをデバイス画面に追加

 

これで、部品の配置が完了した。ここで一旦動作確認をしよう。

 

部品とソースコードのコネクションを確立する

次に下図赤枠のアシスタントエディタボタンを押してViewController.swiftを開く。デバイス画面にあるテキストフィールドをドラッグ&ドロップでソースコードまで運び、吹き出しの設定画面を表示させる。Connectionに「Outlet」、Nameに「testTextField」を入力し、Connectボタンを押す。

テキストビューとスクロールビューも同様にドラッグ&ドロップでソースコードに運び、テキストビューのNameは「testTextView1」と「testTextView2」、スクロールビューのNameは「testScrollView」でコネクションを確立する。これで部品をソースコードから操作できるようになった。

部品とソースコードのコネクションを確立

 

次に、テキストフィールドをドラッグ&ドロップでソースコードに運び吹き出しの設定画面を表示させる。Connectionに「Action」、Nameに「endInputTextField」、Typeに「UITextField」、Typeに「Did End on Exit」を設定し、Connectボタンを押す。これで、テキストフィールドのキーボードはReturnキーを押すと閉じられるようになった。

TextFieldはアクションも接続する

 

キーボードに「閉じるボタン」を追加する

次にテキストビューをタップしたときに表示されるキーボードに「閉じるボタン」を追加しよう。

ViewController.swiftを開き、以下のように変更する。「閉じるボタン」を追加する理由の詳細は次の記事を参照されたし。⇒「記事

 

ここまでで動作確認をしておく。テキストフィールドはReturnボタン、テキストビューは「閉じるボタン」でキーボードが閉じられるようになった。

 

スクロールビューの移動を実装する。

キーボードと入力欄の被る被らないの判定は、前回記事と同じように入力欄の最大距離がキーボードの最小距離より大きい場合に被っていると判断する。

しかし前回記事と同じ判定方法を用いる場合、1つ留意点がある。下図のように、テキストフィールドに対するframeプロパティの距離はビューの最上端からの距離になり、一方、キーボードのフレームは画面上端からの距離となることだ。なので、判定にはスクロールビューの現在位置を考慮して計算する必要がある。

キーボードとの距離

 

具体的には、以下のコードのように「部品の最大距離  − スクロールビューの現在Y座標  − キーボードのY座標」を判定材料とし、被ると判定された場合は、スクロールビューの現在のY座標に被る距離を加算してスクロールさせる。

 

「キーボードが開くときの呼び出しメソッド」は3つの部品から共通で呼ばれるメソッドとして実装する。共通といえども、タップされた部品のY座標を計算に使うので、メソッドが呼ばれたときにどの部品がタップされたのかを知る必要がある。

そこで、下図のデリゲートメソッドを追加し、編集が始まる前に編集部品のインスタンスをクラス変数に格納しておく。「キーボードが開くときの呼び出しメソッド」が呼び出されたらこのクラス変数を利用して計算する。

 

キーボードが閉じられるときの呼び出しメソッドも3つの部品で共通にする。

 

ちなみに、スクロールビューのY座標を変更するときに、スクロールビューのcontentInsetプロパティの値にも被った距離を設定している。contentInsetプロパティとは、スクロール可能な範囲を増減できるプロパティである。

このプロパティを変更しておかないと、以下の動画のように、画面下部でテキストビューをタップしたときにスクロール可能範囲を超えるためにスクロールがうまくできない事象が発生する。

 

最終的にViewController.swiftは以下のコードになった。

 

以下は実際のプレイ動画