[SwiftUI] 独自定義の色(enum)を直接モディファイアに渡す
最近までの話(enum から Color への変換)
アプリ内で使う色を enum で定義しています。
enum MyColor {
case primary
case secondary
}
このままでは使えないので、各フレームワークに合わせてそれぞれの色(UIColor
や SwiftUI.Color)
に変換します。
extension MyColor {
func toColor() -> Color {
...
}
}
色を設定したいコンポーネントで上記メソッドを呼び、フレームワークに合った型に変換して使っていました。
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(MyColor.primary.toColor())
SwiftUI のモディファイアを知る
foregroundStyle
や backgroundStyle
は、名前からして style を渡すことのできるモディファイアだと認識していましたが、 Color
を渡せることが少し疑問でした。
foregroundStyle
モディファイアの定義を見ると、 View
の extension
として以下のように定義されていました。
@inlinable public func foregroundStyle<S>(_ style: S) -> some View where S : ShapeStyle
Color
が ShapeStyle
に適合しているため、各モディファイアに渡すことができたようです。
独自定義の色に ShapeStyle を適合させる
ということであれば、独自定義の MyColor
に ShapeStyle
を実装すれば、変換せずとも各モディファイアに直接渡すことができそうです。
ShapeStyle
の定義は以下のとおりです。
Color
が ShapeStyle
に適合しているので、 Resolved
の型を Color
にして、 resolve(in:)
を実装すれば良さそうです。
extension MyColor: ShapeStyle {
typealias Resolved = Color
func resolve(in environment: EnvironmentValues) -> Color {
self.toColor()
}
}
ShapeStyle
に適合させることで、各モディファイアに渡す際の変換が不要になりました。
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(MyColor.primary)
標準で用意されているスタイルはさらに短い記述
SwiftUI のプロジェクトを作成したとき、 foreground
には .tint
という値が設定されていました。
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
.
から始められるのは、なんだかかっこよさげです。
.tint
の定義を見ていくと、 ShapeStyle
の拡張として、静的プロパティが定義されていることがわかりました。
同様の記述を行えば、独自の色定義もさらに短い記述ができるはずです。
extension ShapeStyle where Self == MyColor {
static var primaryColor: MyColor { .primary }
static var secondaryColor: MyColor { .secondary }
}
.primary
のまま定義することはできないので、 .primaryColor
と名前を変えています。
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.primaryColor)
このように .
から記述することができました。
ただ、せっかく enum で定義しているのに、それぞれ対応した静的プロパティを用意しないといけないのが面倒な気がしました。 enum の定義が多い場合は、ここまでやるメリットは薄いかもしれないですね。
おわりに
- メソッドによる型変換
-
ShapeStyle
への適合 -
ShapeStyle
の拡張静的プロパティ
と、徐々にモディファイアへ渡す際の記述が短くなるような実装を紹介しました。
今回は色のみを扱いましたが、まだまだいろいろできることがありそうです。
色だけなら 2 までで十分かな、と思いましたが、アプリ全体を通してスタイルが決まっているようなら、 3 までかっちり定義するのもアリかもしれません。
追記
iOS 17 以降でなければ使えなかった。
うまくできたと思ったのですが、実際に試してみるときちんと色が反映されない端末がありました。
よくよく resolve(in:)
メソッドを見てみると iOS 17.0+ と書かれているじゃないですか…。
さすがに現時点で iOS 17 以降のみをサポートしているアプリはごく少数かと思います。
まだしばらくは使えなさそうです。残念…。
Discussion