You're running into a couple issues here...
First, there are two curve types for .cornerRadius:
.round -- the default. Works great for making a "round" view
.continuous -- a more pleasing visual curve, best for "rounded corners"
To demonstrate the difference, here is a green view on top of a red view, where the red view uses .round and the green view uses .continuous:

Note that if we set both to .continuous, we get this:

A UIBezierPath(roundedRect: ...) uses the .continuous curve, so if we mask the green view (instead of setting its .cornerRadius) and use the default curve for the red view, it looks the same as the first example:

And if we mask the green view and set the red view to .continuous we get the same result as the second example:

If you're looking closely, you see a faint red "edge" -- this is due to UIKit anti-aliasing. If we set the "green view" background color to white, it's very obvious:

But, that's a different issue, which will probably not affect what you're trying to do.
The next part is trying to "outline" the view. As you already know, the path's stroke gets set to the centerline of the curve:

We have half of the stroke width inside, and half of the stroke width outside.
So, the attempt to "fix" that is to inset the outline view by 1/2 of the stroke width ... but, again as you've seen, we end up with this:

That's because the stroke ends up with 3 Different Radii!
Let's use a corner radius of 40 and a stroke width of 16 to make it easier to see.
Here it is with equal sized views:

If we simply inset the "outline view" by 1/2 the border width and don't change the radius, we get this:

So, let's adjust the bezier path's corner radius by 1/2 of the stroke width:

and our final result (without the center-line shape layer) is:

