NavigationLink 遷移後の TabView 内で List 要素の上端がズレてしまう

## 発生した問題

`SwiftUI` において、`NavigationLink` 遷移後の画面が `List` を含む `TabView` になっている場合、`List` 要素の上端がズレてしまう場合があった。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402114/0df09d19-eff7-fce5-66b2-7cfb095d3dbf.gif" width="350">

上記のように、`NavigationLink` の遷移アニメーションが終わると、ガタつく形で `List` 要素の上端が正常な状態に戻る。また、必ず起こるわけではないが、他タブの `List` 要素の上端がズレたままになっていることもある。

|正常時|ズレたままの時|
|---|---|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402114/49ec961c-7d76-8c00-33f3-11c22865dfe0.png" width="300">|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402114/e7aa0b67-de7d-7219-6ba3-a0a0fa3da4fd.png" width="300">|

### 環境
- macOS 12.4
- Xcode 13.4.1
- iOS 15

## 解決方法（概要）

複数存在する。これらの解決方法から、根本的な原因解明をできると思ったが、すぐには分からなかった。
1. `NavigationLink` の代わりに `.sheet` や `.fullScreenCover` を利用する
2. `List` における `.listStyle` を `.plain` にする
3. `.navigationBarTitleDisplayMode` を `.inline` にする
4. `TabView` を利用しない

## NG 集

1. `NavigationLink` によるアニメーションを無効にする
2. `.navigationTitle` を指定しない

## 詳細

試したコードは以下になります。`ContentView` から `NavigationLink` で `FruitsTabView` に遷移するコードです。`FruitsTabView` は、4 つの `FruitsListView` から構成されており、`FruitsListView` は `List` を利用しています。コンテンツとしては、各季節で旬の果物を、一覧表示しているだけです

```swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: FruitsTabView()) {
                Text("Transition")
            }
        }
    }
}

struct FruitsTabView: View {
    var body: some View {
        TabView() {
            FruitsListView(season: .spring)
                .tabItem {
                    Image(systemName: "1.circle.fill")
                    Text("Spring")
                }
            FruitsListView(season: .summer)
                .tabItem {
                    Image(systemName: "2.circle.fill")
                    Text("Summer")
                }
            FruitsListView(season: .autumn)
                .tabItem {
                    Image(systemName: "3.circle.fill")
                    Text("Autumn")
                }
            FruitsListView(season: .winter)
                .tabItem {
                    Image(systemName: "4.circle.fill")
                    Text("Winter")
                }
        }
        .navigationTitle("Season Fruits")
    }
}

struct FruitsListView: View {

    @State var season: Season

    var body: some View {
        List(self.season.fruits) { fruit in
            Text(fruit.name)
        }
    }

    struct Fruit: Identifiable {
        let id = UUID()
        let name: String
    }

    enum Season {
        case spring
        case summer
        case autumn
        case winter

        var fruits: [Fruit] {
            switch self {
            case .spring:
                return [.init(name: "Strawberry"), .init(name: "Orange"), .init(name: "Kiwi")]
            case .summer:
                return [.init(name: "Meron"), .init(name: "Cherry"), .init(name: "Grape")]
            case .autumn:
                return [.init(name: "Persimmon"), .init(name: "Passionfruit"), .init(name: "Pear")]
            case .winter:
                return [.init(name: "Mandarin Orange"), .init(name: "Apple"), .init(name: "Lemon")]
            }
        }
    }
}
```

|春|夏|秋|冬|
|---|---|---|---|
|![Simulator Screen Shot - iPhone 13 - 2022-08-13 at 20.52.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402114/5f75de37-c200-79a0-0300-d54856899ea3.png)|![Simulator Screen Shot - iPhone 13 - 2022-08-13 at 20.52.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402114/00650f77-1e25-bd25-932c-a3eb8bfd4cbc.png)|![Simulator Screen Shot - iPhone 13 - 2022-08-13 at 20.52.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402114/3f4381bc-bcba-8fba-0b65-4157e71fb41b.png)|![Simulator Screen Shot - iPhone 13 - 2022-08-13 at 20.52.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402114/674a2fa4-a1a2-2911-517f-f0ebc08a1826.png)|

