iOS: Is Possible to Rounder Radius with Different Value in Each Corner

IOS: Is possible to rounder radius with different value in each corner

UIBezierPath has no such method. But you can use bunch of addLineToPoint and addArcWithCenter methods to do it:

let minx = CGRectGetMinX(rect)
let miny = CGRectGetMinY(rect)
let maxx = CGRectGetMaxX(rect)
let maxy = CGRectGetMaxY(rect)

let path = UIBezierPath()
path.moveToPoint(CGPointMake(minx + topLeftRadius, miny))
path.addLineToPoint(CGPointMake(maxx - topRightRadius, miny))
path.addArcWithCenter(CGPointMake(maxx - topRightRadius, miny + topRightRadius), radius: topRightRadius, startAngle:3 * M_PI_2, endAngle: 0, clockwise: true)
path.addLineToPoint(CGPointMake(maxx, maxy - bottomRightRadius))
path.addArcWithCenter(CGPointMake(maxx - bottomRightRadius, maxy - bottomRightRadius), radius: bottomRightRadius, startAngle: 0, endAngle: M_PI_2, clockwise: true)
path.addLineToPoint(CGPointMake(minx + bottomLeftRadius, maxy))
path.addArcWithCenter(CGPointMake(minx + bottomLeftRadius, maxy - bottomLeftRadius), radius: bottomLeftRadius, startAngle: M_PI_2, endAngle: M_PI, clockwise: true)
path.addLineToPoint(CGPointMake(minx, miny + topLeftRadius))
path.addArcWithCenter(CGPointMake(minx + topLeftRadius, miny + topLeftRadius), radius: topLeftRadius, startAngle: M_PI, endAngle: 3 * M_PI_2, clockwise: true)
path.closePath()

For Obj-C:

CGFloat topLeftRadius = 20;
CGFloat topRightRadius = 10;
CGFloat bottomRightRadius = 5;
CGFloat bottomLeftRadius = 5;

CGFloat minx = CGRectGetMinX(self.messageView.bounds);
CGFloat miny = CGRectGetMinY(self.messageView.bounds);
CGFloat maxx = CGRectGetMaxX(self.messageView.bounds);
CGFloat maxy = CGRectGetMaxY(self.messageView.bounds);

UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(minx + topLeftRadius, miny)];
[path addLineToPoint:CGPointMake(maxx - topRightRadius, miny)];
[path addArcWithCenter:CGPointMake(maxx - topRightRadius, miny + topRightRadius) radius: topRightRadius startAngle: 3 * M_PI_2 endAngle: 0 clockwise: YES];
[path addLineToPoint:CGPointMake(maxx, maxy - bottomRightRadius)];
[path addArcWithCenter:CGPointMake(maxx - bottomRightRadius, maxy - bottomRightRadius) radius: bottomRightRadius startAngle: 0 endAngle: M_PI_2 clockwise: YES];
[path addLineToPoint:CGPointMake(minx + bottomLeftRadius, maxy)];
[path addArcWithCenter:CGPointMake(minx + bottomLeftRadius, maxy - bottomLeftRadius) radius: bottomLeftRadius startAngle: M_PI_2 endAngle:M_PI clockwise: YES];
[path addLineToPoint:CGPointMake(minx, miny + topLeftRadius)];
[path addArcWithCenter:CGPointMake(minx + topLeftRadius, miny + topLeftRadius) radius: topLeftRadius startAngle: M_PI endAngle:3 * M_PI_2 clockwise: YES];
[path closePath];

CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.path = path.CGPath;
self.messageView.layer.mask = maskLayer;

Round corners with different radius

I don't believe there is any system call to create a rounded rectangle with different corner radii for each corner. The function UIBezierPath(roundedRect:byRoundingCorners:cornerRadii:) that you are using will create a rounded rectangle with some corners rounded and some not, but the corners you ask to round will all have the same radius.

