- Dec 21, 2024
Mastering Mask Modifier in SwiftUI
- DevTechie Inc
- SwiftUI
SwiftUI’s mask modifier is used to mask one view with another. This modifier is used for masking the original view. When a mask is applied to a view, all the pixels that are transparent in the mask get hidden in the original view.
Let’s learn with an example. We start with a LinearGradient view.
struct ContentView: View {
var body: some View {
LinearGradient(
gradient: Gradient(colors: [.indigo, .orange]),
startPoint: .leading,
endPoint: .trailing
)
.frame(height: 300)
}
}Next, we will add a mask modifier with a Text view.
struct ContentView: View {
var body: some View {
LinearGradient(
gradient: Gradient(colors: [.indigo, .orange]),
startPoint: .leading,
endPoint: .trailing
)
.mask {
Text("Hello DevTechie!")
.font(.custom("Noteworthy Bold", size: 40))
}
.frame(height: 300)
}
}Mask’s content can contain any type of SwiftUI view. Let’s draw border around the Text view.
struct ContentView: View {
var body: some View {
LinearGradient(
gradient: Gradient(colors: [.indigo, .orange]),
startPoint: .leading,
endPoint: .trailing
)
.mask {
Text("Hello DevTechie!")
.font(.custom("Noteworthy Bold", size: 40))
.padding(20)
.border(.primary, width: 10.0)
}
.frame(height: 300)
}
}Mask modifier takes alignment parameter as an argument. If not specified then the alignment is at the center of the screen but we can update it to be any Alignment type. The alignment for mask is applied in relation to the main view.
struct ContentView: View {
var body: some View {
LinearGradient(
gradient: Gradient(colors: [.indigo, .orange]),
startPoint: .leading,
endPoint: .trailing
)
.mask(alignment: .topLeading) {
Text("Hello DevTechie!")
.font(.custom("Noteworthy Bold", size: 40))
.padding(20)
.border(.primary, width: 10.0)
}
.frame(height: 300)
}
}We can also animate between different values of mask alignments.
struct ContentView: View {
let alignments = [Alignment.leading, .trailing, .top, .bottom, .topLeading, .topTrailing, .bottomTrailing , .bottomLeading]
@State private var alignment = Alignment.leading
var body: some View {
LinearGradient(
gradient: Gradient(colors: [.indigo, .orange]),
startPoint: .leading,
endPoint: .trailing
)
.mask(alignment: alignment) {
Text("Hello DevTechie!")
.font(.custom("Noteworthy Bold", size: 40))
.padding(20)
.border(.primary, width: 10.0)
}
.onTapGesture {
withAnimation {
alignment = alignments.randomElement() ?? .leading
}
}
.frame(height: 300)
}
}Let’s build a bit complex UI with this knowledge in hand. We will start by creating a custom shape to draw star with variable corners and smoothness.
struct Star: Shape {
let corners: Int
let smoothness: Double
func path(in rect: CGRect) -> Path {
guard corners >= 2 else { return Path() }
let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
var currentAngle = -CGFloat.pi / 2
let angleAdjustment = .pi * 2 / Double(corners * 2)
let innerX = center.x * smoothness
let innerY = center.y * smoothness
var path = Path()
path.move(to: CGPoint(x: center.x * cos(currentAngle) + center.x,
y: center.x * sin(currentAngle) + center.y))
for corner in 0..<corners * 2 {
let sinAngle = sin(currentAngle)
let cosAngle = cos(currentAngle)
let x: Double
let y: Double
if corner.isMultiple(of: 2) {
x = center.x * cosAngle + center.x
y = center.x * sinAngle + center.y
} else {
x = innerX * cosAngle + center.x
y = innerY * sinAngle + center.y
}
path.addLine(to: CGPoint(x: x, y: y))
currentAngle += angleAdjustment
}
path.closeSubpath()
return path
}
}Let’s use this star inside the content view and animate the shape of the star
struct ContentView: View {
@State private var isAnimating = false
@State private var scale = 1.0
var body: some View {
ZStack {
LinearGradient(
gradient: Gradient(colors: [.blue, .indigo, .mint]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
VStack(spacing: 20) {
Circle()
.fill(
LinearGradient(
gradient: Gradient(colors: [.purple, .mint, .pink]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 300, height: 300)
.mask {
ZStack {
ForEach(0..<6) { index in
Star(corners: 5, smoothness: 0.7)
.frame(width: 300, height: 300)
.rotationEffect(.degrees(isAnimating ? Double(index) * 60 : 0))
}
}
}
.scaleEffect(scale)
Text("DevTechie")
.font(.system(size: 48, weight: .bold))
.foregroundStyle(
LinearGradient(
colors: [.mint, .pink],
startPoint: .leading,
endPoint: .trailing
)
)
}
.onAppear {
withAnimation(.linear(duration: 20).repeatForever(autoreverses: false)) {
isAnimating = true
}
withAnimation(.easeInOut(duration: 2).repeatForever()) {
scale = 0.2
}
}
}
}
}