### 解決方法①: `NavigationLink` の代わりに `.sheet` や `.fullScreenCover` を利用する

以下のように、`.sheet` を利用して遷移を行うと、問題は発生しない（`NavigationView` を利用していないため、`.navigationTitle` も適用されない）。

```diff_swift
struct ContentView: View {
+
+   @State var isPresented = false
+
    var body: some View {
-       NavigationView {
-           NavigationLink(destination: FruitsTabView()) {
-               Text("Transition")
-           }
-       }
+       Button(action: { self.isPresented = true }) {
+           Text("Transition")
+       }
+       .sheet(isPresented: self.$isPresented) {
+           FruitsTabView()
+       }
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402114/90d754b1-5d4f-462e-6c0f-2cf8412b8357.gif" width="350">

ここで、`NavigationLink` の遷移アニメーションが問題である可能性も疑ったが、問題は解消されなかった

```diff_swift
struct ContentView: View {
+
+   init() {
+       UINavigationBar.setAnimationsEnabled(false)
+   }
+    
    var body: some View {
        NavigationView {
            NavigationLink(destination: FruitsTabView()) {
                Text("Transition")
            }
        }
    }
}
```

### 解決方法②: `List` における `.listStyle` を `.plain` にする

厳密に言うと、`.plain` と `.inset` を指定すると解決する。一方で、`grouped`、`insetGrouped`、`.sidebar` を指定しても解決しない

```diff_swift
    var body: some View {
        List(self.season.fruits) { fruit in
            Text(fruit.name)
        }
+       .listStyle(.plain)
    }
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402114/af34e202-4bde-c59c-5328-db06a0801030.gif" width="350">

### 解決方法③: `.navigationBarTitleDisplayMode` を `.inline` にする

`.navigationBarTitleDisplayMode` を `.inline` に設定することでも、問題を解消できた。

```diff_swift
                    Text("Autumn")
                }
            FruitsListView(season: .winter)
                .tabItem {
                    Image(systemName: "4.circle.fill")
                    Text("Winter")
                }
        }
+       .navigationBarTitleDisplayMode(.inline)
        .navigationTitle("Season Fruits")
    }
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402114/0b1c2c7c-93eb-a2ab-5cde-22621a0212eb.gif" width="350">

今回の件とは関係ないが、`.inline` を指定すると、リスト要素の上端に結構スペースが存在するなと感じた。また、`.navigationTitle` 周りが怪しいと思い、`.navigationTitle` の指定をやめてみたが、問題は解消しなかった。

### 解決方法④: `TabView` を利用しない

本当に `TabView` が問題の原因の一端であるかを確認するために、`TabView` を利用しない場合も試したが、問題は解消された。

```diff_swift
struct FruitsTabView: View {
    var body: some View {
-       TabView() {
-           FruitsListView(season: .spring)
-               .tabItem {
-                   Image(systemName: "1.circle.fill")
-                   Text("Spring")
-               }
-           FruitsListView(season: .summer)
-               .tabItem {
-                   Image(systemName: "2.circle.fill")
-                   Text("Summer")
-               }
-           FruitsListView(season: .autumn)
-               .tabItem {
-                   Image(systemName: "3.circle.fill")
-                   Text("Autumn")
-               }
-           FruitsListView(season: .winter)
-               .tabItem {
-                   Image(systemName: "4.circle.fill")
-                   Text("Winter")
-               }
-       }
+       FruitsListView(season: .spring)
+           .tabItem {
+               Image(systemName: "1.circle.fill")
+               Text("Spring")
+           }
        .navigationTitle("Season Fruits")
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402114/8a126654-edac-83a6-9d24-05cf8ec5efb3.gif" width="350">

## 最後に

最初に問題を観測した時には、自身のコードが悪いと思っていましたが、サンプルプロジェクトで試しても発生して驚きました。やはり `SwiftUI` には発展途上の部分が依然として存在するということでしょうか（特に`NavigationView` 周りは多い印象）。

今回の調査では根本的な原因の究明までは叶わなかったので、もし知見をお持ちの方がいたら、教えていただけると有り難いです。

それでは、ありがとうございました。