AutoLayoutでViewのサイズやUIレイアウトをパーセント指定する方法

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](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部品の幅または高さを、同じ値に揃えてくれるものです。

設定値の単位は、ポイントではなく、比率です。比率ですよ。大事なので２回言いました。つまり、パーセント！

このEqual WidthsコンストレイントまたはEqual Heightsコンストレイントを上手く使えばいいのです。

さて、あるクライアントから、かなり複雑な画面レイアウトを注文されたとしましょう。しかも、それはiPhone4SからiPhone6 Plusまで、全ての機種でレイアウトが乱れず一貫していること、という条件付き。

StoryBoardには、残念ながらJavaのSwingやJavaFXにあるような、高度なレイアウトクラスはありません。コンストレイントを駆使するしか無いのです。

ベースとなる考えは、画面をUIViewで区画分けすることです。

例えば以下の様な感じに。

![percentlayout.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/99c03fb0-5e78-3d66-3c13-5c089a892d1f.jpeg "percentlayout.jpg")

さて、上記の図のようなUIViewによる区画分けはどうやればいいんでしょうか？　ポイント固定でやったりしては、iPhone4SからiPhone6 Plusまでスケーラブルに対応できません。
ここで、Equal Widths/Equal Heightsコンストレイントの出番です。

ではまず、UIView「A」の領域を設定することにしましょう。しかし、Equal Widths/Equal Heightsコンストレイントを設定するには、同階層に存在する「もう片方」のUI要素が必要です。

もう片方？　そう、ここで出てくるのが「ダミーUIView」（勝手に命名）です。

まず、「ダミーUIView」となるUIViewをStoryBoardの画面上に配置してください。そして、以下のようにコンストレイントを設定します。

![スクリーンショット 2014-12-02 21.42.54.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/746bf637-43fe-9f54-7d54-377f7b0ee1ce.jpeg "スクリーンショット 2014-12-02 21.42.54.jpg")


上記のコンストレイントを設定すると、高さが画面の縦サイズ（注：ステータスバー除く）と同じで、幅がゼロ、つまり面積ゼロの超細長いUIViewが、画面左端（別に右端でもOKです）にくっついた状態となります。
このUIViewの名前を、後で識別しやすいように分かりやすく「VerticalCriterionView」と設定しましょう。

その名の通り、このダミーUIViewは、ダミーでありながら、縦の長さの基準（Criterion）という重要な役割を担うのです。

さて、次にUIView「A」を配置して、画面上端にくっつけましょう。UIView「A」を選択して、以下のコンストレイトを設定します。ここでは、「Update Frames」は「None」（コンストレイントは設定するが、描画は更新しない）にしておきます。

![スクリーンショット 2014-12-02 21.49.10.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/2f166606-5335-dcc2-4394-10a64772176f.jpeg "スクリーンショット 2014-12-02 21.49.10.jpg")

さぁそして、いよいよEqual Heightsコンストレイントの出番です。
「VerticalCriterionView」と、UIView「A」の両方を選択して、Equal Heightsコンストレイントを設定します。

![スクリーンショット 2014-12-02 21.46.21.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/d12236b1-f28b-022f-ef2d-48c23feb5361.jpeg "スクリーンショット 2014-12-02 21.46.21.jpg")

すると、UIView「A」が画面全体を覆ってしまいました（「Update Frames」を「Items of New Constraints」にしたため、描画の更新が行われた）。これは、Equal Heightsコンストレイントの設定値（Multiplier）のデフォルトが１であるためです。

つまり、ステータスバーを除いた画面の高さと同じ高さである「VerticalCriterionView」と、全く同じ高さがUIView「A」に設定されたわけです。だから、画面を覆い尽くしてしまったんですね。

![スクリーンショット 2014-12-02 21.55.57.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/3f269123-bc03-7675-2a91-fc526984e327.jpeg "スクリーンショット 2014-12-02 21.55.57.jpg")

さて、ここであわてず、XCode左側のビュー階層ペインから、Equal Heightsコンストレイントを選択します。

すると、XCode右側のプロパティパネルで、Equal Heightsコンストレイントの設定値（Multiplier）を変更することができます。


![スクリーンショット-2014-12-02-21.57.43.png](https://qiita-image-store.s3.amazonaws.com/0/12753/c09b6f01-2748-708e-c4c5-5bd596501da1.png "スクリーンショット-2014-12-02-21.57.43.png")

ここで、0.25を入力してみましょう。うまくいけば、綺麗にUIView「A」の高さが「VerticalCriterionView」（つまり、ステータスバーを除いた画面の高さ）の25%になるはずです。

もし、高さが縮むどころかもっと伸びてしまった場合は、下図のように、設定対象を入れ替えれば、ちゃんと25%になってくれます。

![スクリーンショット 2014-12-02 22.03.05.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/e77a4818-d3e7-afef-bff9-c9a5a10e8dd5.jpeg "スクリーンショット 2014-12-02 22.03.05.jpg")

さて、これで、まずUIView「A」の区画設定は完了ですね。

次にUIView「B」に移りましょう。もう、勘の良い読者なら、やり方はわかりますね。「VerticalCriterionView」とUIView「B」を選択して、Equal Heightsコンストレイントを設定、コンストレイント値を0.5に設定すればよいのです。

さて、今度はUIView「C」。これもAやBと同じようにしてもよいのですが、最後の１つは手を抜くことができます。単純に以下のコンストレイントを設定すれば良いのです。

![スクリーンショット 2014-12-02 22.06.29.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/fd54cc91-9955-ba80-71fa-623278f2b711.jpeg "スクリーンショット 2014-12-02 22.06.29.jpg")


AとBの比率が決まっているなら、残りのCのサイズは自ずと決まりますからね。

## 入れ子にしよう＆横バージョンに挑戦！

さて、今度はUIView「B」の中を、横に２分割しましょう。そのためには、UIView「B」の中に、新規UIViewを２つ配置することになります。それぞれ、UIView「B1」とUIView「B2」という名前だとしましょう。

勘の良い読者なら、横バージョンのやり方はもうわかりますね。

ダミーUIViewをまず最初に配置し（今度は横なので「HorizontalCriterionView」という名前にしましょう）、以下のようにコンストレイントを設定します。

![スクリーンショット 2014-12-02 22.09.42.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/4bf2b2a4-fd98-4cb1-fa57-c32898de882d.jpeg "スクリーンショット 2014-12-02 22.09.42.jpg")

そして、UIView「B」を配置。以下のコンストレイントを設定した後、

![スクリーンショット 2014-12-02 22.13.35.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/48669650-17b4-357c-dd2b-a0677f859194.jpeg "スクリーンショット 2014-12-02 22.13.35.jpg")

「HorizontalCriterionView」とUIView「B1」に対してEqual Widthsコンストレイントを設定。

![スクリーンショット 2014-12-02 22.15.00.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/3358f726-c01c-3d6d-a95d-c6339265199e.jpeg "スクリーンショット 2014-12-02 22.15.00.jpg")

コンストレイント値(Multiplier)を0.4に設定します。

![スクリーンショット 2014-12-02 22.17.13.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/58905ccb-13c0-ba9c-85e6-43defcbf8000.jpeg "スクリーンショット 2014-12-02 22.17.13.jpg")

UIView「B2」についても、同様にやってもいいんですが、また楽をしちゃいましょう。以下のようにコンストレイントを設定します。

![スクリーンショット 2014-12-02 22.06.29.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/fd54cc91-9955-ba80-71fa-623278f2b711.jpeg "スクリーンショット 2014-12-02 22.06.29.jpg")

これで、パーセント指定レイアウトの出来上がり！　iPhone4Sから6 Plusまで、各画面サイズでもスケーラブルにレイアウトが維持されていることを確認してみましょう。バッチリですね！

![スクリーンショット 2014-12-02 22.19.30.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/3e70a96f-ef35-199f-f1e5-b0eb31bd4e33.jpeg "スクリーンショット 2014-12-02 22.19.30.jpg")

## 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画面でも、「全機種対応で！」とか言われても、怖くない（？）ですね！