iOS 13 の Large Title 表示を iOS 12 までと同じデザインにする

## はじめに

皆さんは **Large Title** 使っていますか？
iOS 11 から 全体的に太字のデザインに変わって少し残念に
思っていたのですが，**Large Title** だけは好きでよく使っています。

iOS 13 から表示が変わって対応に少し困ったので調べてみました。
`UINavigationBarAppearance`[^1] というのが追加されています。

## iOS 13 の NavigationBar の見た目が変わった

多くのアプリではテーマカラーを持っており，`NavigationBar` の
色設定にテーマカラーやそれ相当の色を充てていたりします。
私のアプリもテーマカラーを充てています。

iOS 13 対応を行う際に Xcode 11 でビルドしたアプリを見ていて
**Large Title** を使っている部分の表示に違和感を感じました。
`NavigationBar` の `barTintColor` を設定していないと
あまり気にならないですが，
**Large Title** 表示時の `NavigationBar` の色がなくなり背景と同化しています。
通常時は，問題なく想定通りです。

下記が実際のアプリでの表示です。

#### Large Title 表示時(背景に溶け込んでいる)

|iOS 11, 12|iOS 13 ⁉️|
|:--:|:--:|
|<img width="375" alt="IMG_1834.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/742b8ac3-3290-72ce-1eeb-96f23a15ffd4.png">|<img width="375" alt="iOS13_large.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/ee1e1f1b-82a5-48ee-c996-dbf96e3159ba.png">|

#### 通常時(問題ない)

|iOS 11, 12 🆗|iOS 13 🆗|
|:--:|:--:|
|<img width="375" alt="IMG_1835.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/60f85695-606a-16ea-fed4-5eceef86cf8a.png">|<img width="375" alt="iOS13_normal.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/06749ecf-fa06-9215-12c3-4e930c43ca7f.png">|


iOS 13 から `NavigationBar` をデフォルトで透過させるようにしたようです。
そのため，`self.view` の `Background Color` と同化して見えるということです。
(上の例は `UITableViewController` のため，`UITableView` の色 `Grouped Table View Color` になっている)

モーダルのデフォルト表示スタイルの変更(Sheet)もそうだけど
デフォルトを変更する(変更を強いる)のやめて欲しい・・・

## `UINavigationBarAppearance` とは

iOS 13 からは `UINavigationBarAppearance` を用いて，
`NavigationBar` 周りのデザインをカスタマイズするようです。

`UINavigationBarAppearance` は `UIBarAppearance`[^2] のサブクラスで，
親戚に `UIToolBarAppearance` や `UITabBarAppearance` などもあります。

設定できる外観(Appearance)は3種類です。

```
.standardAppearance 
.compactAppearance 
.scrollEdgeAppearance 
```

`.standardAppearance` はいわゆる通常の 44pt のもの。
<img width="589" alt="standard.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/facf7264-1d9d-7480-9894-8206025d1709.png">

`.compactAppearance` は Landscape の低い高さのもの。

<img width="1212" alt="compact.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/decc3dfb-5318-44d4-2376-2085e531cd00.png">

`.scrollEdgeAppearance` はラージタイトル用のもの。
スクロールビューがトップに来たときの表示。

<img width="597" alt="largeTitle.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/7d419c2a-7570-b4ab-15a6-fb136d1ceb09.png">

`NavigationBar` のタイトルやフォント **Large Title** のカスタマイズは
iOS 12 までと同様に設定できるようです。

```
titleTextAttributes
largeTitleTextAttributes
```

iOS 12 までは下記のように書いていました。

```swift
self.navigationController?.navigationBar.largeTitleTextAttributes =
[NSAttributedString.Key.foregroundColor: UIColor.white,
 NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 30.0)]
```

iOS 13 からは書き方が変わります。

```swift
// インスタンス生成
let appearance = UINavigationBarAppearance()
// 設定をリセット
appearance.configureWithDefaultBackground() 

// ここに適用させたい設定を書く

// Large Title 表示用設定
self.navigationController?.navigationBar.scrollEdgeAppearance = appearance
// 通常表示時の設定
self.navigationController?.navigationBar.standardAppearance = appearance
```

設定のリセットは3種類あります。
使い分けはいまいちわからないです・・・

```swift
@available(iOS 13.0, *)
open class UIBarAppearance : NSObject, NSCopying, NSSecureCoding {
    /// Reset background and shadow properties to their defaults.
    open func configureWithDefaultBackground()
    
    /// Reset background and shadow properties to display theme-appropriate opaque colors.
    open func configureWithOpaqueBackground()
    
    /// Reset background and shadow properties to be transparent.
    open func configureWithTransparentBackground()
```

