Swiftで遊ぼう! - 204 - フィリングを使ってレイアウト調整(Auto Layoutのまとめ)
- Swiftで遊ぼう!の前書き-> Life-LOG OtherSide
- 初心者はここから!-> 50オヤジでもできるiOS開発
- 私の本業、オフィシャルなブログ-> Life-LOG
オートレイアウトの調整
2020年8月⒉日:SwiftUI開発環境になり、AutoLayoutを使ってUIを調整する必要は無くなりました。Size ClassはUIデザインに必要なので、次のリンクページにSize Classに関する情報をまとめておきます → Swiftで遊ぼう! - 249 - iOSの座標システム: 2020年8月
2016年11月17日:オートレイアウト機能はXcode 8になってかなり強化されたようですが、まだ勉強不足で対応できていません。ここの内容はXcode 7の内容です。画面サイズのテーブルのみ変更*1
ストーリーボードでAuto Layout(オートレイアウト)のまとめ
2015年4月21日追記:以下のフィリング(スペーサー)を使うレイアウト調整に加えて、マジックナンバー(Maginc Number)という概念を知りました。「Swiftで遊ぼう! - 282 - Autolayoutの実習 - Magic Number」も読んで下さい。
Beginning iPhone Development with Swift 3: Exploring the iOS SDK
- 作者:Maskrey, Molly,Topley, Kim,Mark, David,Olsson, Fredrik,LAMARCHE, JEFF
- 発売日: 2016/11/18
- メディア: ペーパーバック
2015年11月30日:下記の内容は確かにiOS8向けの内容です。しかし、Autolayoutの基本的な扱いはiOS9になっても同じです。iOS9で強化された機能は「Stack」で、説明は、Swiftで遊ぼう! - 365 - Developing iOS Apps (Swift) Build a Basic UI - Swiftで遊ぼう! on Hatenaにしてあります。
-
-
- ここからオリジナル
-
今までにボタン、スライダー、テキストフィールド、セグメント・コントロールなど基本的なUIViewControllerの勉強をしてきました。それぞれのオブジェクトにコンストレイントというルールを与えることでサイズの異なるiOSデバイスに対応する方法も学びました。
このセクションで、スクリーンサイズの違い、特にポートレイト(縦長)とランドスケープ(横長)への切り替え(ローテーション)に対応してレイアウトを調整する方法を学んでいきます。
まず、スクリーンのサイズに関して情報の整理をします。
2016年11月17日:最新のデバイスに対応させました
出典:http://yataiblue.ashita-sanuki.jp/e809537.html
画面サイズは、上記のように物理サイズとソフトサイズ、そしてスケーリング値が設定されています。
物理サイズの単位はピクセル(pixels)ですが、高精細なレチナ・ディスプレイが登場したことで文字の可読性を維持するためにソフトサイズという仮想サイズが用意されました。単位はポイント(points)です。スケーリング値が(×1)だったら1ポイントは実際の1ピクセルと一致します。しかし、スケーリング値(×2)なら2×2ピクセルが1ポイントです。
iPhone 6 plusのスケーリング値(×3)の場合は3×3ピクセルが1ポイントになっています。
写真やイメージをピクセルレベルで扱わない開発者が画像の縮尺を意識しないために用意されたのがソフトサイズです。
ポートレイトの場合、画面上部を占めるステータスバー(キャリアのシグナルやバッテリーインディケータ)の幅、20ポイントも考慮する必要があります。
画面の向き、iPhoneでみれば、縦長がポートレートで、横長がランドスケープですが、この向きの切り替え制御に関して知っておかなければならないことが2つあります。
まず、iOSデバイスの向きをサポートをするかどうかです。プロジェクト・ナビゲータからプロジェクト自身にある、「General(ジェネラル)」を選びます。「Device Orientation(デバイス・オリエンテーション)」という項目で、サポートする向きを選択します。この設定はアプリケーションでサポートするかどうかと別問題なので注意が必要です。
Device(デバイス)がUniversal(ユニバーサル)になっているので、デフォルト設定で既に「Upside Down」のチェックボックスは外れています。デバイスタブを切り替えて確認してください。iPhoneでUpside Downは外れていて、iPadでは4つすべてにチェックがついています。iPhoneの場合、通話をするため、ひっくり返った状態を許すとユーザーが混乱するからオフになっています。
何度も言うけど、このチェックマークがある向きだけアプリケーションで対応できるので注意してください。
ミニプロジェクト1 Rotations
新しいプロジェクト「Rotations」、ターゲットデバイスを「Universal」にして作ります。Main.storyboardを選択して、オブジェクト・ライブラリから「Label(ラベル)」をドラッグして画面の真ん中上方に設置します。このとき画面の中央を意味するブルーの点線が出ているところで上方に設置します。ラベルをダブルクリックしてタイトルを「こっちが上」に変えます。
必ずしなければならないのが、コンストレイントの設定です。ラベルを「Ctrl + ドラック」して上方に引っ張るとView画面が青くなります。そこでボタンをリリースさせるとポップアップメニューが出てくるので、「Shift」キーを押して、「Top Apace to Top Layout Guide」と「Center Horizontally in Container」の2項目を選びます。すると水平と垂直の2つのルールが設定されました。
「ラン(Cmd + R)」します。
iOSシュミレータ上なら、Cmd + 右矢印、もしくは左矢印で画面をローテーションさせることができます。iPhoneのシュミレーターなら真下に向けても反応しないでしょう。しかし、iPadをシュミレートしていたらどの向きに向けてもラベルは上方正中に位置しています。
アプリケーションでローテーションの制御
画面の向きをコントロールするのはこれだけなんだと最初は思いました。しかし、よく考えてみると、アプリケーションで向きを細かく設定できる自由さは必要です。
次にViewControllerを使って向きを制御する方法が知っておかなければならない2つ目の項目です。
プロジェクト・ナビゲータでViewController.swiftを選択します。
そして、次のコードを入力します。
override func supportedInterfaceOrientations() -> Int { return Int(UIInterfaceOrientationMask.Portrait.rawValue) | Int(UIInterfaceOrientationMask.LandscapeLeft.rawValue) }
このUIViewControllerのメソッドを使ってViewConrollerに対応する向きを持たせます。ポートレイト(UIInterfaceOrientationMask.Portrait.rawValue)、「|」(OR:もしくはという意味)、ランドスケープ左向き、これは左辺を上にする時計回転に回す向き(UIInterfaceOrientationMask.LandscapeLeft.rawValue)だけに制限するという意味です。「ラン(Cmd + R)」して確かめるといいでしょう。
UIInterfaceOrientationMaskで使いたい向きを指定するんです。
この方法で指定できる向きは他にも色々あるので覚えておきます。
UIInterfaceOrientationMask.Portrait UIInterfaceOrientationMask.LandscapeLeft UIInterfaceOrientationMask.LandscapeRight UIInterfaceOrientationMask.PortraitUpsideDown UIInterfaceOrientationMask.Landscape UIInterfaceOrientationMask.All UIInterfaceOrientationMask.AllButUpsideDown
ミニプロジェクト2 Layout
Xcode6でSingle View Applicationの新しいプロジェクト「Layout」を作ります。ターゲットデバイスは「Universal」です。
Main.storyboardを選択して、オブジェクト・ライブラリからLabelを探します。←検索ボックスにLabelとタイプすると候補が絞られてきます。
これをView上の左上の隅にドラッグして、HIG(ヒューマン・インターフェイス・ガイドライン)で規定されている青い点線が現れる位置でドロップします。
次にそれを「Opt + ドラッグ」(オブジェクトのコピー)して右上隅、さらに左下、最後に右下隅の合計4つのラベルを設置します。各ラベルをダブルクリックして下のイメージのように名前を変えます。名前を変えるとラベルの長さが変わり、HIGの規定から外れてしまうので位置を微調整します。
ここでいきなりiPhone6シュミレーターを起動「Cmd + R」させます。
レイアウトが崩れています。右側に設置したラベルが見えません。このまま左にローテート(Cmd + →)させると、
今度は下に設置したレベルが見えなくなりました。
今まで何度も説明したようにオブジェクトにレイアウトのルール(コンストレイント)を与えていないから生じたレイアウトの崩れです。
インターフェイス・ビルダー(IB)はオブジェクトにコンストレイントをビジュアルに与えることができるので便利です。
既にオブジェクトをHIGで規定されたガイドライン上に設置している(ルール付けはまだです)ので、これをルールにしてしむ機能を持っているので説明しましょう。
ラベルを1つ選んでから、「Shift」キーもしくは、「Cmd」キーを押して、他の3つのラベルを選択していって4つのラベルがすべて選択された状態にします。
メニューから「Editor > Resolve Auto Layout Issues > Add Missing Constraints」を選びます。
すべてのオブジェクトに青い実線が付きます。
青い実線と青い点線の違いを理解する必要があります。青い実線は問題の無いコンストレイントが設定された状態で、青い点線はHIGに準拠したガイドライン上の位置にあるという意味です。
もう一度シュミレーターをラン(Cmd + R)します。
今度はちゃんとすべてのラベルが思っているように並んでいるでしょう。
左にローテート(Cmd + →)すると、
これも問題なく表示されました。
ここまでのまとめですが、Viewの四隅に4つのラベルを置いて、オートレイアウト機能を使って自動的に「Missing Constraints」を加えました。HIGに準じた位置にラベルを置いているので、IBは推測をしてコンストレイントを自動的につけてくれますが、コンストレイントの種類を確認する方法があります。
左上隅の「UL」ラベルを選択して、サイズ・インスペクタを開きます。
具体的なコンストレイントが3つ確認できます。このコンストレイントですが、ラベルの置き方で微妙に変わることがあるので、この図と異なるかもしれません。
この場合の「UL」ラベルの位置決めをを説明すると次のようになります。
- 「UR」ラベルの下辺(Baseline)で整列
- 「UR」ラベルの上方スペースを、View上端から同じ距離に保つ(HIG準拠の距離)
- 「LL」ラベルの左辺(Leading)で整列
このコンストレイントは「UL」ラベルに設定されているものです。実際は4つのラベルそれぞれにコンストレイントが設定されているので、絡み合って、上下左右の位置決めをしていることになります。
次に、新しいラベル「Left」ラベルを左側、垂直線上の中心、ちゃんと青い点線が現れたところに置きます。
メニューから、「Editor > Align > Vertical Center in Container」を選ぶと、垂直線の中心に置くというコンストレイントが加わります。
コンストレイントを加えましたが、オレンジラインが出現しました。
オレンジラインはコンストレイントが足りないということです。垂直方向の真ん中というコンストレイントは加わったものの、他のオブジェクトとの位置関係がルールづけられていないので補填しなければなりません。
やるべきことは、「Editor > Resolve Auto Layout Issues > Add Missing Constraints」です。
次に「Right」ラベルを右側に置きます。
すべきことは「Left」ラベルと同じです。
メニューから「Editor > Resolve Auto Layout Issues > Add Missing Constraints」を選びます。
後でオブジェクオを加えてもコンストレイントを加えて調整してやればレイアウトは崩れません。
次に「UL」ラベルと「UR」ラベルに注目します。
ラベルの幅を広げるのですが、分かり易いようにバックグラウンドの色を変更します。
ラベルを選択してアトリビュート・インスペクタからバックグラウンドを選んで好きな色にします。ここでは黄色に設定しています。
「UL」ラベル右辺にあるサイズコントロールを画面の中心近くまでドラッグして広げます。その後に「UR」ラベルの左辺リサイズコントロールを中心近くまでドラッグしていけば、途中で「UL」ラベルと同じ幅になったことを明示する青い点線(ガイドライン)が出現します*2
オブジェクト間でのコンストレイントを設定します。
メニューから「Editor > Pin > Horizontal Spacing」を選ぶ*3と、
storyboard上では綺麗に並んで見えるのですが、ラン(Cmd + R)をさせると、
上手くいきません。
これを解決するのもコンストレイントの追加です。
選択したオブジェクトの幅を一定にするルールを与えます。
メニューから「Editor > Pin > Widths Equally」を選びます。
オブジェクトにコンストレイントが足りなければ、警告のオレンジラインが出てくることがあるので、メニューから「Editor > Resolve Auto Layout Issues > Update Frames」を選んでからラン(Cmd + R)します。
このように問題があればコンストレイントを追加していけばいいんです。
ミニプロジェクト3 Restructure
今まではレイアウトの基本事項でした。ポートレイトからランドスケープへ変化させてもレイアウトに大きな変化は生じ無かったのですが、現実的に考えるとiPhoneのポートレイトに適したインターフェイスをランドスケープに切り替えて問題にならないということはないでしょう。またiPhoneでデザインされたインターフェイスがそのままiPadで問題無いなってこともないと思います。
サイズやデバイスの違いに合わせてインターフェイスを再構築(リストラクチャ)させる方法を学んでいきます。
Xcodeで新しいプロジェクトを作ります。Single View Applicationに「Restructure」という名前をつけ、ターゲットデバイスを「Universal」にします。
Main.storyboardを選択して、オブジェクト・ライブラリからViewを選びます。ここで検索ボックスにViewと入れるとView Controllerが出てくるので、目的のViewが見つかりにくいので、UIViewをタイプするといいでしょう。
ViewをドラッグしてデフォルトViewの上に重ねるとサイズは画面一杯に広がってしまうので、リサイズコントロールを使って画面の3/4サイズまで縮めます。分かりやすくするために背景に色をつけます。アトリビュート・インスペクタからBackgroundの色を黄色に変更します。
オブジェクト・ライブラリからボタンを選んで画面下部に置いて、タイトルをダブルクリックして「Action One」にする。そのボタンをControl + ドラッグしてコピーしたボタンを3つ作って、名前をそれぞれ「Action Two」「Action Three」「Action Four」に変更させて以下のように配置します。
4つのボタンの設置は大まかでかまいません。位置はコンストレイントで決定していくからです。Action OneとAction Twoのボタン列のグループと、Action ThreeとAction Fourのボタン列グループがコンテイナービューの端から同じぐらいの距離に置くようにします。
コンストレイントの設定
黄色いビューを選択して、右下のPinボタンを選び、オブジェクトの上と左右の位置決めをします。Constrain to marginsのチェックボックスにマークが入っていることを確認して、上下左右に伸びている赤い点線の上と左右の点線をクリックして実線に変えます。すると下にAdd 3 Constraintsというボタンが出てくるので押します。
まだ、黄色いビューにオレンジラインは残っています。黄色いビューと下部の位置関係が未設定のためです。下に並んでいるボタンのコンストレイントを決めてからボタングループとの間にアンカー(錨)をつけていく予定です。
Action OneとAction Twoボタンを選択して、AlignボタンからHorizontal Centersを選び、Add One Constraintボタンを押します。同じ作業をAction ThreeとAction Fourボタンにも反映させます。
次にActon Twoボタンを使って、左端と下端との位置決めをします。Action Oneボタンとの位置を決めていくために選択して、Pinボタンを選択、Constrain to marginsのチェックが入っているのを確かめてから、上、左、下の赤い点線をクリックして実線に変えてAdd 3 Constraintsボタンを押します。再び同じことをAction Fourボタンに加え、上、右、下の点線を選んで同様の操作をします。
最後に、黄色いビューと下にあるボタン群の間隔を決めていきます。黄色いビューをCtrl + ドラッグしてAction Oneボタンが選択されたところで離すとポップアップウィンドウが出るので、この中からVeritcal Spacingを選ぶことで、すべてのコンストレイントが決まりました。
もし、オレンジラインが残っているようなら、Editor > Resolve Auto Layout Issues > Update Frameを選びます。
早速ラン(Cmd + R)してポートレイトで見ると、
これを横にしてランドスケープにすると、
ちゃんとレイアウトに破綻はなく、スクリーンサイズにマッチしています。
しかし、マッチはしているけど、決して見栄えがいいとは言えませんよ。
ランドスケープにすると、黄色いViewの上下がかなり狭まり、ボタンは妙に大きく見えます。
できるたけ、Viewを崩したくないので、ボタンは右端のスペースに縦に並べてしまうほうがいいでしょう。
じゃあどうすればいいか? 答えはSize Classesです。
Size Classes
Size Classesは、Main.storyboardの一番下にある 「wAny hAny」というラベルに隠れています。ここをクリックすると次のようなポップアップが出てきます。
ここでコンストレイントのセットを作る事ができるんです。
グリッドで区切られたセルは、異なる水平(横幅)サイズと垂直(縦幅)サイズの組み合わせを意味しています。
サイズクラスは、異なるデバイスの横と縦のサイズを大まかに分類したもので、Compact(コンパクト)とRegular(レギュラー)があり、AnyはワイルドカードでCompact、Regularどちらにもなりえます。
この組み合わせを表にすると以下のようになります。
CompactはRegularより小さいという印象を持てばいい程度で、かなり大胆なクラス分けをしている印象ですが、注目すべき特徴が2つある。
- ポートレイトで見れば、iPhoneはすべて、横がCompactで縦がRegularなのは納得しやすい。どのiPhoneでも縦が明らかに長いからです。しかし、ローテーションしてランドスケープになるとこのサイズが縦も横もCompactになります。横がRegularになればわかりやすいのですが、iPhone 6 PlusのみRegularとして扱います。
- iPadは、ポートレイトでもランドスケープでも横縦Regularのままなので、ローテーションによる画面の切りかえでコンストレイントのセットを変えるのにsize classだけで対応できません。しかし、iPadの画面サイズはポートレイトでもランドスケープでもあまり変化はなく、多くの場合問題にならないのです。
リファレンスとして見やすいように図示すると、
実際のsize classesを使っていきます。
プロジェクト「Restructure」のMain.storyboardを選択すると。画面下のsize classesは「wAny hAny」になっています。
これはbase design(ベースデザイン)と呼ばれて、すべてのデザインの出発点になります。カスタマイズしてレイアウトを増やしていくには、まずベースデザインを最初に作る必要があります。
iPhone(iPhone 6 Plus以外)ランドスケープのレイアウトのサイズクラスは、wCompact hCompactですが、iPhone 6 Plusは、wRegular hCompactです。iPhoneに共通のレイアウトにするため、ワイルドカードのAnyを使って、hAny wCompactを選択します。
画面に変化が生じました。
下部のツールバーの色が変わり、ベースデザインでないことが強調されています。Viewも縦長のランドスケープに変化しました。
このサイズクラスでできることは以下に挙げる項目です。
デザインを完全に作り変えることはできないようです。限られてはいるがレイアウトの調整は大胆にできるし、この変化は、このサイズクラスを持っているオリエンテーションにしか影響を与えません。
hAny wCompactサイズ・クラスから設定されているコンストレイントをクリアします。
メニューから「Editor > Reslove Auto Layout Issures > Clear Constraints」を選びます。間違えてもベースデザインから消さないように注意が必要です
サイズクラスからコンストレイントがすべて削除されるとDocument Outline(ドキュメントアウトライン)にあるコンストレイントはグレーアウトされます。
このようなグレーアウトの意味は、ベースデザインにコンストレイントは残っているが、現在設定を変えているサイズクラスに存在しないということです。
サイズクラスをhAny wAnyベースデザインに戻してみるとすべてのコンストレイントが復活します。。
hAny wCompactサイズクラスに戻して、黄色いビューのリサイズをします。
そのまま左端に動かすと、ボタンが隠れてしまうので、ボタンが確認できるまで黄色いビューを小さくします。
黄色いビューを左に設置してから、4つのボタンを右側の空いている空間に、適当な間隔を開けて縦に並べます。
コンストレイントを決めて行く前に、ルールを言葉で表現します。「黄色いビューを左側に設置して、ボタンのために右側に固定サイズ幅のスペースを空けてやる。このスペースに4つのボタンを水平中央の列で並べて、それぞれのボタンは等間隔に開ける。」
実は、この内容に合うコンストレイントは存在しないので、1つずつ考えていく必要があります。
- まず黄色いビューを選んでからPinボタンをクリック。ポップアップメニューからConstrain to marginsのチェックを外す*4。それから、上、左、下の赤い点線をクリックして実線に変え、距離をそれぞれ20と入力してAdd 3 Constraintsボタンを押す。
- 黄色いビューの右辺の位置が決められていないので、ビューをControl + ドラッグして右方向に動かしてバックグラウンドビューが青くなったところでボタンをリリースするとポップアップメニューが出てくので、Trailing Space to Constainer Marginを選ぶ。コンテイナーのマージンというのは背景のアウトラインで、トレーリングというのは右辺になります*5。これで右側のスペースが画面のサイズにかかわらず同じ横幅を維持できます。
- 右の空いているスペースに等間隔でボタンを並べるのですが、レイアウトのためのテクニックがあります。一人で考えているとなかなか思いつかないかもしれません。これは先人の知恵として覚えておきましょう。見えない同一サイズのフィリング(filler) でボタンの間を埋めてやる方法です
- UIViewをオブジェクト・ライブラリからドラッグして黄色いビューに重ねて作ってからリサイズして小さくする。高さを10ピクセル以下にする*6。幅50ピクセル高さ10ピクセルして、アトリビュート・インスペクタから背景をグレーに変えて見やすくさせる。
- 最初に作ったフィリングをAction Oneボタンの上に移動させます。このときフィリングの上端が黄色いビューの上端より上にならないようにします*7。次はOpt + ドラッグ(コピー)してAction OneとAction Twoの間に設置、そしてコピーを続けて5つのフィリングをボタンの間に設置します。最後のフィリングは下端が黄色いビューの下端より下にならないようにします。また、フィリングとボタンも重ならないように気をつけます。ボタンの境界が分かりにくければ、Editor > Canvas > SHow Bounds Rectanglesを使うのもいいでしょう。境界線が出現します。
- フィリングを全て選択してから、Pinボタンを押してポップアップメニューからEqual WidthsとEqual Heightsを選ぶと、Add 8 Constraints*8ボタンを押す。すると、このフィリングはすべて同じ高さ同じ幅で変化します。1つのボタンの条件を変えると、他の4つのボタンも追従して変化します。
- 1番上のフィリングを選択して、Pinボタンのポップアップメニューから1番上の左右のサイズを0にして赤い点線を実線に変え、Add 2 Constraintsボタンを押す。左右のオブジェクトとの距離を0にすることで黄色いビューの右端からコンテナビューの右端まで隙間なく埋めることができました。
- フィリングの条件は整ったので、全てのフィリングとボタンを選んでからAlignボタンのHorizontal Centersを選び、Add 8 Constraintsボタンを押せば、右隅のスペースの中央にボタンが整列します。
- 最後に垂直方向に隙間無くフィリングを埋めるステップとして、5つのフィリングを選択して、Pinボタンのポップアップメニューから、Constrain to marginsのチェックボックスを外し、上下のサイズを0にして、点線を実線に変えて、Add 10 Constraintsボタンを押す。これで上下のスペースも埋まりました。
- すべて条件が入った状態で、ごちゃごちゃした線が入っています。本当に条件が上手くいっているかどうか分かりにくいので、メニューからEditor > Resolve Auto Layout Issues > Update Framesを選びます。
- 上手くいっていました。ランの前にフィリングを消す必要があるので、5つのフィリングを選んでアトリビュート・インスペクタから、hiddenチェックボックスにマークを入れます。すると画面から消えます。
ランして画面をローテーションして確かめてみます。
これでXcode6で実装されたAuto Layout機能の説明は終わりです。
*1:2015年11月30日:iOS9対応の内容なんでリンク先も参照してください。
*2:2015年1月27日原稿執筆時は青い点線が出てきませんでした。2015年4月10日加筆時にXcode6のバージョンが変わっているので、できるようになっているかもしれません。この時は同じサイズにするためにサイズ・インスペクタを使いました。
*3:水平方向に並んだオブジェクトの前(Leading)後(trailing)のスペースを固定させるという意味です。
*4:私の使っている教科書でチェックマークを外すように指示はあるが、なぜ外さなければならないのでしょうか?マージンから距離を保つルールであろうが、隣のオブジェクトから距離を保つルールでも、隣にオブジェクトが無ければ問題ないとおもうんですけど...←理由がわかる人教えてください。
*5:左辺はLeading(リーディング)ですね。これは英語的な表現なので覚えるしかない。
*6:この理由は分からないが、小さい方がフィリング間での位置決めのガイドラインが出やすいからかもしれない←オブジェクトに比べて明らかに小さくする必要がある
*7:幅の設定をする時に左右のオブジェクトから大きさを決定するからです。黄色いビューより上に設置してしまうと左のオブジェクトが無くなってしまう...からです