NavigationStack
の入れ子にTabView
を配置し、そのTabView
内の子ViewにPicker
を配置すると、何故かタブバーが透明になってしまったので対処法を模索しました。
環境
- Xcode 14.2
- iOS 16.1
現象
現象①
タブバーの背景が透明になってしまいます。
コード
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
TabView {
ListView()
.tabItem {
Image(systemName: "1.circle")
}
Text("")
.tabItem {
Image(systemName: "2.circle")
}
Text("")
.tabItem {
Image(systemName: "3.circle")
}
}
.navigationTitle("List")
}
}
}
// 子View
struct ListView: View {
enum Category: String, Identifiable, CaseIterable {
case wait
case inProgress
case done
var id: String {
return self.rawValue
}
}
@State private var category: Category = .wait
var body: some View {
VStack {
Picker("", selection: $category) {
ForEach(Category.allCases) {
Text($0.rawValue)
.tag($0)
}
}
.pickerStyle(.segmented)
.padding()
List {
ForEach(0..<20) { index in
Text("Item")
}
}
}
}
}
現象②
子ViewのPicker
とList
の位置を入れ替えると、タブバーが表示されるようになります。
コード
// 子View
struct ListView: View {
enum Category: String, Identifiable, CaseIterable {
case wait
case inProgress
case done
var id: String {
return self.rawValue
}
}
@State private var category: Category = .wait
var body: some View {
VStack {
// Listを上に変更
List {
ForEach(0..<20) { index in
Text("Item")
}
}
// Pickerを下に変更
Picker("", selection: $category) {
ForEach(Category.allCases) {
Text($0.rawValue)
.tag($0)
}
}
.pickerStyle(.segmented)
.padding()
}
}
}
タブバーが透明になってしまうケースとは
同じケースではないが、Stack Overflowで透明になっているケースを発見しました。
iOS 15から変更になったタブバーの仕様で、中身のコンテンツが短い場合にタブバーは透明になるとのこと。
WWDC21 - What's new in UIKitの中(5:50~)でもiOS 15からのタブバーの変更点について説明されていました。
iOS 15での UI の改良点をいくつか紹介します。
UIToolbar と UITabBar の外観を改良しました。この更新された外観では、下にスクロールすると背景素材が削除され、コンテンツがより視覚的に明確になります。
今回発生しているケースとは違いますが、タブバーが透明になってしまうケースが分かりました。
対応策
コードで強制的に背景をつける
onAppear
時にタブバーに背景を付ける処理を行なっています。
struct ContentView: View {
var body: some View {
NavigationStack {
TabView {
ListView()
.tabItem {
Image(systemName: "1.circle")
}
Text("")
.tabItem {
Image(systemName: "2.circle")
}
Text("")
.tabItem {
Image(systemName: "3.circle")
}
}
.navigationTitle("List")
}
.onAppear {
// タブバーに背景を付ける
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithDefaultBackground()
UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance
}
}
}
結果
背景が反映された状態になりました。
NavigationStackとTabViewの入れ子を入れ替える
WWDC22 - Explore navigation design for iOSの内容を見てみると、そもそもTabView
をNavigationStack
の入れ子にするのがApple的にはアンチパターンのように感じました。
タブバーは最上位のコンテンツを表し、アプリの階層の最上位にある必要があります。
![]()
なので、入れ子の箇所を入れ替えてみます。
コード
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
// NavigationStackを入れ子にする
NavigationStack {
ListView()
}
.tabItem {
Image(systemName: "1.circle")
}
Text("")
.tabItem {
Image(systemName: "2.circle")
}
Text("")
.tabItem {
Image(systemName: "3.circle")
}
}
}
}
struct ListView: View {
enum Category: String, Identifiable, CaseIterable {
case wait
case inProgress
case done
var id: String {
return self.rawValue
}
}
@State private var category: Category = .wait
var body: some View {
VStack {
Picker("", selection: $category) {
ForEach(Category.allCases) {
Text($0.rawValue)
.tag($0)
}
}
.pickerStyle(.segmented)
.padding()
List {
ForEach(0..<20) { index in
Text("Item")
}
}
}
.navigationTitle("List")
}
}
結果
こちらでも同様にタブバーの背景が表示されました。
UITabBarAppearance
を操作する方法より、こちらの方がより自然なのでこちらを採用したいと思います。
おわりに
原因の特定は出来ませんでしたが、挙動的にSwiftUI側のバグのような気はします。
今回対応方法を模索していく中でApple側が意図しているTabViewの使い方を理解することが出来て良かったです。
NavigationStack
(またはNavigationView
)とTabView
を合わせて使用した時におかしな挙動をする時は、入れ子の順序を変更してみると良いかもしれないです。