If you want to create a path with different corners rounded to different radii I'm pretty sure you'll have to build such a path yourself using arcs for each corner and line segments for the flat sides of the rounded rectangle. (You'd draw a closed path composed of an arc with an angle of ∏/2, then a line segment, and repeat that 4 times. The center of each arc would be that corner of the rectangle, inset by the corner radius in both dimensions. It helps to diagram it out on graph paper.)

EDIT:

With a little digging I found this post that includes code to generate a rounded rect with a different corner radius for each corner:

IOS: Is possible to rounder radius with different value in each corner

Different cornerRadius for each corner Swift 3 - iOS

You could set the default layer.cornerRadius to the smallest value and then set the layer mask's border to the bigger value.

let demoView = UIView(frame: CGRect(x: 100, y: 200, width: 100, height: 100))
demoView.backgroundColor = UIColor.red
demoView.layer.cornerRadius = 3.0

let maskPath = UIBezierPath(roundedRect: demoView.bounds,
byRoundingCorners: [.topLeft, .topRight, .bottomLeft],
cornerRadii: CGSize(width: 18.0, height: 0.0))

let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.cgPath
demoView.layer.mask = maskLayer
view.addSubview(demoView)

Giving UIView rounded corners

Try this

#import <QuartzCore/QuartzCore.h> // not necessary for 10 years now  :)

...

view.layer.cornerRadius = 5;
view.layer.masksToBounds = true;

Note: If you are trying to apply rounded corners to a UIViewController's view, it should not be applied in the view controller's constructor, but rather in -viewDidLoad, after view is actually instantiated.

iOS UIView - Different rounding radius for each of the 4 corners

Give this a try - you can paste it directly into a Playground page and see how it works:

import UIKit
import PlaygroundSupport

extension Int {
var degreesToRadians: Double { return Double(self) * .pi / 180 }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}

class VariableCornerRadiusView: UIView {

var upperLeftCornerRadius:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

var upperRightCornerRadius:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

var lowerLeftCornerRadius:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

var lowerRightCornerRadius:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

func layoutMask() -> Void {

var pt = CGPoint.zero

let myBezier = UIBezierPath()

pt.x = upperLeftCornerRadius
pt.y = 0

myBezier.move(to: pt)

pt.x = bounds.width - upperRightCornerRadius
pt.y = 0

myBezier.addLine(to: pt)

pt.x = bounds.width - upperRightCornerRadius
pt.y = upperRightCornerRadius

myBezier.addArc(withCenter: pt, radius: upperRightCornerRadius, startAngle: CGFloat(270.degreesToRadians), endAngle: CGFloat(0.degreesToRadians), clockwise: true)

pt.x = bounds.width
pt.y = bounds.height - lowerRightCornerRadius

myBezier.addLine(to: pt)

pt.x = bounds.width - lowerRightCornerRadius
pt.y = bounds.height - lowerRightCornerRadius

myBezier.addArc(withCenter: pt, radius: lowerRightCornerRadius, startAngle: CGFloat(0.degreesToRadians), endAngle: CGFloat(90.degreesToRadians), clockwise: true)

pt.x = lowerLeftCornerRadius
pt.y = bounds.height

myBezier.addLine(to: pt)

pt.x = lowerLeftCornerRadius
pt.y = bounds.height - lowerLeftCornerRadius

myBezier.addArc(withCenter: pt, radius: lowerLeftCornerRadius, startAngle: CGFloat(90.degreesToRadians), endAngle: CGFloat(180.degreesToRadians), clockwise: true)

pt.x = 0
pt.y = upperLeftCornerRadius

myBezier.addLine(to: pt)

pt.x = upperLeftCornerRadius
pt.y = upperLeftCornerRadius

myBezier.addArc(withCenter: pt, radius: upperLeftCornerRadius, startAngle: CGFloat(180.degreesToRadians), endAngle: CGFloat(270.degreesToRadians), clockwise: true)

myBezier.close()

let maskForPath = CAShapeLayer()
maskForPath.path = myBezier.cgPath
layer.mask = maskForPath

}

override func layoutSubviews() {
super.layoutSubviews()
self.layoutMask()
}

}

var testSize = CGSize(width: 200, height: 200)

// set up an orange view to hold it...
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 260))

containerView.backgroundColor = UIColor.orange

// create a VariableCornerRadiusView, just a little smaller than the container view
let TestView = VariableCornerRadiusView(frame: containerView.bounds.insetBy(dx: 20, dy: 20))

// set different radius for each corner
TestView.upperLeftCornerRadius = 20.0
TestView.upperRightCornerRadius = 40.0
TestView.lowerRightCornerRadius = 60.0
TestView.lowerLeftCornerRadius = 80.0

// give it a blue background
TestView.backgroundColor = UIColor.blue

// add it to the container
containerView.addSubview(TestView)

// show it
PlaygroundPage.current.liveView = containerView

Result should look like this:

Sample Image


Edit another approach... Using a filled shape layer (instead of a mask) and a shadow:

