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:
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:
Result with properties changed to:
.fillColor = .green
.shadowOffset = CGSize(width: 2, height: 4)
.shadowRadius = 4 // not quite so "fuzzy"
.shadowOpacity = 0.8
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 'insetValue
is sufficiently larger than the corner radius, you should never have an issue. Likewise, using the iMessage scenario, a single
UILabelcontaining text and/or emoticons should have enough height that a noticeable
cornerRadius` can be had.The key point to set things like
cornerRadius
andinsetValue
is inviewWillLayoutSubviews
, 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:
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:
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.
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
Multiple Lines in UItabbaritem Label
Indexpathforrowatpoint Returns Nil Only for First Cell in a UItableview
Play Keyboard Click Sound in a Collection View Controller
How to Install Self-Signed Certificates in iOS 11
Track Touch Points in Multitouch
Detect Collision of Two UIview's in Swift
Ld: Library Not Found for -Ldoubleconversion React Native 0.59
Screenshot on Swift Programmatically
Swift Unrecognized Selector Sent to Instance - What Am I Missing
Fixing Low Fps in Swift Playground
Determine The Maximum Number of Characters a UIlabel Can Take
How to Access All Images in a .Xcassets at Once
Conversion from String to Date in Swift Returns Nil
Realm Crashes with Rlmexception: Object Has Been Deleted or Invalidated