[Swift 4] ナビゲーションバーの裏にUIが潜り込む問題についての対応 [StoryBoard未使用]

Swift4 + iOSの勉強を初めて見たところ、何やらナビゲーションバーにViewが隠れてしまい色々と苦戦したため、解決方法をメモっておきます。

注：iOS初心者なため、情報に不備がある可能性があります。御了承お願いいたします。
また、この実装は**Storyboardを一切使わず**にコードのみで実装しています。

（Storyboardの削除については[こちら](https://qiita.com/rika-tawashi/items/d975c2d9f85e8bb4aef5)の生地参考にさせて頂きました）

# 求めていた結果
1. ナビゲーションバー表示時：ナビゲーションバー直下に正方形のViewを配置
2. ナビゲーションバー非表示時：ステータスバー直下に正方形のViewを配置
<br><br>
<img src="https://qiita-image-store.s3.amazonaws.com/0/85904/b083cb0e-7848-7666-e8aa-c9c322e7dfed.png" width=30%>
<img src="https://qiita-image-store.s3.amazonaws.com/0/85904/bb1cd609-ce1c-2e41-dd77-63094e0c9187.png" width=30%>

# 実際の結果
1. ナビゲーションバー表示時：ナビゲーションバーの**裏**に正方形のViewが配置される
2. ナビゲーションバー非表示時：ステータスバーの**上**に正方形のViewを配置される
<br><br>
Viewの表示レイヤーが同じではない原因は今回調べていませんが、結果として**端末の画面の一番上の座標**に表示されているようでした。
<br>
<img src="https://qiita-image-store.s3.amazonaws.com/0/85904/628065b7-2944-6395-9372-927093a64047.png" width=30%>
<img src="https://qiita-image-store.s3.amazonaws.com/0/85904/dbdcffed-0907-e2cb-16b9-4c0aa921eaa9.png" width=30%>

# 原因
1. 大前提として、Storyboardを使用していない。（全てコードで記述している）
2. `View.frame`の`y`座標に`0`を指定している。
3. `self.edgesForExtendedLayout = UIRectEdgeNone;`の対応を入れていない。


**※3番についての補足**
どうやらiOS7以降ではナビゲーションバーにViewが隠れてしまう現象は発生しているようで、軽く調べたところ以下の対応をする方が多いようでした。
`self.edgesForExtendedLayout = UIRectEdgeNone;`

ただ[ここ](https://qiita.com/yimajo/items/254c7cebab7864678246)にもあるように上記対応にも良く無い所があるようでした。
ので、今回は別の方向（ゴリ押し）で対応してみています。

**失敗時の実装内容**

``` swift:AppDelegate.swift
    var window: UIWindow?
    var navController: UINavigationController?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        self.window = UIWindow(frame: UIScreen.main.bounds)

        navController = UINavigationController(rootViewController: ViewController())
        // Manually show UINavigationBar
        navController?.isNavigationBarHidden = true
        self.window?.rootViewController = navController
        self.window?.makeKeyAndVisible()
        
        return true
    }
```

``` swift:ViewController.swift
// Setup view
view.backgroundColor = UIColor.lightGray
title = "ViewControllerTitle"

let label = UILabel()
// ここのYが0になっているため、画面最上部に配置される
label.frame = CGRect(x: 0, y: 0, width: viewHeight() / 4, height: viewHeight() / 4)
label.backgroundColor = UIColor.brown
label.text = "Square"
label.textColor = .white
label.textAlignment = .center
view.addSubview(label)
```

``` swift:ViewControllerExtension.swift
extension ViewController {
    func viewWidth() -> CGFloat { return self.view.frame.width }
    func viewHeight() -> CGFloat { return self.view.frame.height }
}
```

# 対応方針
`edgesForExtededLayout`や`Constraints`など色々と便利な設定があるようでしたが、iOS初心者な自分は**アナログでシンプル**な方法で進めています。
また、Android開発時はデフォルトでステータスバー、アクションバーの下にViewが設置されており、ここを意識しながら開発することに違和感があるため、なるべくここを意識せず開発できるよう実装してみました。

**やったこと**
1. `StatusBar`, `UINavigationBar`の高さを取得し、**手動で**Viewの座標を`UINavigationBar`以下に設置する。
2. 毎回設定するのはだるいので、各`UIViewController`で一度だけ上記設定を行い、以降は意識せずにすむようにする。
3. `UINavigationBar`の表示ステータスによって自動的に配置を調整する。

**実装内容**
（`AppDelegate.swift`は変更なしなため、省略）

``` swift:ViewController.swift
// Setup view
view.backgroundColor = UIColor.lightGray
title = "ViewControllerTitle"

// 各種UIの親Viewを、フルスクリーン（UINavigationBar, StatusBarにかぶらないように）定義する。
// 他のViewは全てcontentsの子Viewとして登録する。
let contents = UIView()
contents.frame = CGRect(
    x: 0,
    y: !isNavBarHidden() ? statBarHeight() + navBarHeight() : statBarHeight(),
    width: viewWidth() - 10, // -10 just to check if boundary is set as expected.
    height: viewHeight(includeNavBarHeight: !isNavBarHidden()))
contents.backgroundColor = UIColor.red
// Add contents to view.
view.addSubview(contents)

let label = UILabel()
// labelの設定は変更なし
label.frame = CGRect(x: 0, y: 0, width: viewHeight() / 4, height: viewHeight() / 4)
label.backgroundColor = UIColor.brown
label.text = "Square"
label.textColor = .white
label.textAlignment = .center
// Add all other views to contents view.
contents.addSubview(label)
```

``` swift:ViewControllerExtension.swift
extension ViewController {
    func viewWidth() -> CGFloat { return self.view.frame.width }
    func viewHeight(includeNavBarHeight: Bool = true, includeStatBarHeight: Bool = true) -> CGFloat {
        return self.view.frame.height - (includeStatBarHeight ? statBarHeight() : 0) - (includeNavBarHeight ? navBarHeight() : 0)
    }
    
    func navBarWidth() -> CGFloat { return self.navigationController?.navigationBar.frame.size.width ?? 0 }
    func navBarHeight() -> CGFloat { return self.navigationController?.navigationBar.frame.size.height ?? 0 }
    
    func statBarWidth() -> CGFloat { return UIApplication.shared.statusBarFrame.width }
    func statBarHeight() -> CGFloat { return UIApplication.shared.statusBarFrame.height }
    
    func isNavBarHidden() -> Bool { return self.navigationController?.isNavigationBarHidden ?? true }
}
```

# 結果
上記対応を行うことで、無事`UINavigationBar`表示時、及び非表示時に求めていたレイアウトを表示することができました。
かなりアナログでゴリ押しな設定ですが、どの設定をいじれば良いか分からないという方の参考に少しでもなれば幸いです。