Inspired by this post, here is my solution:
First, let's make a view modifier .inverseMask()
:
extension View {
// view.inverseMask(_:)
public func inverseMask<M: View>(_ mask: M) -> some View {
// exchange foreground and background
let inversed = mask
.foregroundColor(.black) // hide foreground
.background(Color.white) // let the background stand out
.compositingGroup() // ⭐️ composite all layers
.luminanceToAlpha() // ⭐️ turn luminance into alpha (opacity)
return self.mask(inversed)
}
}
Second, let's make a demo view:
Please note that I have used a custom extension in the following code for LinearGradient
, it's listed at the bottom.
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
// lightbulb image
let lightbulb = Image(systemName: "lightbulb")
.resizable().scaledToFit().padding(24)
// rounded rect (shape)
let roundedRect = RoundedRectangle(cornerRadius: 20)
// rounded rect (stroked border)
let border = roundedRect
.stroke(
Gradient.diagonal(.white, .black), // my custom extension
lineWidth: 2
)
// container
return ZStack {
// background color
Color.white.grayscale(0.3)
// rounded card
Gradient.horizontal(.gray, .black) // my custom extension
// ⭐️ inverse mask
.inverseMask(lightbulb)
.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())
and the result is:
----[ Edited ]----
Here is my custom extension for Gradient
if you're curious:
import SwiftUI
extension Gradient {
// general linear gradient ---------------------------
public static func linear(
from start: UnitPoint,
to end: UnitPoint,
colors : [Color] // use array
) -> LinearGradient
{
LinearGradient(
gradient : Gradient(colors: colors),
startPoint: start,
endPoint : end
)
}
public static func linear(
from start: UnitPoint,
to end: UnitPoint,
colors : Color... // use variadic parameter
) -> LinearGradient
{
linear(from: start, to: end, colors: colors)
}
// specialized linear gradients ------------------------
// top to bottom
public static func vertical(_ colors: Color...) -> LinearGradient {
linear(from: .top, to: .bottom, colors: colors)
}
// leading to trailing
public static func horizontal(_ colors: Color...) -> LinearGradient {
linear(from: .leading, to: .trailing, colors: colors)
}
// top leading to bottom trailing
public static func diagonal(_ colors: Color...) -> LinearGradient {
linear(from: .topLeading, to: .bottomTrailing, colors: colors)
}
// top leading to bottom trailing
public static func diagonal2(_ colors: Color...) -> LinearGradient {
linear(from: .bottomLeading, to: .topTrailing, colors: colors)
}
}
Oh, by the way, maybe this is what the OP wanted:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
// background image
let background = Image("red stars.PNG")
// mask
let mask = Image(systemName: "play.circle.fill")
.font(.system(size: 100))
.scaledToFit() // center the mask
// container
return ZStack {
// background color
Color.white.grayscale(0.3)
// card
background
.inverseMask(mask) // ⭐️ cut out the mask
// shadow for hole
.shadow(color: Color.black.opacity(0.7), radius: 3, x: 3, y: 3)
// highlight & shadow for 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())
and the result is: