2015/3/1更新:
「必見!」とかドヤァしておきながら、なんか凄い抜けたことしていたみたいで、
文中で説明しているダミーUIViewを使わなくても、普通に親のViewとEqualWidth/EqualHeightできたみたいです。失礼しました。指摘してくださった方、ありがとうございます。詳しくはコメント欄をご覧ください。
(コメントには書きましたが、親Viewのサイズがちょっとそのままでは使いたくない値だった場合は、ダミーUIView作っていいと思います)
iOS開発者・UIデザイナー必見!
iOSのStoryboardのAutoLayout。
UIコンポーネントの幅・高さはポイント数値指定しかできない。パーセント指定できないこともないけど、それにはコードでコンストレイントをいちいち記述しないといけない。
こんな風に↓
http://saveme-dot-txt.blogspot.jp/2014/03/percentage-based-layouts-using-mostly.html(SaveMe.txt様より)
そう思っていた時期がボクにもありました。
いや、でも実はできちゃうんですよ。Storyboard上だけで幅・高さのパーセント指定が。
そのコツをお伝えしましょう。
ダミーのUIViewとEqual Widths/Equal Heightsコンストレイントを使え!
Storyboard上で設定できるコンストレイントに、Equal WidthsとEqual Heightsがあることをご存知ですか?
これは何かというと、二つ以上のUI部品を選んだ状態でのみ設定できるコンストレイントで、選んだ複数のUI部品の幅または高さを、同じ値に揃えてくれるものです。
設定値の単位は、ポイントではなく、比率です。比率ですよ。大事なので2回言いました。つまり、パーセント!
このEqual WidthsコンストレイントまたはEqual Heightsコンストレイントを上手く使えばいいのです。
さて、あるクライアントから、かなり複雑な画面レイアウトを注文されたとしましょう。しかも、それはiPhone4SからiPhone6 Plusまで、全ての機種でレイアウトが乱れず一貫していること、という条件付き。
StoryBoardには、残念ながらJavaのSwingやJavaFXにあるような、高度なレイアウトクラスはありません。コンストレイントを駆使するしか無いのです。
ベースとなる考えは、画面をUIViewで区画分けすることです。
例えば以下の様な感じに。
さて、上記の図のようなUIViewによる区画分けはどうやればいいんでしょうか? ポイント固定でやったりしては、iPhone4SからiPhone6 Plusまでスケーラブルに対応できません。
ここで、Equal Widths/Equal Heightsコンストレイントの出番です。
ではまず、UIView「A」の領域を設定することにしましょう。しかし、Equal Widths/Equal Heightsコンストレイントを設定するには、同階層に存在する「もう片方」のUI要素が必要です。
もう片方? そう、ここで出てくるのが「ダミーUIView」(勝手に命名)です。
まず、「ダミーUIView」となるUIViewをStoryBoardの画面上に配置してください。そして、以下のようにコンストレイントを設定します。
上記のコンストレイントを設定すると、高さが画面の縦サイズ(注:ステータスバー除く)と同じで、幅がゼロ、つまり面積ゼロの超細長いUIViewが、画面左端(別に右端でもOKです)にくっついた状態となります。
このUIViewの名前を、後で識別しやすいように分かりやすく「VerticalCriterionView」と設定しましょう。
その名の通り、このダミーUIViewは、ダミーでありながら、縦の長さの基準(Criterion)という重要な役割を担うのです。
さて、次にUIView「A」を配置して、画面上端にくっつけましょう。UIView「A」を選択して、以下のコンストレイトを設定します。ここでは、「Update Frames」は「None」(コンストレイントは設定するが、描画は更新しない)にしておきます。
さぁそして、いよいよEqual Heightsコンストレイントの出番です。
「VerticalCriterionView」と、UIView「A」の両方を選択して、Equal Heightsコンストレイントを設定します。
すると、UIView「A」が画面全体を覆ってしまいました(「Update Frames」を「Items of New Constraints」にしたため、描画の更新が行われた)。これは、Equal Heightsコンストレイントの設定値(Multiplier)のデフォルトが1であるためです。
つまり、ステータスバーを除いた画面の高さと同じ高さである「VerticalCriterionView」と、全く同じ高さがUIView「A」に設定されたわけです。だから、画面を覆い尽くしてしまったんですね。
さて、ここであわてず、XCode左側のビュー階層ペインから、Equal Heightsコンストレイントを選択します。
すると、XCode右側のプロパティパネルで、Equal Heightsコンストレイントの設定値(Multiplier)を変更することができます。
ここで、0.25を入力してみましょう。うまくいけば、綺麗にUIView「A」の高さが「VerticalCriterionView」(つまり、ステータスバーを除いた画面の高さ)の25%になるはずです。
もし、高さが縮むどころかもっと伸びてしまった場合は、下図のように、設定対象を入れ替えれば、ちゃんと25%になってくれます。
さて、これで、まずUIView「A」の区画設定は完了ですね。
次にUIView「B」に移りましょう。もう、勘の良い読者なら、やり方はわかりますね。「VerticalCriterionView」とUIView「B」を選択して、Equal Heightsコンストレイントを設定、コンストレイント値を0.5に設定すればよいのです。
さて、今度はUIView「C」。これもAやBと同じようにしてもよいのですが、最後の1つは手を抜くことができます。単純に以下のコンストレイントを設定すれば良いのです。
AとBの比率が決まっているなら、残りのCのサイズは自ずと決まりますからね。
入れ子にしよう&横バージョンに挑戦!
さて、今度はUIView「B」の中を、横に2分割しましょう。そのためには、UIView「B」の中に、新規UIViewを2つ配置することになります。それぞれ、UIView「B1」とUIView「B2」という名前だとしましょう。
勘の良い読者なら、横バージョンのやり方はもうわかりますね。
ダミーUIViewをまず最初に配置し(今度は横なので「HorizontalCriterionView」という名前にしましょう)、以下のようにコンストレイントを設定します。
そして、UIView「B」を配置。以下のコンストレイントを設定した後、
「HorizontalCriterionView」とUIView「B1」に対してEqual Widthsコンストレイントを設定。
コンストレイント値(Multiplier)を0.4に設定します。
UIView「B2」についても、同様にやってもいいんですが、また楽をしちゃいましょう。以下のようにコンストレイントを設定します。
これで、パーセント指定レイアウトの出来上がり! iPhone4Sから6 Plusまで、各画面サイズでもスケーラブルにレイアウトが維持されていることを確認してみましょう。バッチリですね!
UIViewだけでなく、他のUI部品にも有効
見出しですでに書いちゃってますが、このパーセント指定法はUIViewだけでなく、UILabelやUIButton、UIImageViewなど、ほとんどのUI部品に適用可能です。
フォントサイズはどうする?
さて、レイアウトはStoryboard上のみでパーセント指定できましたが、UILabelなどのフォントサイズは、各機種の画面サイズによってスケーラブルにする方法はないのでしょうか?
残念ながら、今のところはないようです。こればっかりは、コード上でフォントサイズを計算してスケールさせるしかありません。
私の場合は、StoryBoard上でUILabelなどのフォントサイズを、iPhone5の画面サイズを前提に設定しています。
それらのUILabelなどをコード側にアウトレット接続し、
-(void)viewWillAppear:(BOOL)animated
などの関数の中で、フォントサイズをスケールさせています。
具体的には、以下の様な感じです。
- (CGFloat)scaleFontSize
{
if ([UIScreen mainScreen].bounds.size.Heights >= 700) { // 縦が700以上だったら、iPhone 6 Plus の標準解像度設定とみなす
return 1.29375; // (iPhone6 Plusの画面横幅)/(iPhone5の画面横幅)
} else if ([UIScreen mainScreen].bounds.size.Heights >= 650){ // 縦が650以上だったら、iPhone 6 の標準解像度設定とみなす
return 1.171875; // (iPhone6の画面横幅)/(iPhone5の画面横幅)
}
return 1.0;
}
-(void)viewWillAppear:(BOOL)animated {
CGFloat scale = [self scaleFontSize];
self.titleLabel.font = [UIFont systemFontOfSize:25.0f * scale];
}
うーん。Storyboard上で同様のことができるような、もっと良い方法ないですかねぇ? ご存知の方いたら教えて下さい。
ちなみに、UIImageViewなどをコード上でスケールさせたい場合は、UIViewのtransformプロパティとCGAffineTransformMakeScale関数を使う方法がお勧めです。
さいごに
さて、いかがだったでしょうか。
上記の方法による、縦と横のパーセント指定と、UIViewの入れ子を駆使すれば、大抵のレイアウトは実現可能です。
これで、どんな複雑なUI画面でも、「全機種対応で!」とか言われても、怖くない(?)ですね!