## iOS 13 でも iOS 11 や 12 でも同じ Large Title の表示にしたい

サンプルプロジェクトを用意しました。
適宜参考にしてください。
**GitHub**: https://github.com/MilanistaDev/NewLargeTitleSample

### 環境
* Xcode 11
* サポート：iOS 11以上(場合分けが増えるため)
* iOS 12 端末
* iOS 13 端末

### 要件

* TableView をスクロールすると **Large Title** が隠れて NavigationBar のタイトルとなる
* NavigationBar の色は赤で，タイトル色は黒
* iOS 13 ではライトモードでタイトルの色は黒，ダークモードでは白とする

(指定は赤だけどライトモードとダークモードとで色味が変わる `.systemRed` の方が良い)

以下，現行コードから移行する感じで書いていきます。

まず，iOS 11, 12 で想定している表示となるコードは下記です。
(長くなるので TableView 周りの実装は省きます。)

```swift:ViewController.swift
class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.setUpNavigationBar()
    }

    private func setUpNavigationBar() {
        // Large Title Basic Settings
        self.navigationItem.title = "Large Title"
        self.navigationController?.navigationBar.prefersLargeTitles = true
        self.navigationItem.largeTitleDisplayMode = .always
        self.navigationController?.navigationBar.barTintColor = .red
    }
}
```

これで iOS 12 端末で実行すると問題なく表示されます。
iOS 13 では，通常表示の場合は正しく赤色が設定されていますが，
**Large Title** 表示時は透過しており，赤色が出ていません。

|iOS 12 🆗|iOS 13 🤔|
|:--:|:--:|
|![RPReplay_Final1570184333.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/654eb1df-7633-358a-8089-2dd0f581bb47.gif)|![RPReplay_Final1570184462.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/32d50c8f-d9ac-205c-40f3-ddce5ef62579.gif)|


`UINavigationBarAppearance` を用いて iOS 13 の NavigationBar 設定を行います。
iOS 11, 12 の場合と場合分けする必要があります。

```swift:ViewController.swift
private func setUpNavigationBar() {
    // Large Title Basic Settings
    
    // abbreviation 略

    if #available(iOS 13.0, *) {
        let appearance = UINavigationBarAppearance()
        appearance.configureWithDefaultBackground()
        appearance.backgroundColor = .red // .systemRed の方が望ましい
        appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.label]
        appearance.titleTextAttributes = [.foregroundColor: UIColor.label]
        // Large Title 用 NavigationBar の色設定
        self.navigationController?.navigationBar.scrollEdgeAppearance = appearance
        // 通常の NavigationBar の色設定
        self.navigationController?.navigationBar.standardAppearance = appearance
    } else {
        // iOS 13 未満はこれまで通り
        self.navigationController?.navigationBar.barTintColor = .red
    }
}
```

これでちゃんと表示されるようになりました！

![RPReplay_Final1570188881.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/f0d5267e-30d4-b604-2a1e-fdc5c172ef3a.gif)

`largeTitleTextAttributes` と `titleTextAttributes` もあるので
テキストカラーとサイズを変更する場合も容易ですね。

**Large Title** のポジションもある程度カスタマイズできるようです。
`titlePositionAdjustment` を下記のように調整します。

```swift
appearance.titlePositionAdjustment = UIOffset(horizontal: CGFloat , vertical: CGFloat)
```

こんなふうに左側に寄せることもできますね。
`vertical` の方は使い勝手難しそう。

![RPReplay_Final1570436073.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/a96f0d19-9c11-6a5e-22e8-25371d3f8083.gif)


## おわりに

今回は，iOS 13 の Large Title 表示を iOS 12 までと同じデザインにする実装に関して書きました。`UINavigationBarAppearance` ってどこで説明されてたの？って思いましたが，ちょうど見忘れていたセッションで説明が軽くありました。
リリースしたアプリはこの対応していないので早速実装しようと思います。

ご覧いただきありがとうございました！！


## 参考

今回参考にしたセッションは細かいけど大事なことが詰め込まれているセッションです。

**Modernizing Your UI for iOS 13**
https://developer.apple.com/videos/play/wwdc2019/224/

**【iOS11】NavigationBarのlargeTitlesの文字をカスタマイズする**
https://qiita.com/MilanistaDev/items/4ff029c2e93bd5357fbd

[^1]: https://developer.apple.com/documentation/uikit/uinavigationbarappearance

[^2]: https://developer.apple.com/documentation/uikit/uibarappearance