I would like to create a square UIView with a 50px diameter half-circle cutting it at the bottom, like this:
I tried this code:
class CustomView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
let radius = 50.0
let path = UIBezierPath(
arcCenter: CGPoint(
x: bounds.midX,
y: bounds.maxY
),
radius: radius,
startAngle: .pi,
endAngle: 0,
clockwise: true
)
path.addLine(
to: CGPoint(
x: bounds.minX,
y: bounds.maxY
)
)
path.close()
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
layer.mask = maskLayer
}
}
But instead, I am getting this:
How can I get this to work?
Here's a solution that involves overriding draw
and using a UIBezierPath
to draw the shape.
The following can be used in an iOS Swift Playground:
import UIKit
import PlaygroundSupport
class CustomView: UIView {
var radius: CGFloat = 25
var fillColor: UIColor = .blue
override func draw(_ rect: CGRect) {
let shape = UIBezierPath()
shape.move(to: .zero) // top-left
shape.addLine(to: CGPoint(x: 0, y: bounds.height)) // bottom-left
shape.addLine(to: CGPoint(x: bounds.width / 2 - radius, y: bounds.height)) // left side of semi-circle
shape.addArc(withCenter: CGPoint(x: bounds.width / 2, y: bounds.height), radius: radius, startAngle: -.pi, endAngle: 0, clockwise: true) // the semi-circle
shape.addLine(to: CGPoint(x: bounds.width, y: bounds.height)) // bottom-right
shape.addLine(to: CGPoint(x: bounds.width, y: 0)) // top-right
shape.close()
self.fillColor.setFill()
shape.fill()
}
}
let v = CustomView(frame: CGRect(x: 0, y: 0, width: 200, height: 150))
v.backgroundColor = .clear
v.fillColor = .blue
PlaygroundPage.current.liveView = v
This will work for any view size with properties to set the radius of the semi-circle and the fill color.
Here's an update to your code that fixes your use of a mask:
class CustomView2: UIView {
var radius: CGFloat = 50
override func layoutSubviews() {
super.layoutSubviews()
// Create a path with the semi-circle
let path = UIBezierPath(
arcCenter: CGPoint(
x: bounds.midX,
y: bounds.maxY
),
radius: radius,
startAngle: -.pi,
endAngle: 0,
clockwise: true
)
// Append the full rectangle
path.append(UIBezierPath(rect: bounds))
path.close()
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
// Set the fill rule - this allows the rectangle to be filled and the semi-circle to be removed
maskLayer.fillRule = .evenOdd
layer.mask = maskLayer
}
}