LoginSignup
18
9

`toolbarBackground` を使って Pure SwiftUI でツールバーの外観を変える

Last updated at Posted at 2022-09-17

SwiftUI と UIBarAppearance

iOS 15 まで、SwiftUI では NavigationBar / TabBar / ToolBar などのツールバーの背景色を設定する手段がありませんでした。そのため、以下のように UIKit の UIBarAppearance を指定する方法がとられていたと思います。

let appearance = UITabBarAppearance()
appearance.backgroundColor = .systemBackground
UITabBar.appearance().standardAppearance = appearance
UITabBar.appearance().scrollEdgeAppearance = appearance

しかし、この方法はグローバルな設定を書き換えてしまうという難点があります。具体的には、以下の記事で書かれているような問題を引き起こすことがあります。

また、 NavigationViewTabView の裏に 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 適用後
2022-09-16 10.11.52.gif 2022-09-16 10.11.34.gif

自分の手元で試した限り、 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 を使う方法では、 UITabBarscrollEdgeAppearance プロパティにエッジまでスクロールした場合の見た目を別途指定できます。

しかし、ほとんどの場合は .toolbarBackground で行える範囲の指定で十分だし、その範囲の指定に収めるのが望ましいと思うので、実質的には iOS 16 以上のみをサポートするアプリに関しては UIBarAppearance を指定する必要は無くなったと言っていいでしょう。

参考

18
9
0

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
18
9