Here's some code to play around and inspect what's going on -- each tap steps through what I've described above:
class CornersViewController: UIViewController {
let stepLabel = UILabel()
let infoLabel = UILabel()
let bgViewWidth: CGFloat = 400
let cornerRadius: CGFloat = 40
let borderWidth: CGFloat = 16
lazy var viewFrame: CGRect = CGRect(x: -200, y: 260, width: bgViewWidth, height: bgViewWidth)
var step: Int = 1
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
stepLabel.translatesAutoresizingMaskIntoConstraints = false
stepLabel.font = .systemFont(ofSize: 12.0, weight: .bold)
//stepLabel.textAlignment = .center
view.addSubview(stepLabel)
infoLabel.translatesAutoresizingMaskIntoConstraints = false
infoLabel.numberOfLines = 0
infoLabel.font = .systemFont(ofSize: 12.0, weight: .light)
view.addSubview(infoLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stepLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
stepLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
stepLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
infoLabel.topAnchor.constraint(equalTo: stepLabel.bottomAnchor, constant: 8.0),
infoLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
infoLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
let t = UITapGestureRecognizer(target: self, action: #selector(gotTap(_:)))
view.addGestureRecognizer(t)
nextStep()
}
@objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
nextStep()
}
func nextStep() {
// remove existing example views, but not the "info" label
view.subviews.forEach { v in
if !(v is UILabel) {
v.removeFromSuperview()
}
}
stepLabel.text = "Step: \(step) of \(infoStrings.count)"
infoLabel.text = infoStrings[step - 1]
// red: .cornerCurve = .round (default)
// green: .cornerCurve = .continuous
if step == 1 {
let redView = UIView(frame: viewFrame)
view.addSubview(redView)
redView.backgroundColor = UIColor.red
redView.layer.cornerRadius = cornerRadius
let greenView = UIView(frame: viewFrame)
view.addSubview(greenView)
greenView.backgroundColor = UIColor.green
greenView.layer.cornerRadius = cornerRadius
greenView.layer.cornerCurve = .continuous
}
// red: .cornerCurve = .continuous
// green: .cornerCurve = .continuous
if step == 2 {
let redView = UIView(frame: viewFrame)
view.addSubview(redView)
redView.backgroundColor = UIColor.red
redView.layer.cornerRadius = cornerRadius
redView.layer.cornerCurve = .continuous
let greenView = UIView(frame: viewFrame)
view.addSubview(greenView)
greenView.backgroundColor = UIColor.green
greenView.layer.cornerRadius = cornerRadius
greenView.layer.cornerCurve = .continuous
}
// red: .cornerCurve = .round (default)
// green: masked with UIBezierPath(roundedRect: ...)
if step == 3 {
let redView = UIView(frame: viewFrame)
view.addSubview(redView)
redView.backgroundColor = UIColor.red
redView.layer.cornerRadius = cornerRadius
let greenView = UIView(frame: viewFrame)
view.addSubview(greenView)
greenView.backgroundColor = UIColor.green
let maskLayer = CAShapeLayer()
let maskBez = UIBezierPath(roundedRect: greenView.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
maskLayer.path = maskBez.cgPath
greenView.layer.mask = maskLayer
}
// red: .cornerCurve = .continuous
// green: masked with UIBezierPath(roundedRect: ...)
if step == 4 {
let redView = UIView(frame: viewFrame)
view.addSubview(redView)
redView.backgroundColor = UIColor.red
redView.layer.cornerRadius = cornerRadius
redView.layer.cornerCurve = .continuous
let greenView = UIView(frame: viewFrame)
view.addSubview(greenView)
greenView.backgroundColor = UIColor.green
let maskLayer = CAShapeLayer()
let maskBez = UIBezierPath(roundedRect: greenView.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
maskLayer.path = maskBez.cgPath
greenView.layer.mask = maskLayer
}
// red: .cornerCurve = .continuous
// bordered: sublayer with UIBezierPath(roundedRect: ...)
// clear fill, 30%-black stroke, lineWidth == borderWidth
if step == 5 {
let redView = UIView(frame: viewFrame)
view.addSubview(redView)
redView.backgroundColor = UIColor.red
redView.layer.cornerRadius = cornerRadius
redView.layer.cornerCurve = .continuous
let borderedView = UIView(frame: viewFrame)
view.addSubview(borderedView)
borderedView.backgroundColor = UIColor.clear
borderedView.layer.cornerRadius = cornerRadius
borderedView.layer.cornerCurve = .continuous
let borderLayer = CAShapeLayer()
let roundPath = UIBezierPath(roundedRect: borderedView.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
borderLayer.path = roundPath.cgPath
borderLayer.lineWidth = borderWidth
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.black.withAlphaComponent(0.3).cgColor
borderedView.layer.addSublayer(borderLayer)
}
// red: .cornerCurve = .continuous
// bordered: sublayer with UIBezierPath(roundedRect: ...)
// clear fill, 30%-black stroke, lineWidth == borderWidth
// frame inset by 1/2 borderWidth
if step == 6 {
let redView = UIView(frame: viewFrame)
view.addSubview(redView)
redView.backgroundColor = UIColor.red
redView.layer.cornerRadius = cornerRadius
redView.layer.cornerCurve = .continuous
let borderedView = UIView(frame: viewFrame)
view.addSubview(borderedView)
borderedView.backgroundColor = UIColor.red
borderedView.layer.cornerRadius = cornerRadius
borderedView.layer.cornerCurve = .continuous
let borderLayer = CAShapeLayer()
let rect = borderedView.bounds.insetBy(dx: borderWidth * 0.5, dy: borderWidth * 0.5)
let roundPath = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
borderLayer.path = roundPath.cgPath
borderLayer.lineWidth = borderWidth
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.black.withAlphaComponent(0.3).cgColor
borderedView.layer.addSublayer(borderLayer)
}
// red: .cornerCurve = .continuous
// bordered: sublayer with UIBezierPath(roundedRect: ...)
// clear fill, 30%-black stroke, lineWidth == borderWidth
// frame inset by 1/2 borderWidth
// showing border centerLine
if step == 7 {
let redView = UIView(frame: viewFrame)
view.addSubview(redView)
redView.backgroundColor = UIColor.red
redView.layer.cornerRadius = cornerRadius
redView.layer.cornerCurve = .continuous
let borderedView = UIView(frame: viewFrame)
view.addSubview(borderedView)
borderedView.backgroundColor = UIColor.red
borderedView.layer.cornerRadius = cornerRadius
borderedView.layer.cornerCurve = .continuous
let borderLayer = CAShapeLayer()
let rect = borderedView.bounds.insetBy(dx: borderWidth * 0.5, dy: borderWidth * 0.5)
let roundPath = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
borderLayer.path = roundPath.cgPath
borderLayer.lineWidth = borderWidth
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.black.withAlphaComponent(0.3).cgColor
borderedView.layer.addSublayer(borderLayer)
let centerLineLayer = CAShapeLayer()
centerLineLayer.path = roundPath.cgPath
centerLineLayer.lineWidth = 1
centerLineLayer.fillColor = UIColor.clear.cgColor
centerLineLayer.strokeColor = UIColor.cyan.cgColor
borderedView.layer.addSublayer(centerLineLayer)
}
// red: .cornerCurve = .continuous
// bordered: sublayer with UIBezierPath(roundedRect: ...)
// clear fill, 30%-black stroke, lineWidth == borderWidth
// frame inset by 1/2 borderWidth
// radius adjusted by 1/2 borderWidth
// showing border centerLine
if step == 8 {
let redView = UIView(frame: viewFrame)
view.addSubview(redView)
redView.backgroundColor = UIColor.red
redView.layer.cornerRadius = cornerRadius
redView.layer.cornerCurve = .continuous
let borderedView = UIView(frame: viewFrame)
view.addSubview(borderedView)
borderedView.backgroundColor = UIColor.red
borderedView.layer.cornerRadius = cornerRadius
borderedView.layer.cornerCurve = .continuous
let borderLayer = CAShapeLayer()
let rect = borderedView.bounds.insetBy(dx: borderWidth * 0.5, dy: borderWidth * 0.5)
let adjustedRadius = cornerRadius - (borderWidth * 0.5)
let roundPath = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: adjustedRadius, height: adjustedRadius))
borderLayer.path = roundPath.cgPath
borderLayer.lineWidth = borderWidth
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.black.withAlphaComponent(0.3).cgColor
borderedView.layer.addSublayer(borderLayer)
let centerLineLayer = CAShapeLayer()
centerLineLayer.path = roundPath.cgPath
centerLineLayer.lineWidth = 1
centerLineLayer.fillColor = UIColor.clear.cgColor
centerLineLayer.strokeColor = UIColor.cyan.cgColor
borderedView.layer.addSublayer(centerLineLayer)
}
// red: .cornerCurve = .continuous
// bordered: sublayer with UIBezierPath(roundedRect: ...)
// clear fill, 30%-black stroke, lineWidth == borderWidth
// frame inset by 1/2 borderWidth
// radius adjusted by 1/2 borderWidth
if step == 9 {
let redView = UIView(frame: viewFrame)
view.addSubview(redView)
redView.backgroundColor = UIColor.red
redView.layer.cornerRadius = cornerRadius
redView.layer.cornerCurve = .continuous
let borderedView = UIView(frame: viewFrame)
view.addSubview(borderedView)
borderedView.backgroundColor = UIColor.red
borderedView.layer.cornerRadius = cornerRadius
borderedView.layer.cornerCurve = .continuous
let borderLayer = CAShapeLayer()
let rect = borderedView.bounds.insetBy(dx: borderWidth * 0.5, dy: borderWidth * 0.5)
let adjustedRadius = cornerRadius - (borderWidth * 0.5)
let roundPath = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: adjustedRadius, height: adjustedRadius))
borderLayer.path = roundPath.cgPath
borderLayer.lineWidth = borderWidth
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.black.withAlphaComponent(0.3).cgColor
borderedView.layer.addSublayer(borderLayer)
}
// red: .cornerCurve = .continuous
// white: .cornerCurve = .continuous
if step == 10 {
let redView = UIView(frame: viewFrame)
view.addSubview(redView)
redView.backgroundColor = UIColor.red
redView.layer.cornerRadius = cornerRadius
redView.layer.cornerCurve = .continuous
let whiteView = UIView(frame: viewFrame)
view.addSubview(whiteView)
whiteView.backgroundColor = UIColor.white
whiteView.layer.cornerRadius = cornerRadius
whiteView.layer.cornerCurve = .continuous
}
step += 1
if step > infoStrings.count {
step = 1
}
}
let infoStrings: [String] = [
"redView:\n .cornerCurve = .round (default)\n\ngreenView:\n .cornerCurve = .continuous",
"redView:\n .cornerCurve = .continuous\n\ngreenView:\n .cornerCurve = .continuous",
"redView:\n .cornerCurve = .round (default)\n\ngreenView:\n masked with UIBezierPath(roundedRect: ...)",
"redView:\n .cornerCurve = .continuous\n\ngreenView:\n masked with UIBezierPath(roundedRect: ...)",
"redView:\n .cornerCurve = .continuous\n\nborderedView:\n sublayer with UIBezierPath(roundedRect: ...)\n clear fill, 30%-black stroke, lineWidth == borderWidth",
"redView:\n .cornerCurve = .continuous\n\nborderedView:\n sublayer with UIBezierPath(roundedRect: ...)\n clear fill, 30%-black stroke, lineWidth == borderWidth\n frame inset by 1/2 borderWidth",
"redView:\n .cornerCurve = .continuous\n\nborderedView:\n sublayer with UIBezierPath(roundedRect: ...)\n clear fill, 30%-black stroke, lineWidth == borderWidth\n frame inset by 1/2 borderWidth\n showing border center line",
"redView:\n .cornerCurve = .continuous\n\nborderedView:\n sublayer with UIBezierPath(roundedRect: ...)\n clear fill, 30%-black stroke, lineWidth == borderWidth\n frame inset by 1/2 borderWidth\n radius adjusted by 1/2 borderWidth\n showing border center line",
"redView:\n .cornerCurve = .continuous\n\nborderedView:\n sublayer with UIBezierPath(roundedRect: ...)\n clear fill, 30%-black stroke, lineWidth == borderWidth\n frame inset by 1/2 borderWidth\n radius adjusted by 1/2 borderWidth\n final result",
"redView:\n .cornerCurve = .continuous\n\nwhiteView:\n .cornerCurve = .continuous\n\n Showing the Anti-Aliasing...",
]
}