StoryBoardでiPadのレイアウトを定義する
AutoLayoutを使用してiPad用のレイアウトを定義するんですが、
SizeクラスがRegular/Regularしかないんですよね。
何が困るかっていうと、iPadでPortraitとLandscapeの区別がそのままではできないってことです。
Regular/Regular用にPortrait用とLandscape用の制約を設定する
Portrait用の制約とLandscape用の制約と両方StoryBoardで設定します。
ただし、そのままだと制約のコンフリクトが発生すると思うので、StoryBoard上では
Portrait用(またはLandscape用のどちらか)のみ有効にしておきます。
Portrait用とLandscape用の制約をOutletCollectionとして接続する
Portrait用とLandscope用と別々にOutletCollectionを用意しましょう。
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *portraitConstraints;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *landscapeConstraints;
あとは、StoryBoardからそれぞれの制約をポチポチと接続していきます。
PortraitとLandscapeの状態に応じて、制約を適用します。
viewWillLayoutSubviewsで制約を適用します。
iPadの時だけ切り替えるならこんな感じですね。
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular &&
self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
// iPad用にレイアウトを切り替え
for (NSLayoutConstraint *constraint in self.portraitConstraints) {
constraint.active = (UIApplication.sharedApplication.statusBarOrientation == UIDeviceOrientationPortrait);
}
for (NSLayoutConstraint *constraint in self.landscapeConstraints) {
constraint.active = (UIApplication.sharedApplication.statusBarOrientation != UIDeviceOrientationPortrait);
}
}
}
これで終わりかと思いきや・・・
実行してみるとレイアウト切り替え時に例外が発生することがありました。
ただし、例外が発生するのは最初の呼び出し時だけなんです。
しかも、制約に関連した条件があるっぽい。(詳細は不明)
状況としてはこれが近いです。
http://stackoverflow.com/questions/18060704/adding-view-programatically-with-auto-layout-gives-nsgenericexception-reason
どうやって解決するか?
viewが表示された後で、制約を適用するようにしました。
具体的にはviewDidAppearでフラグを設定します。
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// 表示完了状態とする
self.didAppear = YES;
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular &&
self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
if (self.didAppear == NO) {
return;
}
// iPad用にレイアウトを切り替え
for (NSLayoutConstraint *constraint in self.portraitConstraints) {
constraint.active = (UIApplication.sharedApplication.statusBarOrientation == UIDeviceOrientationPortrait);
}
for (NSLayoutConstraint *constraint in self.landscapeConstraints) {
constraint.active = (UIApplication.sharedApplication.statusBarOrientation != UIDeviceOrientationPortrait);
}
}
}
起動した直後にPortraitとLandscapeを切り替える
StoryBoardではPortrait用の制約を有効にしていると思うので、
起動直後はPortraitでレイアウトされてしまいます。
最終的にこういうコードにしました。
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// 表示完了状態とする
self.didAppear = YES;
// iPad用にPortrait/Landscape切り替え通知
[self.view setNeedsUpdateConstraints];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular &&
self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
if (self.didAppear == NO) {
// Portraitでレンダリングされるのを非表示にしておく
self.view.hidden = YES;
return;
}
// 表示が完了したら、viewを表示
self.view.hidden = NO;
// iPad用にレイアウトを切り替え
for (NSLayoutConstraint *constraint in self.portraitConstraints) {
constraint.active = (UIApplication.sharedApplication.statusBarOrientation == UIDeviceOrientationPortrait);
}
for (NSLayoutConstraint *constraint in self.landscapeConstraints) {
constraint.active = (UIApplication.sharedApplication.statusBarOrientation != UIDeviceOrientationPortrait);
}
}
}
viewDidAppearでsetNeedsUpdateConstraintsを呼び出して、制約の更新が必要だということを伝えています。
その後、viewWillLayoutSubviewsで制約を切り替えますが、起動直後にPortrait用のレイアウトが表示されるので、レイアウトを適用するまではviewを一旦非表示にしています。
ひとまず、これで切り替えることができました。