[Swift] UIKit 上で SwiftUI を使用時に navigationBarHidden が効かない場合に対処する


# はじめに

`SwiftUI`が登場してからだいぶ経ち、導入を試みているプロジェクトも増えているかと思います。

その際に **UIKit上でSwiftUIを参照する** 場面は増えてくるかと思います。
今回はそんな場面で遭遇した不具合に対処したお話です。

# 事象

## 1. 前提

`UIKit`上でナビゲーションを出し分けする場合、`setNavigationBarHidden`を使うことがあります。


```.swift
navigationController?.setNavigationBarHidden(true, animated: true)
```

これを画面の表示前後（`viewWillAppear`/`viewWillDisappear`）に入れることで

```.swift
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    navigationController?.setNavigationBarHidden(true, animated: true)
}
```

ナビゲーションの出しわけを実現していました。

## 2. 問題

`ViewController`の画面実装を`SwiftUI`に置き換えた場合、以下のようなコードになるでしょう。

```.swift
struct ExampleView: View {
    var body: some View {
        Text("example view")
    }
}
```

```.swift
final class ExampleViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let vc = UIHostingController(rootView: startRegistrationView)
        addChild(vc)
        view.addSubview(vc.view)
        vc.didMove(toParent: self)

        NSLayoutConstraint.activate([
            vc.view.topAnchor.constraint(equalTo: view.topAnchor),
            vc.view.leftAnchor.constraint(equalTo: view.leftAnchor),
            vc.view.rightAnchor.constraint(equalTo: view.rightAnchor),
            vc.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}
```

しかし、この場合「1. 前提」のコードが**うまく動作しません**。

つまり、以下のようなナビゲーションが出てしまった状態です。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3137353234322f37343865383665302d336237382d373131622d623766372d3430396433666334393461622e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/e41256d4-f1d7-7ccf-eaeb-11b6e39dffcc.png)





# 解決策

対応は**２つ行う必要**があります。
いずれかが欠けても、うまく動作しないので注意しましょう。

## ① SwiftUI 側での対応

`SwiftUI`では`navigationBarHidden`というものが用意されており、こちらをセットします。

```.swift
struct ExampleView: View {
    var body: some View {
        Text("example view")
            .navigationBarHidden(true)
    }
}
```

一見すると、こちらだけ解決しそうですが、**iOS14では動作するがiOS15では動作しない**という`SwiftUI`のバグがあり、こちらだけでは対応が不十分になります。

## ② UIKit 側での対応

今までは`viewWillAppear`/`viewWillDisappear`で処理を行なっていました。
これを`viewDidAppear`/`viewDidDisappear`に変更します。
**画面が出終わってから対応する**ことで反映させます。


```.swift
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    navigationController?.setNavigationBarHidden(true, animated: true)
}
```


もし、②しか実装しなかった場合、以下のように遷移から戻ってきた際に**一瞬ナビゲーションバーが見えてしまう現象**が発生します。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/3e7f3dbe-0ca5-b3c2-5aa9-f78ec1802801.gif">


①、② をいずれも対応することで、回避することができます。

※ ちなみに`animated`を`false`にすると遷移先で**動作しない**という罠もあるので注意が必要です


# 終わりに

`SwiftUI`はOS間で挙動の差が出やすいので、こういった細かいバグが発生しやすいです。
まだ見ぬバグを皆さんで踏み抜いていきましょう！

## 参考

https://stackoverflow.com/questions/58704688/unable-to-hide-the-navigationbar-when-embedding-swiftui-in-uikit