I am drawing an arc that is 270° and has rounded coners on both ends. This is working fine, however now I would like to change my arch to be 315° (-45°), but then my corner calculation won't work.
I have tried to calculate this different ways but can't seem to find the formula for making a general function to add rounded corners to my arc when the start and ends are not vertical och horizontal.
This is my playground code:
import UIKit
import PlaygroundSupport
class ArcView: UIView {
private var strokeWidth: CGFloat {
return CGFloat(min(self.bounds.width, self.bounds.height) * 0.25)
}
private let cornerRadius: CGFloat = 10
override open func draw(_ rect: CGRect) {
super.draw(rect)
backgroundColor = UIColor.white
drawNormalCircle()
}
func drawNormalCircle() {
let strokeWidth = CGFloat(min(self.bounds.width, self.bounds.height) * 0.25)
let innerRadius = (min(self.bounds.width, self.bounds.height) - strokeWidth*2) / 2.0
let outerRadius = (min(self.bounds.width, self.bounds.height)) / 2.0
var endAngle: CGFloat = 270.0
let bezierPath = UIBezierPath(arcCenter: self.center, radius: outerRadius, startAngle: 0, endAngle: endAngle * .pi / 180, clockwise: true)
var point = bezierPath.currentPoint
point.y += cornerRadius
let arc = UIBezierPath(arcCenter: point, radius: cornerRadius, startAngle: 180 * .pi / 180, endAngle: 270 * .pi / 180, clockwise: true)
arc.apply(CGAffineTransform(rotationAngle: (360 - endAngle) * .pi / 180))
var firstCenter = bezierPath.currentPoint
firstCenter.y += cornerRadius
bezierPath.addArc(withCenter: firstCenter, radius: cornerRadius , startAngle: 270 * .pi / 180 , endAngle: 0, clockwise: true)
bezierPath.addLine(to: CGPoint(x: bezierPath.currentPoint.x, y: strokeWidth - cornerRadius))
var secondCenter = bezierPath.currentPoint
secondCenter.x -= cornerRadius
bezierPath.addArc(withCenter: secondCenter, radius: cornerRadius , startAngle: 0, endAngle: 90 * .pi / 180, clockwise: true)
bezierPath.addArc(withCenter: self.center, radius: innerRadius, startAngle: 270 * .pi / 180, endAngle: 0, clockwise: false)
var thirdCenter = bezierPath.currentPoint
thirdCenter.x += cornerRadius
bezierPath.addArc(withCenter: thirdCenter, radius: cornerRadius , startAngle: 180 * .pi / 180, endAngle: 270 * .pi / 180, clockwise: true)
bezierPath.addLine(to: CGPoint(x: bezierPath.currentPoint.x + strokeWidth - (cornerRadius * 2), y: bezierPath.currentPoint.y))
var fourthCenter = bezierPath.currentPoint
fourthCenter.y += cornerRadius
bezierPath.addArc(withCenter: fourthCenter, radius: cornerRadius , startAngle: 270 * .pi / 180, endAngle: 0, clockwise: true)
bezierPath.close()
let backgroundLayer = CAShapeLayer()
backgroundLayer.path = bezierPath.cgPath
backgroundLayer.strokeColor = UIColor.red.cgColor
backgroundLayer.lineWidth = 2
backgroundLayer.fillColor = UIColor.lightGray.cgColor
self.layer.addSublayer(backgroundLayer)
}
}
let arcView = ArcView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
PlaygroundPage.current.liveView = arcView
The problem for me is how to calculate the arc center for the corners when the corner is not a given X - CornerRadius, or Y + corner Radius, which it is in perfectly horizontal or vertical cases. How can I have rounded corners when the arc is 315°.
That's my solution. It's just simple trigonometry
/// Create a path made with 6 small subpaths
///
/// - Parameters:
/// - startAngle: the start angle of the path in cartesian plane angles system
/// - endAngle: the end angle of the path in cartesian plane angles system
/// - outerRadius: the radius of the outer circle in % relative to the size of the view that holds it
/// - innerRadius: the radius of the inner circle in % relative to the size of the view that holds it
/// - cornerRadius: the corner radius of the edges
///
/// - Returns: the path itself
func createPath(from startAngle: Double, to endAngle: Double,
outerRadius:CGFloat, innerRadius:CGFloat,
cornerRadius: CGFloat) -> UIBezierPath {
let path = UIBezierPath()
let maxDim = min(view.frame.width, view.frame.height)
let oRadius: CGFloat = maxDim/2 * outerRadius
let iRadius: CGFloat = maxDim/2 * innerRadius
let center = CGPoint.init(x: view.frame.width/2, y: view.frame.height/2)
let startAngle = deg2rad(360.0 - startAngle)
let endAngle = deg2rad(360.0 - endAngle)
// Outer Finish Center point
let ofcX = center.x + (oRadius - cornerRadius) * CGFloat(cos(endAngle - deg2rad(360)))
let ofcY = center.y + (oRadius - cornerRadius) * CGFloat(sin(endAngle - deg2rad(360)))
// Inner Finish Center point
let ifcX = center.x + (iRadius + cornerRadius) * CGFloat(cos(endAngle - deg2rad(360)))
let ifcY = center.y + (iRadius + cornerRadius) * CGFloat(sin(endAngle - deg2rad(360)))
// Inner Starting Center point
let iscX = center.x + (iRadius + cornerRadius) * CGFloat(cos(startAngle - deg2rad(360)))
let iscY = center.y + (iRadius + cornerRadius) * CGFloat(sin(startAngle - deg2rad(360)))
// Outer Starting Center point
let oscX = center.x + (oRadius - cornerRadius) * CGFloat(cos(startAngle - deg2rad(360)))
let oscY = center.y + (oRadius - cornerRadius) * CGFloat(sin(startAngle - deg2rad(360)))
// Outer arch
path.addArc(withCenter: center, radius: oRadius,
startAngle: startAngle, endAngle: endAngle,
clockwise: true)
// Rounded outer finish
path.addArc(withCenter: CGPoint(x: ofcX, y: ofcY), radius: cornerRadius,
startAngle: endAngle, endAngle:endAngle + deg2rad(90),
clockwise: true)
// Rounded inner finish
path.addArc(withCenter: CGPoint(x: ifcX, y: ifcY), radius: cornerRadius,
startAngle: endAngle + deg2rad(90), endAngle: endAngle + deg2rad(180),
clockwise: true)
// Inner arch
path.addArc(withCenter: center, radius: iRadius,
startAngle: endAngle, endAngle: startAngle,
clockwise: false)
// Rounded inner start
path.addArc(withCenter: CGPoint(x: iscX, y: iscY), radius: cornerRadius,
startAngle: startAngle + deg2rad(180), endAngle: startAngle + deg2rad(270),
clockwise: true)
// Rounded outer start
path.addArc(withCenter: CGPoint(x: oscX, y: oscY), radius: cornerRadius,
startAngle: startAngle + deg2rad(270), endAngle: startAngle,
clockwise: true)
return path
}
func deg2rad(_ number: Double) -> CGFloat {
return CGFloat(number * .pi / 180)
}
Usage:
@IBOutlet weak var mainView: UIView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let borderLayer = CAShapeLayer()
borderLayer.path = createPath(from: 30, to: 120, outerRadius: 0.9, innerRadius: 0.3, cornerRadius: 5).cgPath
borderLayer.strokeColor = UIColor.orange.cgColor
borderLayer.fillColor = UIColor.orange.cgColor
borderLayer.lineWidth = 0.0
mainView.layer.addSublayer(borderLayer)
}