I'm trying to create a component like:
To do this in code, the only way i know how is to do a CGPath
to create this shape.
struct TicketShape {
let rect: CGRect
let cornerRadius: CGFloat
let notchRadius: CGFloat
let notchOffsetY: CGFloat
init(rect: CGRect, cornerRadius: CGFloat = 8, notchRadius: CGFloat = 12, notchOffsetY: CGFloat = 0) {
self.rect = rect
self.cornerRadius = cornerRadius
self.notchRadius = notchRadius
self.notchOffsetY = notchOffsetY
}
func makePath() -> CGPath {
let path = CGMutablePath()
let notchCenterY = rect.midY + notchOffsetY
path.move(to: .init(x: cornerRadius, y: 0))
// from left to right line
path.addLine(to: .init(x: rect.width - cornerRadius, y: 0))
// top - right corner
path.addQuadCurve(
to: .init(x: rect.width, y: cornerRadius),
control: .init(x: rect.width, y: 0)
)
// top to bottom - right side
path.addLine(to: .init(x: rect.width, y: notchCenterY - notchRadius))
// trailing notch
path.addArc(
center: .init(x: rect.width, y: notchCenterY),
radius: notchRadius,
startAngle: -CGFloat.pi / 2,
endAngle: CGFloat.pi / 2,
clockwise: true
)
path.addLine(to: .init(x: rect.width, y: rect.height - cornerRadius))
// bottom right corner
path.addQuadCurve(
to: .init(x: rect.width - cornerRadius, y: rect.height),
control: .init(x: rect.width, y: rect.height)
)
path.addLine(to: .init(x: cornerRadius, y: rect.height))
// bottom left corner
path.addQuadCurve(
to: .init(x: 0, y: rect.height - cornerRadius),
control: .init(x: 0, y: rect.height)
)
path.addLine(to: .init(x: 0, y: notchCenterY + notchRadius))
// leading notch
path.addArc(
center: .init(x: 0, y: notchCenterY),
radius: notchRadius,
startAngle: CGFloat.pi / 2,
endAngle: -CGFloat.pi / 2,
clockwise: true
)
path.addLine(to: .init(x: 0, y: cornerRadius))
path.addQuadCurve(
to: .init(x: cornerRadius, y: 0),
control: .init(x: 0, y: 0)
)
path.closeSubpath()
return path
}
func makeCAShape() -> CAShapeLayer {
let shape = CAShapeLayer()
shape.frame = rect
shape.path = makePath()
return shape
}
}
This code is fairly complicated to understand to achieve such a simple concept.
This approach is boils down to draw every line that composes the shape
I was wonderting if there was a different way to achieve this for UIKit.
I wondered if there was a way to "combine shapes" like you do in sketch. Where you
create shapes by substracting 2 shapes or more.
In this case to create the ticket shape I would need to:
- create a rounded rectangle (A)
- create a small circle (B)
- remove B from A = C
- create a second circle (D)
- remove D from C => ticket shape.
As far as i know I cannot just combine layers/shapes in this way.
The other issue is to add this to my UIView class. For that I would do something like:
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer?.fillColor = UIColor.backgroundSecondary.cgColor
guard shapeLayer == nil else { return }
let ticketShape = TicketShape(rect: bounds).makeCAShape()
ticketShape.shadowColor = UIColor.black.cgColor
ticketShape.shadowRadius = 2
ticketShape.shadowOffset = CGSize(width: 1, height: 1)
ticketShape.shadowOpacity = 0.3
ticketShape.fillColor = UIColor.backgroundSecondary.cgColor
backgroundView.layer.addSublayer(ticketShape)
self.shapeLayer = ticketShape
}
To ensure the color of my shape and the size of it matches the size of the UIView.
I'm basically asking if there are better approaches to do this