ナビゲーションバーのカスタマイズ一覧(iOS)

## はじめに

ナビゲーションバーをカスタマイズする方法を毎回忘れて実装に時間がかかるため、まとめました。

## 環境

- OS：macOS Big Sur 11.1
- Xcode：12.4 (12D4e)
- Swift：5.3.2
- iOS：14.4

## ナビゲーションバーのカスタマイズ一覧

### ナビゲーションバーのタイトルに画像をセットする

`UINavigationItem.titleView` に画像をセットすることで、ナビゲーションバーのタイトルを画像にできます。

```MonsterIndexListViewController.swift
final class MonsterIndexListViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let imageView = UIImageView(image: UIImage(named: "navi_zukan"))
        imageView.contentMode = .scaleAspectFit
        self.navigationItem.titleView = imageView
    }

}
```

|テキスト|画像|
|:--|:--|
|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 20.32.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/3a14dc48-3223-aa5e-86eb-6ecc271db5c1.png)|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 20.30.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/3ce5e329-3be8-b73c-5bf9-eff7a2c6ed20.png)|

`UIImageView.contentMode` を `.scaleAspectFit` にすると、画像の縦横比を変えずに表示できます。

### ナビゲーションバーの戻るボタンの画像を変更する

`UINavigationBar` の `backIndicatorImage` と `backIndicatorTransitionMaskImage` を変更することで、戻るボタンの画像を変更できます。

```swift:MonsterIndexListViewController.swift
final class MonsterIndexListViewController: UIViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        let image = UIImage(named: "index_icon_back")
        self.navigationController?.navigationBar.backIndicatorImage = image
        self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = image
    }

}
```

|画像未変更|画像変更|
|:--|:--|
|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 19.09.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/14341d93-b9a5-4e45-56da-80f7fffc3f17.png)|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 19.34.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/dadf6cd9-60d8-3f55-f5cb-dd0ec7446bf3.png)|

セットした画像は以下であり、 `tintColor` が反映されてしまいます。
![icon_back.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/8d784762-7ffd-d388-cf3c-261d02dd85c3.png)

`tintColor` を反映させない方法がわからなかったので、ご存じの方がいたら教えていただけると嬉しいです。

2021/03/23, 追記
[コメント](https://qiita.com/uhooi/items/61445e9284c0ddc77659#comment-0482c3e10b430fe74e6a) で教えていただきました。

対象画像のRender Asを `Default` から `Original Image` に変えると `tintColor` が反映されなくなります。
![スクリーンショット_2021-03-23_19_40_17_after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/33fb66dc-e744-412b-70a3-ffc07cc7ec93.png)
![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-23 at 19.39.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/5cea78da-e63c-23f3-bc2b-94b9dd777c9e.png)

ただ画像の位置が上に寄るのは直りません。
画像のサイズを調整すればかんたんに対応できます。
参考: https://sarunw.com/posts/how-to-change-back-button-image/#position

`UIImage` の位置を変える方法もありますが、 `tintColor` が反映されるのと、 `UIImageView` の位置は変わらないので見切れることがあるため、ベターな方法ではなさそうです。
参考: https://stackoverflow.com/questions/29445644/vertically-center-backindicatorimage-in-swift

### ナビゲーションバーの戻るボタンのテキストを非表示にする

iOS 14以上の場合、  __遷移元のビューコントローラー__ の 
`UINavigationItem.backButtonDisplayMode` を `.minimal` にすることで、戻るボタンのテキストを非表示にできます。

```swift:MainViewController.swift
final class MainViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.navigationItem.backButtonDisplayMode = .minimal
    }

}
```

|表示|非表示|
|:--|:--|
|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 19.09.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/e96a733a-6a2c-5d00-33a8-9e4e1a298d2b.png)|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 19.09.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/12c1b726-fdb7-5796-5e52-f57a0df2b64f.png)|

戻るボタンの長押し時にビューコントローラーの `title` が表示されるので、戻るボタンを非表示にする場合でもセットするのが望ましいです。

```diff_swift:MainViewController.swift
final class MainViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
+       self.title = "メイン"
        self.navigationItem.backButtonDisplayMode = .minimal
    }

}
```

|タイトル未セット|タイトルセット|
|:--|:--|
|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 19.17.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/01570acb-c048-5b7c-b4be-d16459a8d33c.png)|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 19.11.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/6999b30d-3b53-dd02-eb13-f2a49615fde0.png)|

戻るボタンのタイトルに半角スペースをセットする方法もよく見ますが、iOS 14以降では戻るボタン長押し時のメニューにタイトルが表示されなくなるので望ましくありません。

### ナビゲーションバーの戻るボタンの色を変更する

`UINavigationBar` の `tintColor` を変更することで、戻るボタンの色を変更できます。

```swift:MonsterIndexListViewController.swift
final class MonsterIndexListViewController: UIViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.navigationController?.navigationBar.tintColor = UIColor(named: "tekimon_khaki")
    }

}
```

|色未変更|色変更|
|:--|:--|
|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-23 at 20.18.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/d2fd2b11-88d5-48f3-a714-a96b73c9152f.png)|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-23 at 20.21.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/ce3052f1-29d2-fc01-3df8-ed451efe1a5f.png)|

