【SwiftUI】TabViewのtabItemにシステムイメージを設定すると自動的にfillモードで塗りつぶされてしまう問題の解決策
久しぶりにSF Symbolsと向き合えてワクワクしています。
今回は、TabView
の.tabItem
にImage(systemName:)
のようにシステムイメージを設定すると自動的に塗りつぶされてしまって困ったのでその解決策について記載したいと思います。
環境
- Xcode 14
現象
コード
TabView
のcontentと.tabItem
のcontentに同じようにImage(systemName: "heart")
が表示されるようにしています。
import SwiftUI struct ContentView: View { var body: some View { TabView { Image(systemName: "heart") .font(.system(size: 100)) .tabItem { Image(systemName: "heart") } } } }
プレビュー
TabView
のcontentとして表示されているImage(systemName: "heart")
は、そのまま表示されているのに対して、.tabItem
のcontentとして表示されているImage(systemName: "heart")
は何故か塗りつぶされた状態になってしまいました。
この塗りつぶされた状態というのは、SymbolVariantsが.fill
に設定されている状態になっていそうです。
SymbolVariantsとは
SF Symbolsの多くは、一つのシンボルに対して円で囲まれたり、四角で囲まれたり、スラッシュが入っていたりなど、複数のバリアント(異なる形)が存在しています。
iOS 15から使用できるsymbolVariant(_:)
モディファイアを使用すること、特定のバリアントを表示させることが出来ます。
使用例
コード
Image(systemName: "heart")
に対して、symbolVariant(_:)
を使用してそれぞれのバリアントを指定してあげています。
struct ContentView: View { var body: some View { VStack(spacing: 8) { Image(systemName: "heart") .symbolVariant(.none) Image(systemName: "heart") .symbolVariant(.fill) Image(systemName: "heart") .symbolVariant(.circle) Image(systemName: "heart") .symbolVariant(.square) Image(systemName: "heart") .symbolVariant(.rectangle) Image(systemName: "heart") .symbolVariant(.slash) } .font(.system(size: 60)) } }
プレビュー
Image(systemName: "heart")
を表示させていますが、それぞれの指定したバリアントが反映されて表示されているのが分かります。
SymbolVariantsをnoneにしてみる
.symbolVariant(.fill)
になっていそうだなということで、バリアントを.none
に指定してあげる方法を試してみました。
コード
struct ContentView: View { var body: some View { TabView { Image(systemName: "heart") .font(.system(size: 100)) .tabItem { Image(systemName: "heart") // .noneを指定 .symbolVariant(.none) } } } }
プレビュー
結果振るわずでした、、
tabItemのcontentクロージャー内では自動的にfill状態になる
Apple公式 SymbolVariantsのドキュメントに下記の記載がありました。
SwiftUI sets a variant for you in some environments. For example, SwiftUI automatically applies the fill symbol variant for items that appear in the content closure of the swipeActions(edge:allowsFullSwipe:content:) method, or as the tab bar items of a TabView.
環境によってはバリアントが設定されます。例えば、swipeActionsメソッドやTabViewのタブバーアイテムのcontentクロージャー内ではfillのシンボルバリアントが自動的に表示されます。
どうやら.fill
の状態になってしまう仕様のようでした。
解決策
iOS 15以上
実はsymbolVariantsという環境値が存在するので、環境変数に直接アクセスして設定を変えてしまいましょう。
コード
struct ContentView: View { var body: some View { TabView { Image(systemName: "heart") .font(.system(size: 100)) .tabItem { Image(systemName: "heart") // 環境値の設定を.noneを指定 .environment(\.symbolVariants, .none) } } } }
プレビュー
.tabItem
のcontent内でも塗りつぶされていない状態で表示されました!
iOS 14以下
iOS 14以下ではsymbolVariants
が使用出来ない為、やや力技ですが、一度UIImage
に変換したものをImage
に設定してあげます。
コード
struct ContentView: View { var body: some View { TabView { Image(uiImage: UIImage(systemName: "heart")!) // フォントを指定 .font(.system(size: 100)) .tabItem { // UIImageに一度変換 Image(uiImage: UIImage(systemName: "heart")!) } } } }
Image(uiImage: UIImage(systemName: "heart")!)
のように一度UIImage
に変換してあげると、よくも悪くもImage(systemName:)
の時に受けれる恩恵が受けれなくなります。.font
が適用されなかったり、.symbolVariant(_:)
が適用されなかったりするので、その影響を利用することで.tabItem
のcontent内でも.none
のまま表示することが出来ます。
プレビュー
今回はあえて、TabView
のcontent側もImage(uiImage: UIImage(systemName: "heart")!)
を表示させていますが、.font
の影響を受けれていないのが分かります。
しかし、.tabItem
側もsymbolVariants
の影響を受けれていないので、意図した表現をすることが出来ました。
おわりに
無事にTabView
の.tabItem
内でも意図したSF Symbolsを表示することが出来ました。久しぶりにSF Symbolsに触れてとても幸せな思いでした。
引き続き、SF Symbolsのことをよろしくお願い致します。
この記事が誰かの助けになれば嬉しいです。