【SwiftUI】NavigationStackの戻るボタンをカスタマイズする

NavigationStackを使用して画面遷移を行う際、必ず左上に`< Back`というボタンが表示されます。このボタンをカスタマイズしてアプリの雰囲気に調和したデザインに変更したいと思います。

![スクリーンショット 2023-10-31 0.47.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111542/44007fab-bd3f-48e9-b9f4-4bf267830e22.png)

# NavigationStackで画面遷移を記述する
シンプルに`FirstView`と`SecondView`を作成するとこんな感じです。
#### ソースコード
``` Swift: FirstView.swift
struct FirstView: View {
    var body: some View {
        NavigationStack {
            VStack {
                Text("FirstView")
                Spacer()
                    .frame(height: 50)
                NavigationLink(destination: SecondView(), label: {
                    Text("SecondView")
                })
            }
        }
    }
}
```
```Swift: SecondView.swift
struct SecondView: View {
    var body: some View {
        Text("SecondView")
    }
}
```
#### 完成イメージ
|FirstView|SecondView|
|:-:|:-:|
|![スクリーンショット 2023-10-31 1.00.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111542/0f945565-a362-f411-9922-006cd1735c52.png)|![スクリーンショット 2023-10-31 1.01.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111542/0ed1cb6a-4383-943e-86c9-72948a64443b.png)|

`FirstView`で`SecondView`をタップすると画面遷移するというシンプルな構造にしています。

# 戻るボタンをカスタマイズする
初期状態だと青くて普通のボタンなので、`SF Symbols`からとってきた戻る矢印に差し替えましょう。
カスタマイズは戻るボタンが表示される`SecondView`に記述します。

#### ソースコード
```Swift: SecondView.swift
struct SecondView: View {
    @Environment(\.dismiss) var dismiss

    var body: some View {
        Text("SecondView")
            .navigationBarBackButtonHidden(true)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button(
                        action: {
                            dismiss()
                        }, label: {
                            Image(systemName: "arrow.uturn.backward")
                        }
                    ).tint(.black)
                }
            }
    }
}
```
コードについて説明していきます。
#### .navigationBarBackButtonHidden(Bool)
`.navigationBarBackButtonHidden(true)`は元のボタンを消す役割があります。

:::note warn
これは戻るボタンが表示がされなくなるだけでなく、画面端をスワイプすることで戻る機能も失われるので、UXが低下する恐れがあります。
:::

スワイプ機能を追加で実装する方法を以下の記事にまとめたので興味がある方はぜひご覧ください。

https://qiita.com/kaito-seita/items/083831ff99b69a6af207

#### .toolBar
この`Modifier`の中で`ToolBarItem`を記述することで、上部の戻るボタンをカスタマイズすることができます。
`ToolBarItem`の引数の`placement`を`.navigationBarLeading`とすることで、戻るボタンの位置にオリジナルのボタンを配置することができます。

#### dismiss()
`Environment(\.dismiss) var dismiss`と記述していますが、これは`callAsFunction`というもので、dismissという画面を破棄する機能をインスタンスとして呼び出すことができます。

https://developer.apple.com/documentation/swiftui/dismissaction/callasfunction()

#### 完成イメージ
![スクリーンショット 2023-10-31 1.38.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111542/29f47d3c-4c99-ab85-63fc-3c86e701d600.png)

こんな感じで黒い矢印に変更することができました。

# Extensionを使って記述を簡略化する
遷移先がサンプルのように簡単ならこのままでいいですが、実際はそんなこともないと思うので、1行の`Modifier`で記述できるようにします。
#### ソースコード
```Swift: View+NavigationBackButton.swift
struct NavigationBackButton: ViewModifier {
    @Environment(\.dismiss) var dismiss

    func body(content: Content) -> some View {
        content
            .navigationBarBackButtonHidden(true)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button(
                        action: {
                            dismiss()
                        }, label: {
                            Image(systemName: "arrow.uturn.backward")
                        }
                    ).tint(.black)
                }
            }
    }
}

extension View {
    
    func navigationBackButton() -> some View {
        self.modifier(NavigationBackButton())
    }
}
```
このようにして`View`の`extension`として`Modifier`を定義することで`.navigationBackButton()`を`SecondView`に記述するだけでカスタマイズが可能になります。

# 終わりに
Extensionを活用することで非常にシンプルに実装することができました。今回はSF Symbolsを使いましたが、オリジナルの画像などを配置することも可能なので、アプリのデザインに合わせて変えてみるといいと思います。