一部のビューコントローラーのみナビゲーションバーの戻るボタンの色を変更する場合、戻し忘れに気をつけてください。

戻すには `nil` をセットします。

```swift:MainController.swift
final class MainViewController: UIViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.navigationController?.navigationBar.tintColor = nil
    }

}
```

### ナビゲーションバーの背景色を変更する

`UINavigationBar` の `barTintColor` を変更することで、ナビゲーションバーの背景色を変更できます。

```swift:MonsterIndexListViewController.swift
final class MonsterIndexListViewController: UIViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.navigationController?.navigationBar.barTintColor = UIColor(named: "tekimon_khaki")
    }

}
```

|背景色未変更|背景色変更|
|:--|:--|
|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-23 at 21.01.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/f39a8e30-42ac-d3a2-13fc-7d0962b92f08.png)|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-23 at 20.59.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/682aed13-29ad-468a-d572-9565e2ab0417.png)|

一部のビューコントローラーのみナビゲーションバーの背景色を変更する場合、戻し忘れに気をつけてください。

戻すには `nil` をセットします。

```swift:MainController.swift
final class MainViewController: UIViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.navigationController?.navigationBar.barTintColor = nil
    }

}
```

### ナビゲーションバーを透過する

`UINavigationBar` の `backgroundImage` と `shadowImage` を初期化することで、ナビゲーションバーを透過できます。

```swift:MonsterIndexListViewController.swift
final class MonsterIndexListViewController: UIViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
        self.navigationController?.navigationBar.shadowImage = UIImage()
    }

}
```

|非透過|透過|
|:--|:--|
|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 20.39.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/9ee06bdb-0910-e3f6-aa18-8a9d14949593.png)|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 20.36.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/4a7fe7e4-755c-dab7-9bb3-1455aef3c9ab.png)|

一部のビューコントローラーのみナビゲーションバーを透過する場合、戻し忘れに気をつけてください。

非透過にするには `nil` をセットします。

```swift:MainViewController.swift
final class MainViewController: UIViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
        self.navigationController?.navigationBar.shadowImage = nil
    }

}
```

私が参考にしたサイトでは、 `UINavigationBar` に以下のような拡張メソッドを実装していました。
意図が明確になっていいと思います。

```swift:UINavigationBar+Transparency.swift
extension UINavigationBar {

    func enableTransparency() {
        setBackgroundImage(UIImage(), for: .default)
        self.shadowImage = UIImage()
    }

    func disableTransparency() {
        setBackgroundImage(nil, for: .default)
        self.shadowImage = nil
    }

}
```

### ナビゲーションバーを非表示にする

`UINavigationController.setNavigationBarHidden(_:animated:)` メソッドを呼び出すことで、ナビゲーションバーの表示/非表示を切り替えられます。

```swift:MainViewController.swift
final class MainViewController: UIViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.navigationController?.setNavigationBarHidden(true, animated: false)
    }

}
```

|表示|非表示|
|:--|:--|
|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 18.38.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/a4b57033-725d-89b7-c49f-c7af3c431022.png)|![Simulator Screen Shot - iPhone SE (2nd generation) - 2021-03-21 at 18.35.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/28f8a133-0297-38e5-d61b-2cd932d0f57e.png)|

ナビゲーションバーの表示/非表示が混在しているアプリの場合、画面を遷移するたびにナビゲーションバーの表示も切り替える必要があるので、 `viewWillAppear(_:)` メソッド内で実行すべきです。

ナビゲーションバーを表示 or 隠し忘れないように気をつけてください。

## おまけ: スクリーンショットのアプリ

スクリーンショットに使ったアプリは、先日行われたHack Day 2021で私たちのチームが開発しました！

詳細は他のメンバーが書いてくれたので、ぜひ見てください。

|メンバー|URL|
|:--|:--|
|[@hcrane14](https://twitter.com/hcrane14)|https://note.com/hcrane/n/n98a8f1157390|
|[@totokit4](https://twitter.com/totokit4)|https://note.com/totokit4/n/ne82547d53e05|

## おわりに

これでナビゲーションバーをカスタマイズする案件が来ても安心です :relaxed: 

`self.navigationController` を変更すると他のビューコントローラーにも影響するので `viewWillAppear(_:)` で行って戻すのを忘れない、 `self.navigationItem` は変更しても他のビューコントローラーに影響しないので `viewDidLoad()` で行う、と覚えておくといいです（間違っていたらすみません）。

## 参考リンク

- [navigationbarに画像を設定 - Qiita](https://qiita.com/ryy/items/6f10acee3377cd03e824)
- [【Swift4】UINavigatinBar の戻るボタンの画像を変更](https://3jino-oyatsu.com/blog/37/)
- https://twitter.com/the_uhooi/status/1374311737885745152?s=20
- [iOS14で戻るボタンのタイトルを空欄にするきちんとした方法 | Spinners Inc.](https://spinners.work/posts/ios14_blank_back_button/)
- [【Swift/iOS】ナビゲーションバーのカスタマイズ | カピ通信](https://capibara1969.com/933/)
- [UINavigationBarの透明化 - ttlog](https://tommy10344.hatenablog.com/entry/2019/10/15/113806)