👔view.inverseMask()
blend mode - 用
.destinationOut可以達到類似的效果。 ⭐️
import SwiftUI
extension View {
// view.inverseMask(_:)
public func inverseMask<M: View>(_ mask: M) -> some View {
// exchange foreground and background
let inversed = mask
.foregroundColor(.black) // opacity = 0 (hide)
.background(Color.white) // opacity = 1 (show)
.compositingGroup() // ⭐️ composite all layers
// ⭐️ dark -> transparent (opacity = 0)
// bright -> opaque black (opacity = 1)
// 有點「負片」的感覺。
.luminanceToAlpha() // ⭐️ turn luminance into alpha (opacity)
return self.mask(inversed)
}
}
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
// light bulb (resizable image)
let lightbulb = Image(systemName: "lightbulb")
.resizable().scaledToFit().padding(24)
// rounded rect (shape)
let roundedRect = RoundedRectangle(cornerRadius: 20)
// rounded gradient border
let border = roundedRect
.stroke(Neu.gradient.diagonalBorderDark, lineWidth: 2)
// card container
return ZStack {
// card background
Neu.color.grayBackground
// card
Neu.gradient.horizontalDark
.inverseMask(lightbulb) // ⭐️ inverse mask
.shadow(color: Color.black.opacity(0.6), radius: 4, x: 4, y: 4)
.frame(width: 150, height: 200)
.clipShape(roundedRect)
.overlay(border)
.shadow(color: Color.white.opacity(0.9), radius: 18, x: -18, y: -18)
.shadow(color: Color.black.opacity(0.3), radius: 14, x: 14, y: 14)
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
How to Create a Neumorphic Design With SwiftUI - RayWenderlich
Inverted mask swiftui with system image -
destinationView mask.blendMode(.destinationOut) // source view (mask)
細節說明
在 .inverseMask() 中用到的 .foregroundColor(.black) 和 .backgroud(Color.white),對 Image 來說沒有任何作用,如下圖所示:

但 .luminanceToAlpha() 會將原圖中「黑色的部分」轉為「透明」(opacity = 0),而「白色的部分」則轉為「全黑」(opacity = 1),這時的圖有點像相片的「底片」(負片)。
套用 .mask() 的時候,opacity = 1 的會「保留原來的色彩」,opacity = 0 的部分則轉為「透明」。這就是爲什麼原圖小狗的眼睛四周比較黑,會造成最後的圖在小狗眼睛四周有些透明。
import SwiftUI
import PlaygroundSupport
extension View {
@ViewBuilder
func label(_ text: String) -> some View {
VStack(alignment: .leading) {
self.border(Color.black)
Text(text).padding(.leading).padding(.bottom, 10)
}.border(Color.black)
}
}
let shad = Color.black.opacity(0.8)
let numberPaper = Image("numbers")).resizable().scaledToFill()
.rotationEffect(.degrees(90)).frame(maxWidth: 280)
struct ContentView: View {
let puppy: some View = Image("puppy")).resizable().scaledToFit().frame(width: 150)
var v1: some View { puppy.foregroundColor(.black) } // opacity = 0 (hide)
var v2: some View { v1.background(Color.white) } // opacity = 1 (show)
var v3: some View { v2.compositingGroup() } // ⭐️ composite all layers
var v4: some View { v3.luminanceToAlpha() } // ⭐️ turn luminance into alpha
var v5: some View {
ZStack {
numberPaper.mask(v4).shadow(color: shad, radius: 4, x: 4, y: 4)
puppy.hidden().border(Color.black)
}.background(Gradient.down(.yellow, .green))
}
var body: some View {
VStack(alignment: .leading) {
HStack {
puppy.label("image")
v1 .label("fg = black")
v2 .label("bg = white")
}
HStack(alignment: .top) {
v4.background(Color.white).label("亮度轉 alpha")
v5.label("mask(image)")
}
} // container (HStack)
.padding()
.background(Color.gray)
} // body
}
PlaygroundPage.current.setLiveView(ContentView())加上黑色前景、白色背景都有效,這時會有兩個圖層,套用 .compositingGroup() 會合併圖層。合併後,再套用 .luminanceToAlpha(),mask 原圖前景的部分 (也就是原來圖示的部分) 轉為透明,這時再套用 .mask() 就可以「挖掉」原圖的這個部分,這也就是為什麼 .inverseMask() 會有效的原因。

import SwiftUI
import PlaygroundSupport
let shad = Color.black.opacity(0.8)
let clear = Image(uiImage: #imageLiteral(resourceName: "IMG_0328.PNG"))
let paper = Image(uiImage: #imageLiteral(resourceName: "red stars.PNG"))
.resizable().scaledToFit()
.frame(maxWidth: 280, maxHeight: 200)
extension View {
@ViewBuilder
func label(_ text: String) -> some View {
VStack(alignment: .leading) {
self.border(Color.black)
Text(text).padding(.leading).padding(.bottom, 10)
}.border(Color.black)
}
}
struct ContentView: View {
let mask: some View = Image(systemName: "cloud.drizzle.fill").resizable().scaledToFit().frame(width: 150)
var v1: some View { mask.foregroundColor(.black) } // opacity = 0 (hide)
var v2: some View { v1.background(Color.white) } // opacity = 1 (show)
var v3: some View { v2.compositingGroup() } // ⭐️ composite all layers
var v4: some View { v3.luminanceToAlpha() } // ⭐️ turn luminance into alpha
var v5: some View {
ZStack {
paper.mask(v4).shadow(color: shad, radius: 4, x: 4, y: 4)
mask.hidden().border(Color.black)
}.background(Gradient.down(.yellow, .green))
}
var body: some View {
VStack(alignment: .leading) {
HStack {
mask.label("image")
v1 .label("fg = black")
v2 .label("bg = white")
}
HStack(alignment: .top) {
v4.background(clear).clipped().label("亮度轉 alpha")
v5.label("mask(image)")
}
} // container (HStack)
.padding()
.background(Color.gray)
} // body
}
PlaygroundPage.current.setLiveView(ContentView())Last updated
Was this helpful?