SwiftUI 裁切形狀的 clipShape & mask

在 SwiftUI 我們可以利用 clipShape 或 mask 將 view 裁切成各種形狀。

ps: 若想針對背景 裁切形狀,可參考以下連結。

clipShape

func clipShape<S>(_ shape: S, style: FillStyle = FillStyle()) -> some View where S : Shape
  • 圓形

clipShape 的參數 shape 可傳入任何遵從 protocol Shape 的形狀,以下我們傳入內建的 circle 將知名影集六人行的圖片裁切成圓形。

Image(.friends)
.clipShape(.circle)
  • 自訂形狀

我們也可以自訂形狀,比方以下例子傳入自訂的菱形 Diamond

struct Diamond: Shape {
func path(in rect: CGRect) -> Path {
Path({ path in
path.addLines([
CGPoint(x: rect.midX, y: 0),
CGPoint(x: rect.maxX, y: rect.midY),
CGPoint(x: rect.midX, y: rect.maxY),
CGPoint(x: 0, y: rect.midY)
])
})
}
}

struct ContentView: View {
var body: some View {
Image(.friends)
.clipShape(Diamond())
}
}

mask

看來 clipShape 很完美,為什麼還需要 mask 呢 ? 因為 clipShape 有個很大的限制,它只能傳入遵從 protocol Shape 的形狀,而 mask 卻可傳入任何 view 當遮罩裁切形狀。

func mask<Mask>(_ mask: Mask) -> some View where Mask : View
  • mask 遮罩的原理

假設我們想顯示六人行,星星是遮罩。此時可以想像兩張圖疊在一起,然後六人行只會顯示出星星有顏色的區域,其它地方被切掉。

因此最後合成的圖變成這樣。

  • 使用 SF symbols

SF symbol 繪製了許多常見的圖案,很適合當遮罩,比方我們使用 SF symbols 的星星 star.fill 當遮罩,這樣就不用自己寫程式畫星星的形狀了。

struct ContentView: View {
var body: some View {

Image(.friends)
.mask(
Image(systemName: "star.fill")
.resizable()
.scaledToFit()
)
}
}

值得注意的,剛剛如果 Image(systemName: "star.fill") 沒有呼叫 resizable() 讓星星縮放到跟六人行一樣大,mask 後我們只能看到小小的星星,因為 Image(systemName: "star.fill") 預設產生的星星尺寸很小的關係。

Image(.friends)
.mask(Image(systemName: "star.fill"))
  • 調整 mask 的位置。

mask 預設會從圖片的左上角開始遮罩,我們也可以調整遮罩的位置,比方利用 offset(x: 100, y: 50) 將星星遮罩的區塊向右移動 100,向下移動 50。

struct ContentView: View {
var body: some View {
VStack {
Image(.friends)
Image(.friends)
.mask(
Image(systemName: "star.fill")
.font(.system(size: 100))
)
Image(.friends)
.mask(
Image(systemName: "star.fill")
.font(.system(size: 100))
.offset(x: 100, y: 50)
)
}
}
}
  • 使用剪影圖片。

如果從 SF symbol 找不到喜歡的形狀,我們也可以使用剪影圖片當遮罩。

  1. 找尋剪影圖片。

從網站 silhouetteAC 或在 google 搜尋剪影 png 或 Silhouette png。

2. 利用 Preview App 去背,只留下黑色影子的區塊。

3. 將圖片加到 assets。

4. 程式

Image(.friends)
.mask(Image(.head))

一樣要特別注意尺寸的問題,如上圖所示,由於 head 比六人行大上許多,圖片只露出了頭髮的區塊。因此我們最好再從 Image(.head) 呼叫 resizable().scaledToFit()

struct ContentView: View {
var body: some View {
Image(.friends)
.mask(
Image(.head)
.resizable()
.scaledToFit()
)
}
}

想要更帥嗎 ? 我們可以將六人行縮放到整個螢幕的大小再裁切成帥氣的臉龐,然後再搭配憂鬱的陰影。

struct ContentView: View {
var body: some View {
Image(.friends)
.resizable()
.scaledToFill()
.frame(minWidth: 0, maxWidth: .infinity)
.mask(
Image(.head)
.resizable()
.scaledToFit()
)
.shadow(radius: 20)
}
}
  • 使用文字

我們也可以使用 Text 當 mask,讓圖片變成文字的形狀。

struct ContentView: View {
var body: some View {
Image(.friends)
.mask(
Text("Friends")
.font(.system(size: 80))
.scaledToFit()
)
}
}

將顏色 & 漸層裁切形狀

任何的 SwiftUI view 都能搭配 clipShape & mask 裁切形狀,因此除了剛剛的圖片例子,接著我們再看看顏色和漸層的例子。

struct ContentView: View {
var body: some View {

VStack {
Color.yellow
.clipShape(.circle)

LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing)
.mask(
Image(systemName: "heart.fill")
.resizable()
.scaledToFit()
)

LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing)
.mask(
Text("用天線排成愛你的形狀")
.font(.system(size: 40))
.scaledToFit()
)
}
}
}

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com