こんにちは、共同開発部 iOS アプリ開発担当の図子です。
iOS 開発をしている皆さんはどういったタイミングで View のレイアウトをおこなっていますか?私は1年くらい前までは ViewController の viewWillAppar 等のメソッド内や、ViewController から View へ情報をセットするタイミングで ViewController 側から直接 View の frame やその View が持つ UILabel の frame を触っていたりしていました。これだと、想定外のタイミングでレイアウトが崩れたり、ViewController 側のコード量がふくれあがってしまいがちであまり良くないと思います。また、autoresizingMask を使うと楽ですが少し込み入ったレイアウトが必要になってくると autoresizingMask では対応できなくなってしまいます。そこで最近私がよくおこっている方法をご紹介します。
ある特定の表示領域を UIView のサブクラスとして実装します。そしてそのクラスの layoutSubviews メソッドをオーバーライドしてその中でレイアウトをおこないます。この UIView のサブクラスが持つセッター内で [self setNeedsLayout]; を呼ぶように実装しておけば、ViewController 側からは必要な情報をセットするだけで自動的に与えられた情報に応じてレイアウトが調整されるようになります。
- (void)layoutSubviews { UIButton *b = self.someButton; b.frame = CSCGRectVerticalAlignmentCenter(self.bounds, b.frame); descriptionView.frame = CSCGRectVerticalAlignmentCenter(self.bounds, descriptionView.frame); }
本題から脱線しますが上記例にある CSCGRectVerticalAlignmentCenter という関数は自前のセンタリング用関数です。縦方向へのセンタリングをおこないます。座標位置が浮動小数点になってしまうと描画がボケてしまうので roundf で丸めています。roundf を使っているのは CGRectIntegral では origin だけでなく size も変更してしまう場合があるのが好きじゃないからです。
CGRect CSCGRectVerticalAlignmentCenter(CGRect baseRect, CGRect targetRect) { targetRect.origin.y = roundf((CGRectGetHeight(baseRect) - CGRectGetHeight(targetRect)) / 2); targetRect.origin.y += baseRect.origin.y; return targetRect; }
layoutSubviews のオーバーライド時の注意点
本題に戻りますが、layoutSubviews をオーバーライドする際の注意点としては、このメソッド内ではレイアウト作業以外の操作はおこなわないようにすることです。layoutSubviews でレイアウト調整をおこなう方法は UIScrollView で特に威力を発揮するのですが、layoutSubviews の呼び出し頻度も高いです。呼び出し頻度が高いのでこのなかで重い処理は本来の目的とは違う処理を中に書いてしまうと動きが悪くなってしまいます。
今回紹介した方法を頻繁に使うようになってからは ViewController のコード量が減り見通しが良くなりました。また、変更が入った場合も該当箇所がわかりやすくなりますし、色々とメリットが多いと思っています。デメリットをあげるとするならサブクラス化するためにクラス数が多くなってしまうことでしょうか。これまであまり layoutSubviews を使っていなかった方は一度お試しください。