import UIKit
import PlaygroundSupport

extension Int {
var degreesToRadians: Double { return Double(self) * .pi / 180 }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}

class VariableCornerRadiusShadowView: UIView {

var upperLeftCornerRadius:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

var upperRightCornerRadius:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

var lowerLeftCornerRadius:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

var lowerRightCornerRadius:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

var fillColor: UIColor = .white {
didSet {
self.setNeedsLayout()
}
}

var shadowColor: UIColor = .black {
didSet {
self.setNeedsLayout()
}
}

var shadowOffset: CGSize = CGSize(width: 0.0, height: 2.0) {
didSet {
self.setNeedsLayout()
}
}

var shadowOpacity: Float = 0.5 {
didSet {
self.setNeedsLayout()
}
}

var shadowRadius: CGFloat = 8.0 {
didSet {
self.setNeedsLayout()
}
}

let theShapeLayer = CAShapeLayer()

override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}

func commonInit() -> Void {
layer.addSublayer(theShapeLayer)
}

func layoutShape() -> Void {

var pt = CGPoint.zero

let myBezier = UIBezierPath()

pt.x = upperLeftCornerRadius
pt.y = 0

myBezier.move(to: pt)

pt.x = bounds.width - upperRightCornerRadius
pt.y = 0

myBezier.addLine(to: pt)

pt.x = bounds.width - upperRightCornerRadius
pt.y = upperRightCornerRadius

myBezier.addArc(withCenter: pt, radius: upperRightCornerRadius, startAngle: CGFloat(270.degreesToRadians), endAngle: CGFloat(0.degreesToRadians), clockwise: true)

pt.x = bounds.width
pt.y = bounds.height - lowerRightCornerRadius

myBezier.addLine(to: pt)

pt.x = bounds.width - lowerRightCornerRadius
pt.y = bounds.height - lowerRightCornerRadius

myBezier.addArc(withCenter: pt, radius: lowerRightCornerRadius, startAngle: CGFloat(0.degreesToRadians), endAngle: CGFloat(90.degreesToRadians), clockwise: true)

pt.x = lowerLeftCornerRadius
pt.y = bounds.height

myBezier.addLine(to: pt)

pt.x = lowerLeftCornerRadius
pt.y = bounds.height - lowerLeftCornerRadius

myBezier.addArc(withCenter: pt, radius: lowerLeftCornerRadius, startAngle: CGFloat(90.degreesToRadians), endAngle: CGFloat(180.degreesToRadians), clockwise: true)

pt.x = 0
pt.y = upperLeftCornerRadius

myBezier.addLine(to: pt)

pt.x = upperLeftCornerRadius
pt.y = upperLeftCornerRadius

myBezier.addArc(withCenter: pt, radius: upperLeftCornerRadius, startAngle: CGFloat(180.degreesToRadians), endAngle: CGFloat(270.degreesToRadians), clockwise: true)

myBezier.close()

theShapeLayer.path = myBezier.cgPath
theShapeLayer.fillColor = fillColor.cgColor

layer.shadowRadius = shadowRadius
layer.shadowOffset = shadowOffset
layer.shadowOpacity = shadowOpacity
layer.shadowColor = shadowColor.cgColor

}

override func layoutSubviews() {
super.layoutSubviews()
self.layoutShape()
}

}

var testSize = CGSize(width: 200, height: 200)

// set up an orange "container" view to hold it...
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 260))

containerView.backgroundColor = UIColor.white

let sampleView = VariableCornerRadiusShadowView(frame: containerView.bounds.insetBy(dx: 20, dy: 20))

// set different radius for each corner
sampleView.upperLeftCornerRadius = 20.0
sampleView.upperRightCornerRadius = 40.0
sampleView.lowerRightCornerRadius = 60.0
sampleView.lowerLeftCornerRadius = 80.0

// if we want to adjust defaults
//sampleView.fillColor = .green
//sampleView.shadowOffset = CGSize(width: 2, height: 4)
//sampleView.shadowRadius = 4 // not quite so "fuzzy"
//sampleView.shadowOpacity = 0.8

// add view to container
containerView.addSubview(sampleView)

// show it
PlaygroundPage.current.liveView = containerView

Result with "default" properties:

Sample Image

Result with properties changed to:

.fillColor = .green
.shadowOffset = CGSize(width: 2, height: 4)
.shadowRadius = 4 // not quite so "fuzzy"
.shadowOpacity = 0.8

Sample Image


Edit 2 -

