(This question is a sequel to this one—I'm trying to figure out how to adapt the code to new circumstances.)
I have a code that produces this shape:

The code is extracted into a function, producing CGPath:
private func makePath(
width: CGFloat,
height: CGFloat,
cornerRadius: CGFloat,
cutOutRadius: CGFloat,
cutOutCornerRadius: CGFloat,
cutOutPosition: CGFloat
) -> CGPath {
let path = CGMutablePath()
// Start at bottom-left before left cut-out.
path.move(to: CGPoint(x: .zero, y: height - cornerRadius))
// Up left edge to left cut-out (rounded corner).
path.addArc(
tangent1End: CGPoint(x: .zero, y: cutOutPosition + cutOutRadius),
tangent2End: CGPoint(x: cutOutRadius, y: cutOutPosition + cutOutRadius),
radius: cutOutCornerRadius
)
// Left cut-out arc (top to bottom, clockwise).
path.addArc(
center: CGPoint(x: cutOutCornerRadius, y: cutOutPosition),
radius: cutOutRadius,
startAngle: .pi / 2.0,
endAngle: 3.0 * .pi / 2.0,
clockwise: true
)
// Down left edge after cut-out to bottom-left (rounded corner).
path.addArc(
tangent1End: CGPoint(x: .zero, y: cutOutPosition - cutOutRadius),
tangent2End: .zero,
radius: cutOutCornerRadius
)
// Bottom-left corner to bottom-right corner.
path.addArc(
tangent1End: .zero,
tangent2End: CGPoint(x: width, y: .zero),
radius: cornerRadius
)
// Bottom-right corner up to right cut-out start (rounded corner).
path.addArc(
tangent1End: CGPoint(x: width, y: .zero),
tangent2End: CGPoint(x: width, y: cutOutPosition - cutOutRadius),
radius: cornerRadius
)
// Up right edge to right cutout (rounded corner).
path.addArc(
tangent1End: CGPoint(x: width, y: cutOutPosition - cutOutRadius),
tangent2End: CGPoint(
x: width - cutOutRadius, y: cutOutPosition - cutOutRadius
),
radius: cutOutCornerRadius
)
// Right cut-out arc (bottom to top, counterclockwise).
path.addArc(
center: CGPoint(x: width - cutOutCornerRadius, y: cutOutPosition),
radius: cutOutRadius,
startAngle: 3.0 * .pi / 2.0,
endAngle: .pi / 2.0,
clockwise: true
)
// Up right edge after cut-out to top-right (rounded corner).
path.addArc(
tangent1End: CGPoint(x: width, y: cutOutPosition + cutOutRadius),
tangent2End: CGPoint(x: width, y: height),
radius: cutOutCornerRadius
)
// Top-right corner to top-left.
path.addArc(
tangent1End: CGPoint(x: width, y: height),
tangent2End: CGPoint(x: .zero, y: height),
radius: cornerRadius
)
// Top-left corner down to starting point.
path.addArc(
tangent1End: CGPoint(x: .zero, y: height),
tangent2End: .zero,
radius: cornerRadius
)
path.closeSubpath()
return path
}
I need to tweak the code so that the centre of the cut-out arc is not moved horizontally towards the shape centre by the size of the radius, but stays on the shape edge. In other words, instead of this:

I need this:

I suppose it would involve even more complicated geometry, and I again fail to figure out how it could be implemented.
Any guidance is much appreciated!
P.S. Here's some code you could paste into Playgrounds along with the function above and have the complete working piece of software:
import PlaygroundSupport
import UIKit
let width = 300.0
let height = 200.0
let view = UIView(
frame: CGRect(origin: .zero, size: CGSize(width: width, height: height))
)
view.backgroundColor = .clear
let shapeLayer = CAShapeLayer()
shapeLayer.frame = view.bounds
shapeLayer.fillColor = UIColor.systemRed.cgColor
shapeLayer.fillRule = .evenOdd
shapeLayer.path = makePath(
width: width,
height: height,
cornerRadius: 20.0,
cutOutRadius: 30.0,
cutOutCornerRadius: 10.0,
cutOutPosition: height * 0.6
)
view.layer.insertSublayer(shapeLayer, at: 0)
PlaygroundPage.current.liveView = view
Notice that the angle that the cutout spans across would be less than 180 degrees. To determine exactly how much less, we can draw a diagram like this:
The angle we want is the angle at the center of the cutout. This is
asin(cutOutCornerRadius / (cutOutRadius + cutOutCornerRadius))
We need to adjust both the start and end angles of the cutout by this much.
Using this, we can also figure out the end angle of the rounded corner. This is just the angle made at the center of the corner, i.e. 90 degrees minus the above expression.
Finally, we also need the horizontal distance between the center of the corner and the center of the cutout, so that we know where all the renters are. This is just simple Pythagorus's theorem.
sqrt(pow(cutOutRadius + cutOutCornerRadius, 2) - pow(cutOutCornerRadius, 2))
Here is some code that draws just one side of the shape,
private func makePath(
cutOutRadius: CGFloat,
cutOutCornerRadius: CGFloat,
cutOutPosition: CGFloat
) -> CGPath {
let path = CGMutablePath()
path.move(to: .zero)
let angleReduced = asin(cutOutCornerRadius / (cutOutRadius + cutOutCornerRadius))
let cornerAngle = .pi / 2 - angleReduced
let distanceBetweenCenters = sqrt(pow(cutOutRadius + cutOutCornerRadius, 2) - pow(cutOutCornerRadius, 2))
path.addLine(to: .init(x: 0, y: cutOutPosition - distanceBetweenCenters))
path.addArc(
center: .init(x: cutOutCornerRadius, y: cutOutPosition - distanceBetweenCenters),
radius: cutOutCornerRadius,
startAngle: .pi,
endAngle: .pi - cornerAngle,
clockwise: true
)
path.addArc(
center: .init(x: 0, y: cutOutPosition),
radius: cutOutRadius,
startAngle: 3 * .pi / 2 + angleReduced,
endAngle: .pi / 2 - angleReduced,
clockwise: false
)
path.addArc(
center: .init(x: cutOutCornerRadius, y: cutOutPosition + distanceBetweenCenters),
radius: cutOutCornerRadius,
startAngle: .pi + cornerAngle,
endAngle: .pi,
clockwise: true
)
path.addLine(to: .init(x: 0, y: path.currentPoint.y + 100))
return path
}
makePath(cutOutRadius: 100, cutOutCornerRadius: 20, cutOutPosition: 250)
By adjusting the start/end angles and clockwise:, you can adapt this into your existing code:
private func makePath(
width: CGFloat,
height: CGFloat,
cornerRadius: CGFloat,
cutOutRadius: CGFloat,
cutOutCornerRadius: CGFloat,
cutOutPosition: CGFloat
) -> CGPath {
let path = CGMutablePath()
let angleReduced = asin(cutOutCornerRadius / (cutOutRadius + cutOutCornerRadius))
let cornerAngle = .pi / 2 - angleReduced
let distanceBetweenCenters = sqrt(pow(cutOutRadius + cutOutCornerRadius, 2) - pow(cutOutCornerRadius, 2))
path.move(to: CGPoint(x: .zero, y: height - cornerRadius))
path.addLine(to: .init(x: 0, y: cutOutPosition + distanceBetweenCenters))
path.addArc(
center: .init(x: cutOutCornerRadius, y: cutOutPosition + distanceBetweenCenters),
radius: cutOutCornerRadius,
startAngle: .pi,
endAngle: .pi + cornerAngle,
clockwise: false
)
path.addArc(
center: .init(x: 0, y: cutOutPosition),
radius: cutOutRadius,
startAngle: .pi / 2 - angleReduced,
endAngle: 3 * .pi / 2 + angleReduced,
clockwise: true
)
path.addArc(
center: .init(x: cutOutCornerRadius, y: cutOutPosition - distanceBetweenCenters),
radius: cutOutCornerRadius,
startAngle: .pi - cornerAngle,
endAngle: .pi,
clockwise: false
)
path.addArc(
tangent1End: .zero,
tangent2End: CGPoint(x: width, y: .zero),
radius: cornerRadius
)
path.addArc(
tangent1End: CGPoint(x: width, y: .zero),
tangent2End: CGPoint(x: width, y: cutOutPosition - cutOutRadius),
radius: cornerRadius
)
path.addLine(to: .init(x: width, y: cutOutPosition - distanceBetweenCenters))
path.addArc(
center: .init(x: width - cutOutCornerRadius, y: cutOutPosition - distanceBetweenCenters),
radius: cutOutCornerRadius,
startAngle: 0,
endAngle: cornerAngle,
clockwise: false
)
path.addArc(
center: .init(x: width, y: cutOutPosition),
radius: cutOutRadius,
startAngle: 3 * .pi / 2 - angleReduced,
endAngle: .pi / 2 + angleReduced,
clockwise: true
)
path.addArc(
center: .init(x: width - cutOutCornerRadius, y: cutOutPosition + distanceBetweenCenters),
radius: cutOutCornerRadius,
startAngle: 3 * .pi / 2,
endAngle: 3 * .pi / 2 + cornerAngle,
clockwise: false
)
path.addArc(
tangent1End: CGPoint(x: width, y: height),
tangent2End: CGPoint(x: .zero, y: height),
radius: cornerRadius
)
path.addArc(
tangent1End: CGPoint(x: .zero, y: height),
tangent2End: .zero,
radius: cornerRadius
)
path.closeSubpath()
return path
}