SwiftUI と UIBarAppearance
iOS 15 まで、SwiftUI では NavigationBar / TabBar / ToolBar などのツールバーの背景色を設定する手段がありませんでした。そのため、以下のように UIKit の UIBarAppearance
を指定する方法がとられていたと思います。
let appearance = UITabBarAppearance()
appearance.backgroundColor = .systemBackground
UITabBar.appearance().standardAppearance = appearance
UITabBar.appearance().scrollEdgeAppearance = appearance
しかし、この方法はグローバルな設定を書き換えてしまうという難点があります。具体的には、以下の記事で書かれているような問題を引き起こすことがあります。
また、 NavigationView
や TabView
の裏に UIKit がいるという内部実装に依存しているので、新しい iOS でも常に想定通り動き続けるという保証はありません。とは言え、他にツールバーの背景色を指定する手段があるわけでもないので、これまでは UIBarAppearance
を指定するしかありませんでした。
.toolbarBackground
iOS 16 で toolbarBackground(_:for:)
という新しい API が追加され、 SwiftUI の範囲内でツールバーの背景色を指定できるようになりました。toolbarBackground
の第一引数に適用したい ShapeStyle
を、第二引数に対象の ToolBarPlacement
を指定することで背景色を設定することができます。
struct FirstTab: View {
var body: some View {
NavigationStack {
List {
ForEach(1..<30) { i in
Text("Text \(i)")
}
}
.navigationTitle("First Tab")
// NavigationBar の背景色を設定
.toolbarBackground(.blue, for: .navigationBar)
}
}
}
struct ContentView: View {
var body: some View {
TabView {
FirstTab()
// TabBar の背景色を設定
.toolbarBackground(.gray, for: .tabBar)
.tabItem {
Label("first", systemImage: "1.circle")
}
}
}
}
上記の例では toolbarBackground
の第一引数に Color
を渡していますが、 ShapeStyle
protocol に準拠しているものであれば渡すことができます。ただし、 Xcode 14.1 Beta で試した限りでは .linearGradient
/ .angularGradient
/ .image
などは動作しないように見えます。これは仕様かもしれませんが、単なる不具合で今後修正される可能性もあると思います。いずれにしても、現実的には Color
を指定したいケースがほとんどだと思うので困ることはあまりないでしょう。
エッジまでスクロールした状態の背景色指定
toolbarBackground(_:for:)
に単に ShapeStyle
を渡しただけだと、iOS の場合エッジまでスクロールするとツールバーは透明になり、渡した設定が反映されなくなります。スクロール位置に関わらず設定を反映させるには toolbarBackground(_:for:)
を使います。 ShapeStyle
を渡す API と名前は同じですが、第一引数は Visibility
です。これを .visible
に指定することで常に設定した背景色が使われるようになります。
NavigationStack {
List {
// ...
}
.navigationTitle("First Tab")
.toolbarBackground(.blue, for: .navigationBar)
+ .toolbarBackground(.visible, for: .navigationBar)
}
これを NavigationBar / TabBar に対して適用した前後の様子は以下のようになります。
.visible 適用前 | .visible 適用後 |
---|---|
自分の手元で試した限り、 Xcode 14.0 の時点では TabBar に対して .visible
の指定が効いてないようでした。もしうまく動作しない場合は Xcode 14.1 以上で試してみてください。
複数のタブに対する TabBar の背景色指定
TabBar に対する指定は、指定したタブがアクティブになっている時だけ有効になります。すべてのタブに対して同じ背景色を指定したい場合は TabView
の子を Group
にして、それに対して .toolbarBackground
を指定すればよいです。
TabView {
Group {
FirstTab()
.tabItem { Label("first", systemImage: "1.circle") }
SecondTab()
.tabItem { Label("second", systemImage: "2.circle") }
}
.toolbarBackground(.gray, for: .tabBar)
}
逆にアクティブなタブごとに TabBar に異なる背景色を指定したい場合は Tab ごとに toolbarBackground
を指定します。
ツールバーの背景色指定の今後
.toolbarBackground
のおかげで UIBarAppearance
を直接操作する時代は終わったのかというと、 .toolbarBackground
は iOS 16 以降でしか使えないので、当然ながら iOS 15 以前をサポートしているアプリでは UIBarAppearance
を指定する必要があるでしょう。
また、 iOS 16 以上のみをサポートする場合であっても .toolbarBackground
よりも UIBarAppearance
の方ができることの幅が広いので、特殊な要件のあるアプリでは引き続き UIBarAppearance
を指定したい場合があるかもしれません。
例えば、 iOS の .toolbarBackground
では、エッジまでスクロールした場合の TabBar の見た目は
- 通常の状態と同じ(
.toolbarBackground(.visible, for: .tabBar)
) - 透明(
.toolbarBackground(.automatic, for: .tabBar)
)
の2通りしか指定できないように思います。これに対して、 UIBarAppearance
を使う方法では、 UITabBar
の scrollEdgeAppearance
プロパティにエッジまでスクロールした場合の見た目を別途指定できます。
しかし、ほとんどの場合は .toolbarBackground
で行える範囲の指定で十分だし、その範囲の指定に収めるのが望ましいと思うので、実質的には iOS 16 以上のみをサポートするアプリに関しては UIBarAppearance
を指定する必要は無くなったと言っていいでしょう。