2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

発生した問題

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

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

正常時 ズレたままの時

環境

  • 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 から NavigationLinkFruitsTabView に遷移するコードです。FruitsTabView は、4 つの FruitsListView から構成されており、FruitsListViewList を利用しています。コンテンツとしては、各季節で旬の果物を、一覧表示しているだけです

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 Simulator Screen Shot - iPhone 13 - 2022-08-13 at 20.52.26.png Simulator Screen Shot - iPhone 13 - 2022-08-13 at 20.52.28.png Simulator Screen Shot - iPhone 13 - 2022-08-13 at 20.52.30.png

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

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

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()
+       }
    }
}

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

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

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

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

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

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

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

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

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

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

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

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")
    }
}

最後に

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

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

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

2
2
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?