This version is now @IBDesignable, with some properties renamed to show better in IB. Also added border width and color:

@IBDesignable
class VariableCornerRadiusShadowView: UIView {

@IBInspectable
var radTopLeft:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

@IBInspectable
var radTopRright:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

@IBInspectable
var radBotLeft:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

@IBInspectable
var radBotRright:CGFloat = 0 {
didSet {
self.setNeedsLayout()
}
}

@IBInspectable
var fillColor: UIColor = .white {
didSet {
self.setNeedsLayout()
}
}

@IBInspectable
var borderColor: UIColor = .clear {
didSet {
self.setNeedsLayout()
}
}

@IBInspectable
var borderWidth: CGFloat = 0.0 {
didSet {
self.setNeedsLayout()
}
}

@IBInspectable
var shadowColor: UIColor = .black {
didSet {
self.setNeedsLayout()
}
}

@IBInspectable
var shadowXOffset: CGFloat = 0.0 {
didSet {
self.setNeedsLayout()
}
}

@IBInspectable
var shadowYOffset: CGFloat = 0.0 {
didSet {
self.setNeedsLayout()
}
}

@IBInspectable
var shadowOpacity: Float = 0.5 {
didSet {
self.setNeedsLayout()
}
}

@IBInspectable
var shadowRadius: CGFloat = 8.0 {
didSet {
self.setNeedsLayout()
}
}

let theShapeLayer = CAShapeLayer()

override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForInterfaceBuilder() {
commonInit()
}

func commonInit() -> Void {
backgroundColor = .clear
layer.addSublayer(theShapeLayer)
}

func layoutShape() -> Void {

var pt = CGPoint.zero

let myBezier = UIBezierPath()

pt.x = radTopLeft
pt.y = 0

myBezier.move(to: pt)

pt.x = bounds.width - radTopRright
pt.y = 0

myBezier.addLine(to: pt)

pt.x = bounds.width - radTopRright
pt.y = radTopRright

myBezier.addArc(withCenter: pt, radius: radTopRright, startAngle: .pi * 1.5, endAngle: 0, clockwise: true)

pt.x = bounds.width
pt.y = bounds.height - radBotRright

myBezier.addLine(to: pt)

pt.x = bounds.width - radBotRright
pt.y = bounds.height - radBotRright

myBezier.addArc(withCenter: pt, radius: radBotRright, startAngle: 0, endAngle: .pi * 0.5, clockwise: true)

pt.x = radBotLeft
pt.y = bounds.height

myBezier.addLine(to: pt)

pt.x = radBotLeft
pt.y = bounds.height - radBotLeft

myBezier.addArc(withCenter: pt, radius: radBotLeft, startAngle: .pi * 0.5, endAngle: .pi, clockwise: true)

pt.x = 0
pt.y = radTopLeft

myBezier.addLine(to: pt)

pt.x = radTopLeft
pt.y = radTopLeft

myBezier.addArc(withCenter: pt, radius: radTopLeft, startAngle: .pi, endAngle: .pi * 1.5, clockwise: true)

myBezier.close()

theShapeLayer.path = myBezier.cgPath
theShapeLayer.fillColor = fillColor.cgColor

theShapeLayer.strokeColor = borderColor.cgColor
theShapeLayer.lineWidth = borderWidth

layer.shadowRadius = shadowRadius
layer.shadowOffset = CGSize(width: shadowXOffset, height: shadowYOffset)
layer.shadowOpacity = shadowOpacity
layer.shadowColor = shadowColor.cgColor

}

override func layoutSubviews() {
super.layoutSubviews()
self.layoutShape()
}

}

Consistent curves with dynamic corner radius (Swift)?

Here's the best I can do. I don't know if this will be of help, but hopefully it will give you some ideas.

First the code:

class ViewController: UIViewController {

let cornerRadius:CGFloat = 10
let insetValue:CGFloat = 10
var numberOfViews:Int = 0
var myViews = [UIView]()

override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = false
}

override func viewWillLayoutSubviews() {
setNumberOfViews()
createViews()
createViewHierarchy()
addConstraints()
}

func setNumberOfViews() {
var smallerDimension:CGFloat = 0
if view.frame.height < view.frame.width {
smallerDimension = view.frame.height
} else {
smallerDimension = view.frame.width
}
let viewCount = smallerDimension / (insetValue * 2)
numberOfViews = Int(viewCount)
}

