post Image
UINavigationControllerと座標ズレの小ネタ

画面遷移にUINavigationControllerを使うことは多いと思いますが、少し注意をしないとレイアウトが思い通りにならないことがあります。
自分の経験に基づいて、注意すべき点を2点あげます。

UINavigationController.navigationBar.isTranslucent

ナビゲーションバーを透過にするかのフラグです。と共に、ビューの開始位置を決めるフラグでもあります。標準ではtrueになっています。


追記: コメントで指摘をいただきました。@dokubekoさん、ありがとうございます。

バーのisTranslucentがビューの面積をバーの下までのばすトリガーではありません。

ビューの面積をバーの下まで伸ばすかどうかはビューコントローラのedgesForExtendedLayoutプロパティで指定します。これに関連し、バーが不透明な場合にもそうするかどうかをextendedLayoutIncludesOpaqueBarsで指定します。

isTranslucentがビューの面積の拡張のトリガーのようにみえるのは単にedgesForExtendedLayouttopが含まれてていてextendedLayoutIncludesOpaqueBarsfalseの状態で切り替えているからです。extendedLayoutIncludesOpaqueBarstrueにすれば、isTranslucentfalseでもビューはバーの下まで伸びます。

ビューの描画領域を伸ばすのはUIViewController.edgesForExtendedLayoutみたいです。こいつはデフォルトで.allになっています。
extendedLayoutIncludesOpaqueBarsはデフォルトでfalseになっています。

Storyboardだと、以下で変更できました。
スクリーンショット 2016-12-20 10.51.50.png

extendedLayoutIncludesOpaqueBarstrueにしてみると、たしかに描画範囲がナビゲーションバーのところまで広がります。
スクリーンショット 2016-12-20 10.59.50.png

edgesForExtendedLayoutの方は.top.bottomしか設定できないのですが、両方チェックがついている状態だと.allになります。
いずれか一つのみチェックをすると.topまたは.bottomになり、両方チェックを外すと一切拡張されなくなります。
.left.rightは一体・・・?また調べます。

とにかく、以下の記事は

edgesForExtendedLayout = .all
extendedLayoutIncludesOpaqueBars = false

という前提でお読みください。


例えばこんな画面を作ってみます。
スクリーンショット 2016-12-19 15.14.05.png
ナビゲーションバーに隠れるように’見えないはず’というラベルを置き、中心に’動かす’ボタン、下に’した’ラベルを置きました。
動かすボタンは以下のように、ナビゲーションバーを出したり隠したりします。

@IBAction func switchNavigationbarHidden(_ sender: UIButton) {

    let status = !self.navigationController!.isNavigationBarHidden
    self.navigationController?.setNavigationBarHidden(status, animated: true)
}

まずはisTranslucenttrueの状態で動かしてみます。
スクリーンショット 2016-12-19 15.19.13.png
ナビゲーションバーが表示されている状態では、’見えないはず’ラベルは見えません。

スクリーンショット 2016-12-19 15.19.16.png
ナビゲーションバーを隠すと、’見えないはず’ラベルが見えるようになりました。

以上から、isTranslucenttrueにすると次のようなことが起きます。

  • ナビゲーションバーが透過になります(例だとちょっとわかりにくいけど)
  • ViewControllerのビューの描画範囲は、ナビゲーションバーで隠れる部分を含めて画面全体になります

次に、isTranslucentfalseにしてみましょう。
画像ではStoryboard上でfalseにしていますが、もちろんコードからしても構いません。
スクリーンショット 2016-12-19 15.23.21.png

既にStoryboard上でも変化が起きています。先程はナビゲーションバーに隠されて見えなかった’見えないはず’ラベルが見えるようになりました。
さらに、’動かす’ボタンの位置も先程より少し下になりました。

実行してみます。
スクリーンショット 2016-12-19 15.26.05.png
ナビゲーションバーが表示されている状態は、Storyboardで見たとおりです。

スクリーンショット 2016-12-19 15.26.08.png
ナビゲーションバーを隠すと、’見えないはず’ラベルと’動かす’ボタンが少し上に移動しました!

以上から、isTranslucentfalseにすると次のようなことが起きます。

  • ナビゲーションバーが透過されなくなります
  • ナビゲーションバーが表示されている状態では、ViewControllerのビューの描画範囲が狭くなります
  • ナビゲーションバーを隠すと、その分ViewControllerのビューの描画範囲が広がります

個人で開発する分にはそこまで問題になることはないと思いますが、複数人で開発する際、ある人はtrue前提、またある人はfalse前提だったりすると、レイアウトがおかしなことになります。
開発を始める前にすり合わせをしておきましょう。

UIViewController.automaticallyAdjustsScrollViewInsets

スクロールできるビューを置いたときに、スクロール開始位置を自動的にずらしてくれるありがたい(場合によっては迷惑な)機能です。標準ではtrueになっています。

次はこんな画面を作ってみます。
スクリーンショット 2016-12-19 16.10.40.png
画面いっぱいにTableViewを置いて、そこに一つTableViewCellを追加しました。
TableViewはナビゲーションバーに隠れる部分まで領域があるのに、TableViewCellはナビゲーションバーに隠れること無く表示されています。これがタイトルの自動調整機能なのです。

試しに、automaticallyAdjustsScrollViewInsetsfalseにしてみましょう。
画像ではStoryboard上でfalseにしていますが、もちろんコードからしても構いません。
スクリーンショット 2016-12-19 16.16.47.png
TableViewCellがナビゲーションバーに隠されました。

この自動調節機能、別にナビゲーションバーに重ならないところにTableViewを配置しても、しっかり発動してくれます。
スクリーンショット 2016-12-19 16.20.01.png

こういうときはいらないのでfalseにしてしまいましょう。
スクリーンショット 2016-12-19 16.20.27.png

Storyboardで作業をしているときはautomaticallyAdjustsScrollViewInsetsの設定が目に見えますが、xibを作っているときなんかは要注意です。
ScrollViewを置いていて、contentSizeScrollViewframeより小さいはずなのに、何故かスクロールが発生するなんてときは、automaticallyAdjustsScrollViewInsetsが悪さしている可能性があります。

おわりに

今となっては困ることはなくなりましたが、勉強し初めの頃はこういった暗黙の了解的なものにだいぶ苦しめられました。
ネットで得られる断片的な知識だけで開発しようとするとこういった状況に陥りやすい気がします。
公式ドキュメントを読めばいいんですが、なかなか面倒ですよね・・・
とりあえず、UINavigationControllerを使うときは上記2点に気をつけましょう!


『 Swift 』Article List