The solution posted by @davidev is simple and works for static use cases. However I implemented a much more flexible approach using path:
struct ScanOverlayView: View {
var body: some View {
GeometryReader { geometry in
let cutoutWidth: CGFloat = min(geometry.size.width, geometry.size.height) / 1.5
ZStack {
Rectangle()
.fill(Color.black.opacity(0.5))
RoundedRectangle(cornerRadius: 20)
.fill(Color.black)
.frame(width: cutoutWidth, height: cutoutWidth, alignment: .center)
.blendMode(.destinationOut)
}.compositingGroup()
Path { path in
let left = (geometry.size.width - cutoutWidth) / 2.0
let right = left + cutoutWidth
let top = (geometry.size.height - cutoutWidth) / 2.0
let bottom = top + cutoutWidth
path.addPath(
createCornersPath(
left: left, top: top,
right: right, bottom: bottom,
cornerRadius: 40, cornerLength: 20
)
)
}
.stroke(Color.blue, lineWidth: 8)
.frame(width: cutoutWidth, height: cutoutWidth, alignment: .center)
.aspectRatio(1, contentMode: .fit)
}
}
private func createCornersPath(
left: CGFloat,
top: CGFloat,
right: CGFloat,
bottom: CGFloat,
cornerRadius: CGFloat,
cornerLength: CGFloat
) -> Path {
var path = Path()
// top left
path.move(to: CGPoint(x: left, y: (top + cornerRadius / 2.0)))
path.addArc(
center: CGPoint(x: (left + cornerRadius / 2.0), y: (top + cornerRadius / 2.0)),
radius: cornerRadius / 2.0,
startAngle: Angle(degrees: 180.0),
endAngle: Angle(degrees: 270.0),
clockwise: false
)
path.move(to: CGPoint(x: left + (cornerRadius / 2.0), y: top))
path.addLine(to: CGPoint(x: left + (cornerRadius / 2.0) + cornerLength, y: top))
path.move(to: CGPoint(x: left, y: top + (cornerRadius / 2.0)))
path.addLine(to: CGPoint(x: left, y: top + (cornerRadius / 2.0) + cornerLength))
// top right
path.move(to: CGPoint(x: right - cornerRadius / 2.0, y: top))
path.addArc(
center: CGPoint(x: (right - cornerRadius / 2.0), y: (top + cornerRadius / 2.0)),
radius: cornerRadius / 2.0,
startAngle: Angle(degrees: 270.0),
endAngle: Angle(degrees: 360.0),
clockwise: false
)
path.move(to: CGPoint(x: right - (cornerRadius / 2.0), y: top))
path.addLine(to: CGPoint(x: right - (cornerRadius / 2.0) - cornerLength, y: top))
path.move(to: CGPoint(x: right, y: top + (cornerRadius / 2.0)))
path.addLine(to: CGPoint(x: right, y: top + (cornerRadius / 2.0) + cornerLength))
// bottom left
path.move(to: CGPoint(x: left + cornerRadius / 2.0, y: bottom))
path.addArc(
center: CGPoint(x: (left + cornerRadius / 2.0), y: (bottom - cornerRadius / 2.0)),
radius: cornerRadius / 2.0,
startAngle: Angle(degrees: 90.0),
endAngle: Angle(degrees: 180.0),
clockwise: false
)
path.move(to: CGPoint(x: left + (cornerRadius / 2.0), y: bottom))
path.addLine(to: CGPoint(x: left + (cornerRadius / 2.0) + cornerLength, y: bottom))
path.move(to: CGPoint(x: left, y: bottom - (cornerRadius / 2.0)))
path.addLine(to: CGPoint(x: left, y: bottom - (cornerRadius / 2.0) - cornerLength))
// bottom right
path.move(to: CGPoint(x: right, y: bottom - cornerRadius / 2.0))
path.addArc(
center: CGPoint(x: (right - cornerRadius / 2.0), y: (bottom - cornerRadius / 2.0)),
radius: cornerRadius / 2.0,
startAngle: Angle(degrees: 0.0),
endAngle: Angle(degrees: 90.0),
clockwise: false
)
path.move(to: CGPoint(x: right - (cornerRadius / 2.0), y: bottom))
path.addLine(to: CGPoint(x: right - (cornerRadius / 2.0) - cornerLength, y: bottom))
path.move(to: CGPoint(x: right, y: bottom - (cornerRadius / 2.0)))
path.addLine(to: CGPoint(x: right, y: bottom - (cornerRadius / 2.0) - cornerLength))
return path
}
}
This will result in:
