【SwiftUI】TabViewのtabItemにシステムイメージを設定すると自動的にfillモードで塗りつぶされてしまう問題の解決策

2022.10.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

久しぶりにSF Symbolsと向き合えてワクワクしています。

今回は、TabView.tabItemImage(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のことをよろしくお願い致します。

この記事が誰かの助けになれば嬉しいです。

参考