SwiftUIでBottomBarが消えるバグを回避する

# BottomBarが消えるバグが存在する
SwiftUIは、BottomBarのアイテムが消える既知のバグが存在する。
発生条件は以下の通りである。

## 発生条件
- iOS14.4(執筆時の最新OS)
- ListのContentにNavigationLinkを指定している
- 「親階層→子階層→孫階層→子階層→親階層」の順に画面遷移

![RocketSim Recording - iPhone 12 Pro Max - 2021-02-21 16.14.13.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595751/0bbb0734-66e7-d6aa-2596-5b2f74c2349f.gif)


### バグが発生してしまうコード

```ruby:BottomBarが消える
struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(
                    destination: ChildView(),
                    label: {
                        Text("Parent")
                    })
            }
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Text("Item")
                }
            }
            .navigationTitle("バグ発生")
        }
    }
}

struct ChildView: View {
    var body: some View {
        NavigationLink(
            destination: Text("Grandchild"),
            label: {
                Text("Child")
            })
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Text("Item")
                }
            }
    }
}
```

## 解決策１：idインスタンスメソッドを活用
[StackOverflow](https://stackoverflow.com/questions/65126986/swiftui-bottombar-toolbar-disappears-when-going-back)に解決策として記載されていました。
### idインスタンスメソッドとは？
プロキシ値が更新された場合、画面を再描画します。

### idインスタンスメソッドを活用した解決策
```ruby:idを活用
struct ContentView: View {
    @State var refresh = UUID()  // ←　固有識別番号を割り当てる
    var body: some View {
        NavigationView {
            List {
                NavigationLink(
                    destination: ChildView(),
                    label: {
                        Text("Parent")
                    })
            }
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Text("Item")
                }
            }
            .navigationTitle("バグ発生")
            .id(refresh)  // ←　子と孫階層の再描画を防ぐ
        }
    }
}
```
### 解説
StackOverflowの解説によると、
> 描画タイミングを自明にすることで子階層の強制再描画を防ぐ
>> 引用元：[swiftUI bottomBar toolbar disappears when going back](https://stackoverflow.com/questions/65126986/swiftui-bottombar-toolbar-disappears-when-going-back)

と書いていた。しかし、私の環境はバグを回避することができませんでした。一応、StackOverflowの有識者がバグの回避策として提言されていたため、執筆しました。

## 解決策２：ListのContentにNavigationLinkを宣言しない
バグの発生条件として、「ListのContentにNavigationLinkを指定している」が存在する。そのため、`Content`に`NavigationLink`を指定しなければバグが発生しない。

### Contentに宣言せず、遷移処理を実装する
```ruby:isActionを使うよ
struct ContentView: View {
    @State var transition = false
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(
                    destination: ChildView(),
                    isActive: $transition,
                    label: {})
                List {
                    Button(action: {
                        transition.toggle()
                    }, label: {
                        Text("Parent")
                    })
                }
            }
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Text("Item")
                }
            }
            .navigationTitle("バグ回避")
        }
    }
}
```
![RocketSim Recording - iPhone 12 Pro Max - 2021-02-21 16.47.53.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595751/859e3ba5-628b-f6ab-c704-42a658c40ca3.gif)

### 解説
`NavigationLink`の`isActive`を用いて遷移処理を実装します。`label`パラメーターを宣言しない状態だと、画面にアイテムが描画しない状態で`NavigationLink`を宣言することができます。そして`VStack(or HStack)`で`Button`とセットにしなければエラーの原因になるので、注意が必要です。

### 注意
この解決策はガイドラインに則っていません。また、コードも汚いです。

# 最後に
今回はBottomBarが消えるバグの回避策を執筆しました。ガイドラインに適さない手法で実装しており、褒められた手段ではありません。
他の手段もしくは記事に間違いがある場合、コメント欄で指導してくださると幸いです。

# 参考サイト
- id(_ :)のドキュメント：https://developer.apple.com/documentation/swiftui/view/id(_:)
- StackOverflow:https://stackoverflow.com/questions/65126986/swiftui-bottombar-toolbar-disappears-when-going-back