前回のつづき。

最初にViewController + xibの組み合わせの代わりにViewController + Storyboardの組み合わせを推奨しました。最後にViewController + xibの問題点を説明したいと思います。

サンプルコード: https://github.com/stack3/iOS7FullScreenViewSamples

サンプルコードのSTFromXibViewController.xibを開いてください。

01

 

一番上の青いLabelは、Superviewの上端、左右端との間隔20pxのConstraintが設定されています。

03

 

04

 

上から二番目の赤いLabelは、Superviewの上端64px、左右端20pxのConstraintが設定されています。

05

 

06

 

一番下の青いLabelは、Superviewの下端0px、左右端20pxのConstraintが設定されています。

07

 

08

 

一番下から二番目の赤いLabel、Superviewの下端44px、左右端20pxのCostraintが設定されています。

09

 

Top Layout Guide、Bottom Layout Guideを使っていないのでNavigationBar、Toolbarに重なってしまうのではないかということに気づいた人もいると思いますが、そのとおりです。

from xib with NavigationBar and Toolbarを選択します。NavigationControllerにpushされて、NavigationBarとToolbarが表示されています。

13

 

想像通り青いLabelはNavigationBarとToolbarに隠れてしまいました。ただし赤いLabelはうまく表示されています。これはConstraintの働きにより、上の赤いLabelは上端から64px(StatusBar + NavigationBarの高さ)に表示され、下の赤いLabelは下端から44px(Toolbarの高さ)に表示されるからです。

しかし横画面ではこのように赤いLabelとNavigationBar、Toolbarの間に余白ができてしまいます。

14

これは横画面ではNavigationBarとToolbarの高さが変わってしまうからです。

xibファイルではLayout Guideの指定ができない

STFromXibViewController.xibファイルを開いてください。

一番上の青いLabelを選択しPinボタンを押して、Top SpaceのConstraintのプルダウンを押します。

15

Storyboardの時は表示されたTop Layout Guideがありません。

次は一番下の青いLabelを選択しPinボタンを押して、Bottom SpaceのConstraintのプルダウンを押します。

16

こちらもStoryboardの時は表示されたBottom Layout Guideがありません。

ちなみにStoryboardの場合、左サイドバーのViewの上にTop Layout Guide、Bottom Layout Guideの表示があります。

17

xibファイルの場合、Viewの上にLayout Guideの表示はありません。

18

つまりxibのViewはLayout Guideに対応していないようです。そもそもLayout GuideはViewControllerの管轄なので当然といえば当然です。(UIViewControllerのプロパティとしてtopLayoutGuide、bottomLayoutGuideが定義されています)。

edgesForExtendedLayoutを使う方法

UIViewController + xib(View)とUIViewController#edgesForExtendedLayoutを組み合わせて、NavigationBarやToolbarと重ならないようにする方法もあります。

サンプルのMainからRectEdgeNone with NavigationBar and Toolbarを選択しましょう。青いラベルが縦画面、横画面でもNavigationBar、Toolbarと重ならないようになっています。

20

21

STRectEdgeNoneViewControllerのviewDidLoadでedgesForExtendedLayoutにUIRectEdgeNoneを設定しています。

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.title = @"RectEdgeNone";
    self.edgesForExtendedLayout = UIRectEdgeNone;
}

こうするとNavigationBar、Toolbarと重ならないようにViewが配置されます。

NavigationBarとToolbarの背景がグレーっぽくなっているのに気づいたでしょうか?Viewと重ならなくなったせいで、後ろのwindowの背景色(黒色)が透けて、このような色合いになっています。解決するためには、AppDelegateのapplication:didFinishLaunchingWithOptions:などでwindowの背景色を白にします。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window.backgroundColor = [UIColor whiteColor];
    return YES;
}

ただし、edgesForExtendedLayout = UIRectEdgeNoneにしても、NavigationBarが表示されない場合は、StatusBarと重なってしまいます。

サンプルコードのMain画面からRectEdgeNoneを選択すると、それを確認できます。

19

iOS 7以前のものをうまく動かすために、edgesForExtendedLayoutを使うのも1つの手だと思いますが、iOS 7以降のみ対応するなら使うことはないでしょう。

xibファイルにViewControllerを配置した時の問題

xibにもStoryboard同様にViewControllerを配置できます。この場合は、Layout Guideが表示されます。ただし以下の様な問題があります。

xibのViewControllerをオブジェクト化するときは以下のようにします。

NSString *nibName = NSStringFromClass([self class]);
UINib *nib = [UINib nibWithNibName:nibName bundle:nil];
UIViewController *con = [[nib instantiateWithOwner:nil options:nil] objectAtIndex:0];

一見うまくオブジェクト化されるように見えるのですが、このような順番でメソッドが呼ばれます。

  • UIViewController#awakeFromNib
    • UIViewController#viewDidLoad(awakeFromNibの中で呼ばれる)
  • 配置したSubviewのawakeFromNib

本来はSubviewのawakeFromNibでSubviewの初期化を行い、ViewControllerのviewDidLoadでSubviewに上書き的な設定を行う流れが望ましいはずです。

Storyboardからオブジェクト化した時は、

  • UIViewControllerのawakeFromNib
  • 配置したSubviewのawakeFromNib(UIViewController#awakeFromNibから抜けた後に呼ばれる)
  • UIViewController#viewDidLoad

の順で呼ばれます。望ましい流れです。

ちなみにUIStoryboard#instantiateInitialViewController、instantiateViewControllerWithIdentifierでViewControllerをオブジェクト化したときは、viewはまだ生成されていません。ViewControllerをallocでプログラムからオブジェクト生成した場合も、まだviewは生成されていません。しかし、xibのViewControllerをUINib#instantiateWithOwnerオブジェクト化したときは、viewも生成されます。この動作も好ましくないように思えます。上記のようなメソッドの呼び出し順になるのも、この辺りが理由ではないでしょうか。

いずれにせよ、xibにUIViewControllerを配置してオブジェクト化する方法は良くなさそうです。

以上、長くなりましたが、結論として最初に述べたようにStoryboardとAuto Layoutを使うがiOS 7でのベストアンサーだと思います。