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

上記のように、NavigationLink
の遷移アニメーションが終わると、ガタつく形で List
要素の上端が正常な状態に戻る。また、必ず起こるわけではないが、他タブの List
要素の上端がズレたままになっていることもある。
正常時 | ズレたままの時 |
---|---|
![]() |
![]() |
環境
- macOS 12.4
- Xcode 13.4.1
- iOS 15
解決方法(概要)
複数存在する。これらの解決方法から、根本的な原因解明をできると思ったが、すぐには分からなかった。
-
NavigationLink
の代わりに.sheet
や.fullScreenCover
を利用する -
List
における.listStyle
を.plain
にする -
.navigationBarTitleDisplayMode
を.inline
にする -
TabView
を利用しない
NG 集
-
NavigationLink
によるアニメーションを無効にする -
.navigationTitle
を指定しない
詳細
試したコードは以下になります。ContentView
から NavigationLink
で FruitsTabView
に遷移するコードです。FruitsTabView
は、4 つの FruitsListView
から構成されており、FruitsListView
は List
を利用しています。コンテンツとしては、各季節で旬の果物を、一覧表示しているだけです
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")]
}
}
}
}
春 | 夏 | 秋 | 冬 |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
解決方法①: 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
を指定すると解決する。一方で、grouped
、insetGrouped
、.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
周りは多い印象)。
今回の調査では根本的な原因の究明までは叶わなかったので、もし知見をお持ちの方がいたら、教えていただけると有り難いです。
それでは、ありがとうございました。