はじめに
SwiftUI で形状指定をしたいとき、background
や clipShape
あるいは mask
といくつかの候補が浮かんで「あれ、どれを使えばいいんだ?」と思ったことはありませんか? 使い分けが紛らわしいので簡単なチャットバブルを例に比べてみます。
標準図形を描画できるか
ひとまず SwiftUI の標準図形たちを描画できるかどうかで実力を測ってみることにします
今回作りたい簡単なチャットバブルはこちら
- 円形
Circle
(楕円形Ellipse
) - カプセル
Capsule
- 四角形
Rectangle
(角丸四角形RoundedRectangle
UnevenRoundedRectangle
)
といったシンプルな図形をカバーしています
結論
これらの標準的な図形の描画については、全部いけます。
というのも、それぞれ Shape
プロトコルに準拠した型を受け取ることが可能だからです。
具体的な実装を見ていきましょう。
.background
背景に View
全般を描画できるので、当然 Shape
に準拠した標準図形も引数に取れます
.background(
// View
)
// Circle
Image("hamster_transparent") // *1
.resizable()
.aspectRatio(contentMode: .fit)
.background(
Circle().fill(.white)
)
// Capsule
Text("Hello, world.")
.background(
Capsule().fill(.white) // *2
)
// RoundedRectangle
Text("Lorem ipsum dolor sit amet, ...")
.background(
RoundedRectangle(cornerRadius: 16).fill(.white)
)
.clipShape
形状に合わせてビューそのものを切り取るもので、Shape
に準拠した型のみを受け取ります
.clipShape(
// Shape
)
// Circle
Image("hamster")
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(
Circle()
)
// Capsule & RoundedRectangle
Text("Lorem ipsum dolor sit amet, ...")
.background(.white)
.clipShape(
RoundedRectangle(cornerRadius: 16)
)
.mask
指定したビューの透明部分を使い、元のビューを特定の形に見せるもので、View
全般を受け取れます
.mask(
// View
)
// Circle
Image("hamster")
.resizable()
.aspectRatio(contentMode: .fit)
.mask(
Circle()
)
// Capsule & RoundedRectangle
Text("Lorem ipsum dolor sit amet, ...")
.background(.white)
.mask(
RoundedRectangle(cornerRadius: 16)
)
使い分けを考える
mask
は複雑な形や透明度を操作するため実は処理がかなり重くなります。対して背景を追加するだけの background
と、ビューを切り取る clipShape
は処理が軽いため、シンプルな形状なら前者を使うのが適していると言えます。
背景を操作する background
とビューを切り取る clipShape
は、得意とする用途が全く違うと言えます。background
は背後に要素を重ねるだけでビュー自体を切り取ったり形を変えるわけではありません。元のビューのレイアウトや形状には影響を与えず、単に背景に色や画像を追加する場合に適しています。
一方、明確に元のビューを切り取りたいだけであれば、clipShape
を使用するのが適しているでしょう。
先にみたように、(Shape
型に準拠した)単純な形を作りたい場合は、処理が軽量な前者の clipShape
が適しています。
一方で、複雑な形状に変えたい場合は mask
の方が適切です。
とはいえ、複雑な形状って何でしょう?
例えば、こういったチャットバブルを作るには mask
が適切です(とはいえ、チャットバブルとして成り立たせるには工夫が必要そうです)
// あくまでイメージだが、png 画像の透明エリアを使って形状を作っている
Text(text)
.background(.white)
.mask(
Image("hamster_transparent")
.resizable()
.aspectRatio(contentMode: .fill)
)
.multilineTextAlignment(.center)