I am trying to make the above shape with UIKit. The small independent circle I am not concerned about, however, the semi-circle on the bottom left of the rounded rect is where I am getting stuck, especially given the border and the fact that the background color has a 70% alpha component (meaning it is partially transparent.)
I tried at first to just create two UIViews.
Directly using views resulted in:
Given that, I decided to try and override the UIView draw function. here is my attempt:
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let fillColor = configuration.isTransparent
? UIColor.primaryPurple.withAlphaComponent(0.7)
: UIColor.primaryPurple
let width = rect.width
let height = rect.height
let roundedRect = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: width, height: height - 8), cornerRadius: 24)
let circle = UIBezierPath(ovalIn: CGRect(x: 24, y: height - 24, width: 24, height: 24))
// Combine into one path
let combinedPath = UIBezierPath()
combinedPath.append(roundedRect)
combinedPath.append(circle)
combinedPath.usesEvenOddFillRule = false // Important: treat as single shape
// Fill
context.addPath(combinedPath.cgPath)
context.setFillColor(fillColor.cgColor)
context.fillPath()
// Stroke
context.addPath(combinedPath.cgPath)
context.setStrokeColor(UIColor.primaryPurple.cgColor)
context.setLineWidth(1)
context.setLineJoin(.round)
context.strokePath()
}
This resulted in:
truthfully, I am not sure where to go from here
Another approach which you may find a little more flexible, we can use the .union
method of CGPath
:
class AnotherCustomView: UIView {
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let strokeColor: UIColor = .init(red: 0.367, green: 0.387, blue: 0.771, alpha: 1.0)
let fillColor: UIColor = strokeColor.withAlphaComponent(0.7)
// if we want to inset the drawing...
// we'll use 8 here to better show the shape
// for no inset, use 0
var rectR: CGRect = rect.insetBy(dx: 8, dy: 8)
let x = rectR.minX
let y = rectR.minY
let width = rectR.width
let height = rectR.height - 8
let roundedRect = CGPath(roundedRect: .init(x: x, y: y, width: width, height: height), cornerWidth: 24, cornerHeight: 24, transform: nil)
let circle = CGPath(ellipseIn: .init(x: x + 24, y: y + height - 16, width: 24, height: 24), transform: nil)
let combinedPath = roundedRect.union(circle)
// Fill
context.addPath(combinedPath)
context.setFillColor(fillColor.cgColor)
context.fillPath()
// Stroke
context.addPath(combinedPath)
context.setStrokeColor(strokeColor.cgColor)
context.setLineWidth(1)
context.setLineJoin(.round)
context.strokePath()
}
}
With this example controller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let v1 = AnotherCustomView(frame: .init(x: 40, y: 80, width: 160, height: 120))
view.addSubview(v1)
v1.backgroundColor = .black
}
}
we get this output: