SwiftUIのViewModifier Protocol メモ
最近ViewModifier
Protocolというものを知ったので、簡単にメモ。
結論、このように書ける。
/// Usage:
/// Text("Text")
/// .modifier(BorderedFrame(color: Color.green))
struct BorderedFrame: ViewModifier {
let color: Color
func body(content: Content) -> some View {
content
.padding(5)
.border(color, width: 1)
}
}
以下詳細
SwiftUIのViewに対し、独自のmodifierを定義したい場合、今まで次のようにしていた。
/// Usage:
/// Text("Text")
/// .borderedFrame(Color.red)
extension View {
func borderedFrame(_ color: Color) -> some View {
BorderedFrame(color: color, content: self)
}
}
/// View for modification
struct BorderedFrame<Content: View>: View {
let color: Color
let content: Content
var body: some View {
content
.padding(5)
.border(color, width: 1)
}
}
正直この簡単な例では、わざわざmodification用のViewを新たに作らなくても直接extension内でself
に対しmodifierを生やしていけばよい。しかしEnvironmentValue
を扱いたいなど要件が複雑になってくるとそうはいかず、このようなViewを定義してあげる必要がある。
このmodification用のViewを定義するためのずばりそのものなProtocolがSwiftUIにはあり、それがViewModifier
Protocolである。このProtocolを使うと、記事冒頭のような形で書くことができる。
/// Usage:
/// Text("Text")
/// .modifier(BorderedFrame(color: Color.green))
struct BorderedFrame: ViewModifier {
let color: Color
func body(content: Content) -> some View {
content
.padding(5)
.border(color, width: 1)
}
}
個人的にはmodifier
を明示的に書くのは微妙なので、次のように改めてViewのextensionとして生やすやり方を多用している。
/// Usage:
/// Text("Text)
/// .borderedFrame(Color.green)
extension View {
func borderedFrame(_ color: Color) -> some View {
modifier(BorderedFrame(color: color))
}
}
このViewModifier
Protocolにはconcat
というメソッドも生えており、他のViewModifier
を続けて使うこともできる。
/// Usage:
Text("Text")
.modifier(BorderedFrame(color: Color.blue).concat(ShadowView(color: Color.green)))
/// View Modofiers
struct BorderedFrame: ViewModifier {
let color: Color
func body(content: Content) -> some View {
content
.padding(5)
.border(color, width: 1)
}
}
struct ShadowView: ViewModifier {
let color: Color
func body(content: Content) -> some View {
content
.padding(10)
.shadow(color: color, radius: 10)
}
}
適用順は直感通り、concat
が呼ばれたViewが先、引数で渡されたViewが後の順で適用される。
Text("Text1")
.modifier(BorderedFrame(color: Color.blue).concat(ShadowView(color: Color.green)))
Text("Text2")
.modifier(ShadowView(color: Color.green).concat(BorderedFrame(color: Color.blue)))
とはいえ、可読性が微妙なので特段理由がなければ普通にそれぞれView Extensionとして定義して使う方が個人的には良いと思う。
modifier
やcontent
メソッドが返すのはViewではなく、ModifiedContent
というstructなので、実際に描画されるViewの生成を省略・遅延できるというメリットはありそう。きちんと検証すればパフォーマンスに差異がありそうだが、それはまた今度。