func createViews() {
for i in 1...numberOfViews {
switch i % 5 {
case 0:
myViews.append(MyView(UIColor.black, cornerRadius))
case 1:
myViews.append(MyView(UIColor.blue, cornerRadius))
case 2:
myViews.append(MyView(UIColor.red, cornerRadius))
case 3:
myViews.append(MyView(UIColor.yellow, cornerRadius))
case 4:
myViews.append(MyView(UIColor.green, cornerRadius))
default:
break
}
}
}

func createViewHierarchy() {
view.addSubview(myViews[0])
for i in 1...myViews.count-1 {
myViews[i-1].addSubview(myViews[i])
}
}

func addConstraints() {
for view in myViews {
view.topAnchor.constraint(equalTo: (view.superview?.topAnchor)!, constant: insetValue).isActive = true
view.leadingAnchor.constraint(equalTo: (view.superview?.leadingAnchor)!, constant: insetValue).isActive = true
view.trailingAnchor.constraint(equalTo: (view.superview?.trailingAnchor)!, constant: -insetValue).isActive = true
view.bottomAnchor.constraint(equalTo: (view.superview?.bottomAnchor)!, constant: -insetValue).isActive = true
}
}
}

class MyView: UIView {
convenience init(_ backgroundColor:UIColor, _ cornerRadius:CGFloat) {
self.init(frame: CGRect.zero)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = backgroundColor
self.layer.cornerRadius = cornerRadius
}
}

Explanation:

