2013年10月25日金曜日

iOS7のナビゲーションバーを考慮した位置の模索

iOS7になって、画面領域がステータスバー(20px)も含むようになりました。
これにより、ナビゲーションバーの高さは、iOS6で44px, iOS7で64pxになります。

Storyboardで、Adjust Scroll View Insets にチェックがついている場合、
UITableViewやUICollectionViewのようなUIScrollViewを継承したものをViewの一番目に配置すると、コンテンツ領域が自動的にナビバーの下から表示されるようになります。
(Insetsが自動的に設定される)



Adjust Scroll View Insets のチェックを外すと、

こんな感じに。

UITableViewなどは、Storyboard上でも上手くレイアウトしてくれるのでデザインしやすいのですが、UIScrollViewを使った場合にはStoryboard上ではレイアウトしてくれない。

レイアウト時には、Storyboard上のView asの設定を 「iOS6.1 and Earlier」にすると NavigationBarがある状態でもY位置が0pxの位置からデザインができる
(他に良い方法があるのだろうか....?)

そもそも、縦スクロールはない とか、スクロールしてもナビゲーションバーの下に表示されなくてもいい のであれば、Storyboard上のViewControllerの設定で Under Top Bars のチェックを外せばOK。
0,0 の位置がナビゲーションバーの下に表示されるのでレイアウトしやすくなります。


多分、Under Bottom Bars はToolBarかな?

iOS6の時は44px...とコードに直接書きたくはないし、3.5inch/4inchで画面が違うときには...と考えたくもない。というわけで、Storyboard上でAutoLayoutを使いつつ頑張ってレイアウトする訳ですが、動的にUIScrollViewにViewを追加する場合もある。

AutoLayoutを使わず、Autosizingを使えば簡単にできそうと思う所も、AutoLayoutを使いたいのだ。という場面に まぁ なる。

AutoLayoutをプログラムで指定するには、NSLayoutConstraintをを作って追加します。

例:
        [contentView addConstraint:
         [NSLayoutConstraint constraintWithItem:customView
                                      attribute:NSLayoutAttributeWidth
                                      relatedBy:NSLayoutRelationEqual
                                         toItem:contentView
                                      attribute:NSLayoutAttributeWidth
                                     multiplier:1
                                       constant:0]];
        
        
        [contentView addConstraint:
         [NSLayoutConstraint constraintWithItem:customView
                                      attribute:NSLayoutAttributeTop
                                      relatedBy:NSLayoutRelationEqual
                                         toItem:contentView
                                      attribute:NSLayoutAttributeTop
                                     multiplier:1

                                       constant:0]];

        [contentView addConstraint:
         [NSLayoutConstraint constraintWithItem:customView
                                      attribute:NSLayoutAttributeHeight
                                      relatedBy:NSLayoutRelationEqual
                                         toItem:contentView
                                      attribute:NSLayoutAttributeHeight
                                     multiplier:1
                                       constant:0]];


UIScrollViewに動的にViewを追加した場合、ContentSizeを変更する必要があるが、このタイミングも間違えてはいけない。

UIViewController#loadView などでは、まだ正確なレイアウトはできていない。
loadViewで UIScrollView#setContentSize をしても、Adjust Scroll View Insetsの機能がその後に実装されて設定が有効にならない。

AutoLayout, Adjust Scroll View Insets が反映された後のタイミングは、
UIViewController#viewDidLayoutSubviews になりますので、ここで処理を入れます。

このタイミングで、UIScrollViewの情報を見ると、iOS7の場合だと
UIScrollView#contentInsets は {64, 0, 0, 0}
UIScrollView#contentOffset は {0, -64}
に自動的に設定されている。

ということで、UIScrollViewに設定するcontentSizeについてはInsetsの値を考慮した値にします。
たとえば、縦はScrollViewの高さと同じにしたい場合は、
scrollView.bounds.size.height - contentInsets.top
という形で。

contentOffsetを指定する場合も、 y = -64 というデフォルト値が入っていることを考慮するのを忘れずに。


iOS7から
UIViewControllerに、topLayoutGuide, bottomLayoutGuide のプロパティが追加されていて、topLayoutGuide の値に 64 が入っていました。

iOS6ではこのプロパティは使えないので、このプロパティがなかったら0(+NavigationBar)、あったらその値 という感じで、Contents領域の上の位置を取得する 感じで位置取得も可能そう..なのかな?



• • •