1. emadurandal
Changes in body
Source | HTML | Preview
@@ -1,170 +1,170 @@
## 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部品の幅または高さを、同じ値に揃えてくれるものです。
設定値の単位は、ポイントではなく、比率です。比率ですよ。大事なので2回言いました。つまり、パーセント!
この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)のデフォルトが1であるためです。
つまり、ステータスバーを除いた画面の高さと同じ高さである「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「B」に移りましょう。もう、の良い読者なら、やり方はわかりますね。「VerticalCriterionView」とUIView「B」を選択して、Equal Heightsコンストレイントを設定、コンストレイント値を0.5に設定すればよいのです。
さて、今度はUIView「C」。これもAやBと同じようにしてもよいのですが、最後の1つは手を抜くことができます。単純に以下のコンストレイントを設定すれば良いのです。
![スクリーンショット 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」の中を、横に2分割しましょう。そのためには、UIView「B」の中に、新規UIViewを2つ配置することになります。それぞれ、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画面でも、「全機種対応で!」とか言われても、怖くない(?)ですね!