Creating the scratch card effect in SwiftUI
In this post we’ll learn how to create the scratch card effect in SwiftUI.
The idea for this view came when I was going through Azam Sharp’s video on drawing in SwiftUI using the Canvas
view. I wondered if one could apply the strokes using the DragGesture to a view, it would somewhat replicate the scratch card effect that is normally seen in modern iOS applications. Turns out, implementing this is simpler than I thought. All we need to do is to apply the strokes that a user creates via the DragGesture onto a view but using the mask modifier. The code below is modified to do just that.
struct Line {
var points = [CGPoint]()
var lineWidth: Double = 50.0
}
struct ScratchCardCanvasMaskView: View {
@State private var currentLine = Line()
@State private var lines = [Line]()
var body: some View {
ZStack {
// MARK: Scratchable overlay view
RoundedRectangle(cornerRadius: 20)
.fill(.red)
.frame(width: 250, height: 250)
// MARK: Hidden content view
RoundedRectangle(cornerRadius: 20)
.fill(.yellow)
.frame(width: 250, height: 250)
.mask(
Canvas { context, _ in
for line in lines {
var path = Path()
path.addLines(line.points)
context.stroke(path,
with: .color(.white),
style: StrokeStyle(lineWidth: line.lineWidth,
lineCap: .round,
lineJoin: .round)
)
}
}
)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged({ value in
let newPoint = value.location
currentLine.points.append(newPoint)
lines.append(currentLine)
})
)
}
}
}
In the gesture modifier we use the DragGesture to capture points that we will use to draw lines with. These lines are stroked within the Canvas in the mask modifier with the specified lineWidth
of 50.0 for broader strokes and setting the lineCap
and lineJoin
to round
. Unlike in Azam’s video, I chose not to implement the onEnded
modifier in order to reset the currentLine
as I felt that it helped cover more area when the user starts performing a new drag gesture after ending the previous one. If you’d like the strokes between each drag gesture to be disjointed, feel free to reset currentLine
. Note that one has to provide a stroke color to the context.stroke
method. It could be any color other than .clear
.
Another way to implement this scratch card would be to directly stroke shape path(s) like so:
struct ScratchCardPathMaskView: View {
@State var points = [CGPoint]()
var body: some View {
ZStack {
// MARK: Scratchable overlay view
RoundedRectangle(cornerRadius: 20)
.fill(.red)
.frame(width: 250, height: 250)
// MARK: Hidden content view
RoundedRectangle(cornerRadius: 20)
.fill(.yellow)
.frame(width: 250, height: 250)
.mask(
Path { path in
path.addLines(points)
}.stroke(style: StrokeStyle(lineWidth: 50, lineCap: .round, lineJoin: .round))
)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged({ value in
points.append(value.location)
})
)
}
}
}
View it in action:
And that’s it! The complete code can be found here.
Share this article if you found it useful !