前振り
Auto LayoutとSize Classesを有効にしたStoryboardにて、画面いっぱいのUIImageViewを作ろうとした際、UIViewControllerのviewにaddしているUIImageViewの制約を設定しようとすると左右-16ptがデフォルトで設定されてしまいました。
この-16ptを素直に設定しても見た目がおかしくなるわけではないし、0ptにする方法もあるんだけどなぜデフォルトで-16ptが必要なのかを調べたメモです。
謎の16ptについて説明されている記事
この文章で説明することは、下記のページで説明されているのでそれらを読んで分かるなら問題ないと思います。
SizeClassesとXcode6でのAutoLayoutの謎マージン
http://qiita.com/uskiita/items/c643f5868f60b496911e
What is “Constrain to margin” in Storyboard in Xcode 6
http://stackoverflow.com/questions/25807545/what-is-constrain-to-margin-in-storyboard-in-xcode-6
Layout attributes relative to the layout margin on iOS versions prior to 8.0
http://stackoverflow.com/questions/25261326/layout-attributes-relative-to-the-layout-margin-on-ios-versions-prior-to-8-0/
本題:上の記事でわからない人向けの説明
"constrain to margins"のチェックを外す
stackoverflowでは0ptにするための対応策として"constrain to margins"のチェックを外すとあり、0ptにしたいのであればこれが一番手っ取り早いです。
↑このスクリーンショットはデフォルトの状態としてチェックが入っていて-16ptとなっています
↑"Constrain to margins"のチェックを外すと0ptになる
これで-16ptというワケの分からない数字を見なくて良くなって解決です。
しかし、疑問なのは"Constrain to margins"は"マージンに制約を課す"という意味だとは思いますが、なぜこれのチェックがありデフォルト有効なのか、マージンとは何か、16ptなのは何故かという疑問でした。
ここまでの疑問まとめ
- "Constrain to margins"とは何?
- なぜデフォルト有効なの?
- 16ptなのはなぜ?
"Constrain to margins"とは何?
"Constrain to margins"とは何かを理解するために、UILabelに対してAuto Layoutを設定し"Constrain to margins"が有効でかつleftとrightを0ptの際の状態を見てみます。
UILabelの両端leftとrightに空白が有ります。
次にleftとrightの制約を-16ptにして位置を変更しみましょう。
↑UILabel(俺は『納得』したいだけだ!...省略)が横にピッタリとしています。このサンプルではそれほど違和感はありませんが、実際のアプリでこのようにUIViewControllerの左端や右端に対してぴったりとUILabelを置きたいという要件はあまりないと思います。
つまり、view全体にUIImageViewを表示しようしたら、viewにあるマージンからUIImageViewの距離は16ptになり、-16ptの制約を設定することになってしまうんですな。
そもそもなぜデフォルトで"Constrain to margins"のチェックが入っているの?
なぜデフォルトかはデフォルト動作としてふさわしい状態が、アプリの洗練さに繋がるからだと思います。
と当初は深く考えずに思っていましたが、もう少し整理しましょう。
Appleの意図としては下記のようなものがあるのではないかと最近は考えています。
- デフォルトで美しい配置それが"Constrain to margins"で0にしたものなのでそれに従って欲しい
- それを変更するなら横からのマージンを決めたらいいじゃない
ちょっと突飛な話ですが、"Constrain to margins"をチェックし0に設定するデフォルトは、将来的には横からのマージンで+16なのか+20なのかは同じViewでもiOSのバージョンによって決まるかもしれません。そういう意図があるのではないかと感じています。また、AutoLayoutのVisual Formatでも0を明示しなければこの謎の数値がマージンとして使われるのもその裏付けとなっています。
また、そう考えると"Constrain to margins"をチェックしたうえで、0ではない4とか5とか制約をするのはあまり良い方法ではなく、チェックを外し横いっぱいから設定するほうがベターかなと思います。
16ptなのはなぜ?
なぜ16ptにしなければいけないかというと、それがアプリの洗練さに繋がるためその値にしているだけで、大した意味は無いでしょう。きっと15ptだと少ないし、17ptだと多かっただけじゃないかと思います。
ちなみに、UIViewのプロパティでこの16ptを取得できます。
@property(nonatomic) UIEdgeInsets layoutMargins
UIViewのデフォルトでは{8, 8, 8, 8}となりますが、UIViewControllerのルートビューのみAuto Layoutが反映されるviewWillLayoutSubviews以降のタイミングでこの値が{0, 16, 0, 16}に変わるようです。
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"%@",NSStringFromUIEdgeInsets(self.view.layoutMargins)); //=>{8, 8, 8, 8}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%@",NSStringFromUIEdgeInsets(self.view.layoutMargins)); //=>{8, 8, 8, 8}
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
NSLog(@"%@",NSStringFromUIEdgeInsets(self.view.layoutMargins)); //=>{0, 16, 0, 16}
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
NSLog(@"%@",NSStringFromUIEdgeInsets(self.view.layoutMargins)); //=>{0, 16, 0, 16}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%@",NSStringFromUIEdgeInsets(self.view.layoutMargins)); //=>{0, 16, 0, 16}
}
(このタイミングは単純に好奇心で調べてみただけです)
View自体はちゃんとlayoutMarginsDidChangeメソッド後に{0, 16, 0, 16}になってますね。
@implementation SMView
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
NSLog(@"%@", NSStringFromUIEdgeInsets(self.layoutMargins));//=>{8, 8, 8, 8}
}
return self;
}
- (void)layoutMarginsDidChange
{
NSLog(@"%@", NSStringFromUIEdgeInsets(self.layoutMargins)); //=>{0, 16, 0, 16}
}