This is fairly simple code. The intent was to create as deeply nested a view hierarchy as possible, and, using auto layout, have two main variables: cornerRadius (the view's corner radius) and insetValue (the "frame's" inset). These two variables can be adjusted for experimenting.

The bulk of the logic is in viewWillLayoutSubviews, where the root view frame size is know. Since I'm using 5 different background colors, I'm calculating how many views can fit in the hierarchy. Then I'm creating them, followed by creating the view hierarchy, and finally I'm adding the constraints.

Experimenting and conclusions:

  • I was able to see what your concern is - yes, if a view's size components are smaller than the corner radius, you end up with inconsistent looking corners. But these values are pretty small - pretty much 10 or less. Most views are unusable at that size. (If I recall even the HIG suggests that a button should be no less than 40 points in size. Sure, even Apple breaks that rule. Still.)

  • If your 'insetValueis sufficiently larger than the corner radius, you should never have an issue. Likewise, using the iMessage scenario, a singleUILabelcontaining text and/or emoticons should have enough height that a noticeablecornerRadius` can be had.

  • The key point to set things like cornerRadius and insetValue is in viewWillLayoutSubviews, when you can decide (1) which is the smaller dimension, height or width, (2) how deeply you can nest views, and (3) how large of a corner radius you can set.

  • Use auto layout! Please note the absolute lack of frames. Other than determining the root view's dimensions at the appropriate time, you can write very compact code without worrying about device size or orientation.

Set specific value to round corners of UIBezierPath

The cornerRadius only adjusts the corners of the bounds of the layer, not of the path. If you want path with rounded corners, you’ll have to stroke that yourself.

There are two approaches to rendering an arc with a particular corner radius:

  1. If rendering a simple solid rendition, one very simple approach is to render two arcs, one clockwise, one counter clockwise, using different radii. The line width of these individual arcs should be twice the desired corner rounding of the final shape. Then, if you render these two arcs with a matching stroke color and fill color, you’ll get the desired shape.

    Here it is, animated so you can see what’s going on:

    Sample Image

    This is the code (without the animation):

    class ArcView: UIView {
    var startAngle: CGFloat = .pi * 3 / 4
    var endAngle: CGFloat = .pi * 5 / 4
    var clockwise: Bool = true

    /// Radius of center of this arc
    var radius: CGFloat = 100

    /// The linewidth of this thick arc
    var lineWidth: CGFloat = 50

    /// The corner radius of this thick arc
    var cornerRadius: CGFloat = 10

    static override var layerClass: AnyClass { return CAShapeLayer.self }
    var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer }

    override func layoutSubviews() {
    super.layoutSubviews()
    updatePath()
    }

    func updatePath() {
    let center = CGPoint(x: bounds.midX, y: bounds.midY)
    let innerRadius = radius - lineWidth / 2 + cornerRadius
    let innerAngularDelta = asin(cornerRadius / innerRadius) * (clockwise ? 1 : -1)
    let outerRadius = radius + lineWidth / 2 - cornerRadius
    let outerAngularDelta = asin(cornerRadius / outerRadius) * (clockwise ? 1 : -1)

    let path = UIBezierPath(arcCenter: center, radius: innerRadius, startAngle: startAngle + innerAngularDelta, endAngle: endAngle - innerAngularDelta, clockwise: clockwise)
    path.addArc(withCenter: center, radius: outerRadius, startAngle: endAngle - outerAngularDelta, endAngle: startAngle + outerAngularDelta, clockwise: !clockwise)
    path.close()

    // configure shapeLayer

    shapeLayer.lineWidth = cornerRadius * 2
    shapeLayer.fillColor = UIColor.blue.cgColor
    shapeLayer.strokeColor = UIColor.blue.cgColor
    shapeLayer.lineJoin = .round
    shapeLayer.path = path.cgPath
    }

    }

    The only trick in the above is how to adjust the starting and ending angles of these inner and outer arcs such that they’ll be perfectly circumscribed by the desired final shape. But a little trigonometry can be used to figure out those angular deltas, as shown above.

  2. The other approach is to define a path for the outline of the desired shape. Calculating the inner arc and outer arcs is similar, but you have to manually calculate the angles for the four rounded corners. It’s just a little trigonometry, but it’s a little hairy:

    class ArcView: UIView {
    var startAngle: CGFloat = .pi * 3 / 4
    var endAngle: CGFloat = .pi * 5 / 4
    var clockwise: Bool = true

    /// Radius of center of this arc
    var radius: CGFloat = 100

    /// The linewidth of this thick arc
    var lineWidth: CGFloat = 100

    /// The corner radius of this thick arc
    var cornerRadius: CGFloat = 10

    static override var layerClass: AnyClass { return CAShapeLayer.self }
    var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer }

    override func layoutSubviews() {
    super.layoutSubviews()
    updatePath()
    }

    func updatePath() {
    let center = CGPoint(x: bounds.midX, y: bounds.midY)
    let innerRadius = radius - lineWidth / 2
    let innerAngularDelta = asin(cornerRadius / (innerRadius + cornerRadius)) * (clockwise ? 1 : -1)
    let outerRadius = radius + lineWidth / 2
    let outerAngularDelta = asin(cornerRadius / (outerRadius - cornerRadius)) * (clockwise ? 1 : -1)

    let path = UIBezierPath(arcCenter: center, radius: innerRadius, startAngle: startAngle + innerAngularDelta, endAngle: endAngle - innerAngularDelta, clockwise: clockwise)

    var angle = endAngle - innerAngularDelta
    var cornerStartAngle = angle + .pi * (clockwise ? 1 : -1)
    var cornerEndAngle = endAngle + .pi / 2 * (clockwise ? 1 : -1)
    var cornerCenter = CGPoint(x: center.x + (innerRadius + cornerRadius) * cos(angle), y: center.y + (innerRadius + cornerRadius) * sin(angle))
    path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise)

    angle = endAngle - outerAngularDelta
    cornerStartAngle = cornerEndAngle
    cornerEndAngle = endAngle - outerAngularDelta
    cornerCenter = CGPoint(x: center.x + (outerRadius - cornerRadius) * cos(angle), y: center.y + (outerRadius - cornerRadius) * sin(angle))
    path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise)

    path.addArc(withCenter: center, radius: outerRadius, startAngle: endAngle - outerAngularDelta, endAngle: startAngle + outerAngularDelta, clockwise: !clockwise)

    angle = startAngle + outerAngularDelta
    cornerStartAngle = angle
    cornerEndAngle = startAngle - .pi / 2 * (clockwise ? 1 : -1)
    cornerCenter = CGPoint(x: center.x + (outerRadius - cornerRadius) * cos(angle), y: center.y + (outerRadius - cornerRadius) * sin(angle))
    path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise)

    angle = startAngle + innerAngularDelta
    cornerStartAngle = cornerEndAngle
    cornerEndAngle = angle + .pi * (clockwise ? 1 : -1)
    cornerCenter = CGPoint(x: center.x + (innerRadius + cornerRadius) * cos(angle), y: center.y + (innerRadius + cornerRadius) * sin(angle))
    path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise)

    path.close()

    // configure shapeLayer

    shapeLayer.fillColor = UIColor.blue.cgColor
    shapeLayer.strokeColor = UIColor.clear.cgColor
    shapeLayer.lineJoin = .round
    shapeLayer.path = path.cgPath
    }
    }


Related Topics



Leave a reply



Submit