How to draw rounded rect that wraps around circular button? - ios

I want to design the highlighted portion of curve of the below screen snapshot using bezier path in swift.

It’s just a little trigonometry to figure out the arcs around that button in the bottom right hand corner, e.g.
func updatePath() {
let buttonCenter = CGPoint(x: bounds.maxX - circleRadius, y: bounds.maxY - circleRadius)
circleShapeLayer.path = UIBezierPath(arcCenter: buttonCenter, radius: circleRadius, startAngle: 0, endAngle: .pi * 2, clockwise: true).cgPath // red
let angle1 = acos((circleRadius - cornerRadius) / (circleRadius + spaceRadius + cornerRadius))
let angle2 = acos((circleRadius - bottomDistance - cornerRadius) / (circleRadius + spaceRadius + cornerRadius))
let arc1Center = CGPoint(x: bounds.maxX - cornerRadius,
y: buttonCenter.y - (circleRadius + cornerRadius + spaceRadius) * sin(angle1))
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.maxX, y: bounds.minY + cornerRadius))
path.addArc(withCenter: arc1Center, radius: cornerRadius, startAngle: 0, endAngle: .pi / 2 + (.pi / 2 - angle1), clockwise: true) // blue
path.addArc(withCenter: buttonCenter, radius: circleRadius + spaceRadius, startAngle: 2 * .pi - angle1, endAngle: .pi / 2 + angle2, clockwise: false) // green
let arc2Center = CGPoint(x: buttonCenter.x - (circleRadius + cornerRadius + spaceRadius) * sin(angle2), y: bounds.maxY - bottomDistance - cornerRadius)
path.addArc(withCenter: arc2Center, radius: cornerRadius, startAngle: -(.pi / 2 - angle2), endAngle: .pi / 2, clockwise: true) // yellow
path.addArc(withCenter: CGPoint(x: bounds.minX + cornerRadius, y: bounds.maxY - (bottomDistance + cornerRadius)), radius: cornerRadius, startAngle: .pi / 2, endAngle: .pi, clockwise: true) // cyan
path.addArc(withCenter: CGPoint(x: bounds.minX + cornerRadius, y: bounds.minY + cornerRadius), radius: cornerRadius, startAngle: .pi, endAngle: .pi * 3 / 2, clockwise: true) // white
path.addArc(withCenter: CGPoint(x: bounds.maxX - cornerRadius, y: bounds.minY + cornerRadius), radius: cornerRadius, startAngle: .pi * 3 / 2, endAngle: 2 * .pi, clockwise: true) // black
path.close()
backgroundShapeLayer.path = path.cgPath
}
Yielding:
Or, if you want to match the above strokes, here it is color coded to the comments in the code above:
For example:
#IBDesignable
class BackgroundView: UIView {
#IBInspectable var cornerRadius: CGFloat = 10 { didSet { setNeedsLayout() } }
#IBInspectable var spaceRadius: CGFloat = 10 { didSet { setNeedsLayout() } }
#IBInspectable var circleRadius: CGFloat = 50 { didSet { setNeedsLayout() } }
#IBInspectable var bottomDistance: CGFloat = 30 { didSet { setNeedsLayout() } }
private let backgroundShapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.clear.cgColor
return shapeLayer
}()
private let circleShapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.clear.cgColor
return shapeLayer
}()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
updatePath()
}
}
private extension BackgroundView {
func configure() {
layer.addSublayer(circleShapeLayer)
layer.addSublayer(backgroundShapeLayer)
}
func updatePath() {
let buttonCenter = CGPoint(x: bounds.maxX - circleRadius, y: bounds.maxY - circleRadius)
circleShapeLayer.path = UIBezierPath(arcCenter: buttonCenter, radius: circleRadius, startAngle: 0, endAngle: .pi * 2, clockwise: true).cgPath
let angle1 = acos((circleRadius - cornerRadius) / (circleRadius + spaceRadius + cornerRadius))
let angle2 = acos((circleRadius - bottomDistance - cornerRadius) / (circleRadius + spaceRadius + cornerRadius))
let arc1Center = CGPoint(x: bounds.maxX - cornerRadius,
y: buttonCenter.y - (circleRadius + cornerRadius + spaceRadius) * sin(angle1))
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.maxX, y: bounds.minY + cornerRadius))
path.addArc(withCenter: arc1Center, radius: cornerRadius, startAngle: 0, endAngle: .pi / 2 + (.pi / 2 - angle1), clockwise: true)
path.addArc(withCenter: buttonCenter, radius: circleRadius + spaceRadius, startAngle: 2 * .pi - angle1, endAngle: .pi / 2 + angle2, clockwise: false)
let arc2Center = CGPoint(x: buttonCenter.x - (circleRadius + cornerRadius + spaceRadius) * sin(angle2), y: bounds.maxY - bottomDistance - cornerRadius)
path.addArc(withCenter: arc2Center, radius: cornerRadius, startAngle: -(.pi / 2 - angle2), endAngle: .pi / 2, clockwise: true)
path.addArc(withCenter: CGPoint(x: bounds.minX + cornerRadius, y: bounds.maxY - (bottomDistance + cornerRadius)), radius: cornerRadius, startAngle: .pi / 2, endAngle: .pi, clockwise: true)
path.addArc(withCenter: CGPoint(x: bounds.minX + cornerRadius, y: bounds.minY + cornerRadius), radius: cornerRadius, startAngle: .pi, endAngle: .pi * 3 / 2, clockwise: true)
path.addArc(withCenter: CGPoint(x: bounds.maxX - cornerRadius, y: bounds.minY + cornerRadius), radius: cornerRadius, startAngle: .pi * 3 / 2, endAngle: 2 * .pi, clockwise: true)
path.close()
backgroundShapeLayer.path = path.cgPath
}
}

You can use Paint code application on mac, to draw the curve using the pen tool and then copy the result of it, since the app provides Drawings-to-code

Related

Shape in ios swift

how to draw the curve with blur background?
here I have attached my code but am not getting the exact output
#IBDesignable class CurvedHeaderView: UIView {
#IBInspectable var curveHeight:CGFloat = 50.0
var curvedLayer = CAShapeLayer()
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.move(to: CGPoint(x: 0, y: rect.height))
// path.addLine(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
path.addArc(withCenter: CGPoint(x: CGFloat(rect.width) - curveHeight, y: 100), radius: curveHeight, startAngle: 0, endAngle: 1.5 * CGFloat.pi, clockwise: false)
// path.addArc(withCenter: CGPoint(x: CGFloat(rect.width) - curveHeight, y: rect.height), radius: curveHeight, startAngle: 0, endAngle: 1.5 * CGFloat.pi, clockwise: false)
// path.addLine(to: CGPoint(x: curveHeight, y: rect.height - curveHeight))
// path.addLine(to: CGPoint(x: curveHeight, y: 50))
path.addLine(to: CGPoint(x: curveHeight, y: curveHeight))
path.addArc(withCenter: CGPoint(x: curveHeight, y: 0), radius: curveHeight, startAngle: 0, endAngle: CGFloat.pi, clockwise: true)
path.addLine(to: CGPoint(x: 0, y: 0))
// path.addArc(withCenter: CGPoint(x: curveHeight, y: rect.height - (curveHeight * 2.0)), radius: curveHeight, startAngle: 0, endAngle: CGFloat.pi, clockwise: true)
path.close()
curvedLayer.path = path.cgPath
curvedLayer.fillColor = UIColor.white.cgColor
curvedLayer.frame = rect
self.layer.insertSublayer(curvedLayer, at: 0)
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowRadius = 10.0
self.layer.shadowOpacity = 0.7
}
}
Please share me reference if anything wrong
Thanks in advance
I have tried but it showing code working but not exactly been getting
#IBOutlet weak var blurEffectView: UIVisualEffectView!
override func viewDidLayoutSubviews() {
let maskView = CurvedHeaderView(frame: blurEffectView.bounds)
maskView.clipsToBounds = true;
maskView.backgroundColor = UIColor.clear
blurEffectView.mask = maskView
}

UIBezierPath animation from circle to rounded square

I'm trying to animate from a circle to a square with rounded corners. As far as I understand, number of points from both paths should be equal for smooth animation. So I wrote a function that builds a circle bases on a number of arcs:
private func circleShape(segmentNumber: Int = 8, radius: CGFloat = 32) -> CGPath {
let center = self.bounds.center
let circlePath = UIBezierPath()
let segmentAngle = CGFloat.pi * CGFloat(2) / CGFloat(segmentNumber)
var currentAngle: CGFloat = 0
for _ in 0 ..< segmentNumber {
let nextAngle = currentAngle + segmentAngle
circlePath.addArc(withCenter: center, radius: radius,
startAngle: currentAngle, endAngle: nextAngle, clockwise: true)
currentAngle = nextAngle
}
circlePath.close()
return circlePath.cgPath
}
I'm checking number of points with getPathElementsPointsAndTypes() and it increases unproportionally to the segmentNumber (segmentNumber: 1, points: 13; sN: 2, p: 14; sN: 3, p: 21; sN: 4, p: 16). So I can't find a segmentNumber that would give the same number of points (19) as the function that draws the square:
private func squareShape(size: CGFloat = 44, radius: CGFloat = 8) -> CGPath {
let rect = CGRect(center: self.bounds.center, size: CGSize(width: size, height: size))
let left = CGFloat.pi
let up = CGFloat(1.5) * CGFloat.pi
let down = CGFloat.pi / CGFloat(2)
let right = CGFloat(0.0)
let squarePath = UIBezierPath()
squarePath.addArc(withCenter: CGPoint(x: rect.minX + radius, y: rect.minY + radius), radius: radius, startAngle: left, endAngle: up, clockwise: true)
squarePath.addArc(withCenter: CGPoint(x: rect.maxX - radius, y: rect.minY + radius), radius: radius, startAngle: up, endAngle: right, clockwise: true)
squarePath.addArc(withCenter: CGPoint(x: rect.maxX - radius, y: rect.maxY - radius), radius: radius, startAngle: right, endAngle: down, clockwise: true)
squarePath.addArc(withCenter: CGPoint(x: rect.minX + radius, y: rect.maxY - radius), radius: radius, startAngle: down, endAngle: left, clockwise: true)
squarePath.close()
return squarePath.cgPath
}
Animation code:
let animation = CABasicAnimation(keyPath: "path")
animation.duration = 2
animation.toValue = self.circleShape()
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
self.testLayer.add(animation, forKey: "pathAnimation")
Result:
What could I change to make the animation same smooth as in example without rounded rects?
You can simply have two paths
func circlePath () -> UIBezierPath {
return UIBezierPath(roundedRect: CGRect(x:0, y:0, width:50, height:50), cornerRadius: 25)
}
func squarePath () -> UIBezierPath {
return UIBezierPath(roundedRect: CGRect(x:0, y:0, width:30, height:30), cornerRadius: 4)
}

Set the radius of one corner of a SKShapeNode

I want to change the radius of two corners of a SKShapeNode (rect) but I didn't find a working solution.
I've tried to use a path, and it didn't work.
Swift 4.2, iOS 12.1.1, Xcode 10.1
let shape = SKShapeNode()
shape.path = UIBezierPath(roundedRect: CGRect(x: -128, y: -128, width: 256, height: 256), cornerRadius: 64).cgPath
shape.position = CGPoint(x: frame.midX, y: frame.midY)
shape.fillColor = UIColor.red
shape.strokeColor = UIColor.blue
shape.lineWidth = 10
addChild(shape)
I made a function that will allow you to customize each corner radius to whatever size you'd like. You can have 1,2,3 or 4 corners with a radius. If you always just want two corners then I would suggest making a wrapper function so you don't have so many parameters to fill in each time you call it.
func CustomRoundRectPath(_ rect:CGRect, _ TLR:CGFloat,_ TRR:CGFloat,_ BLR:CGFloat,_ BRR:CGFloat) -> CGPath {
let w = rect.width
let h = rect.height
//TLP:(TLP)
let TLP = CGPoint(x: TLR, y: h - TLR)
let TRP = CGPoint(x: w - TRR, y: h - TRR)
let BLP = CGPoint(x: BLR, y: BLR)
let BRP = CGPoint(x: w - BRR, y: BRR)
//Create path and addComponents
let path = CGMutablePath()
path.addArc(center: TLP, radius: TLR, startAngle: CGFloat.pi, endAngle: CGFloat.pi/2, clockwise: true)
path.addLine(to: CGPoint(x: TRP.x, y: h))
path.addArc(center: TRP, radius: TRR, startAngle: CGFloat.pi/2, endAngle: 0, clockwise: true)
path.addLine(to: CGPoint(x: w, y: BRP.y))
path.addArc(center: BRP, radius: BRR, startAngle: 0, endAngle: -CGFloat.pi/2, clockwise: true)
path.addLine(to: CGPoint(x: BLP.x, y: 0))
path.addArc(center: BLP, radius: BLR, startAngle: -CGFloat.pi/2, endAngle: -CGFloat.pi, clockwise: true)
path.addLine(to: CGPoint(x: 0, y: TLP.y))
return path
}

Swift: UIBezierPath Stroke Animation from Center

I've been making a simple UIBezierPath animation with Swift. This path consists on creating a rounded rectangle with a colored border. The animation must be the drawing of the colored border. To do so, I've created a CAShapeLayer with a UIBezierPath(roundedRect:, cornerRadius: )
let layer = CAShapeLayer()
var viewPrueba = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
viewPrueba = UIView(frame: CGRectMake(self.view.frame.width/2-100, self.view.frame.height/2 - 100, 200, 200))
self.view.addSubview(viewPrueba)
let path = UIBezierPath(roundedRect: CGRectMake(0, 0, 200, 200), cornerRadius: 40.0)
layer.path = path.CGPath
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blueColor().CGColor
layer.strokeStart = 0.0
layer.strokeEnd = 0.0
layer.lineWidth = 4.0
layer.lineJoin = kCALineJoinRound
viewPrueba.layer.addSublayer(layer)
let tapGR = UITapGestureRecognizer(target: self, action: #selector(ViewController.anim))
self.view.addGestureRecognizer(tapGR)
}
func anim() {
let anim1 = CABasicAnimation(keyPath: "strokeEnd")
anim1.fromValue = 0.0
anim1.toValue = 1.0
anim1.duration = 4.0
anim1.repeatCount = 0
anim1.autoreverses = false
anim1.removedOnCompletion = false
anim1.additive = true
anim1.fillMode = kCAFillModeForwards
self.layer.addAnimation(anim1, forKey: "strokeEnd")
}`
It works well. The only problem is that the animation starts from the top-left part of the square and not from the top-center. How can I do that?
The only thing I've found in order to achieve this is by doing it with a circle and not a rectangle, which is not what we want.
Thanks
CoreAnimate animated as the same order as which the UIBezierPath was drawn.
The system method
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;
return a UIBezierPath which was drawn from the top-left,so your animation started from the top-left.
But you can create your own UIBezierPath drawn form top-center:
func centerStartBezierPath(frame:CGRect,cornerRadius:CGFloat) -> UIBezierPath {
let path = UIBezierPath()
path.moveToPoint(CGPointMake(frame.width/2.0, 0))
path.addLineToPoint(CGPointMake(frame.width-cornerRadius, 0))
path.addArcWithCenter(CGPointMake(frame.width-cornerRadius, cornerRadius),
radius: cornerRadius,
startAngle: CGFloat(-M_PI/2),
endAngle: 0,
clockwise: true)
path.addLineToPoint(CGPointMake(frame.width, frame.height-cornerRadius))
path.addArcWithCenter(CGPointMake(frame.width-cornerRadius, frame.height-cornerRadius),
radius: cornerRadius,
startAngle: 0,
endAngle: CGFloat(M_PI/2),
clockwise: true)
path.addLineToPoint(CGPointMake(cornerRadius, frame.height))
path.addArcWithCenter(CGPointMake(cornerRadius, frame.height-cornerRadius),
radius: cornerRadius,
startAngle: CGFloat(M_PI/2),
endAngle: CGFloat(M_PI),
clockwise: true)
path.addLineToPoint(CGPointMake(0, cornerRadius))
path.addArcWithCenter(CGPointMake(cornerRadius, cornerRadius),
radius: cornerRadius,
startAngle: CGFloat(M_PI),
endAngle: CGFloat(M_PI*3/2),
clockwise: true)
path.closePath()
path.applyTransform(CGAffineTransformMakeTranslation(frame.origin.x, frame.origin.y))
return path;
}
And it works like this:
You can also change the code,and start from any point you want.
Swift syntax changed much during these three years. Here is my updated version of originally accepted answer, but in Swift 5.1+
private extension UIBezierPath {
convenience init(roundedRectFromCenter frame: CGRect, cornerRadius: CGFloat) {
self.init()
move(to: CGPoint(x: frame.width / 2, y: 0))
addLine(to: CGPoint(x: frame.width - cornerRadius, y: 0))
addArc(
withCenter: CGPoint(x: frame.width - cornerRadius, y: cornerRadius),
radius: cornerRadius,
startAngle: -.pi / 2,
endAngle: 0,
clockwise: true
)
addLine(to: CGPoint(x: frame.width, y: frame.height - cornerRadius))
addArc(
withCenter: CGPoint(x: frame.width - cornerRadius, y: frame.height - cornerRadius),
radius: cornerRadius,
startAngle: 0,
endAngle: .pi / 2,
clockwise: true
)
addLine(to: CGPoint(x: cornerRadius, y: frame.height))
addArc(
withCenter: CGPoint(x: cornerRadius, y: frame.height - cornerRadius),
radius: cornerRadius,
startAngle: .pi / 2,
endAngle: .pi,
clockwise: true
)
addLine(to: CGPoint(x: 0, y: cornerRadius))
addArc(
withCenter: CGPoint(x: cornerRadius, y: cornerRadius),
radius: cornerRadius,
startAngle: .pi,
endAngle: .pi * 3 / 2,
clockwise: true
)
close()
apply(CGAffineTransform(
translationX: frame.origin.x,
y: frame.origin.y
))
}
}

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

I want to rounder my UIView with the value like that
top-left-radius:20; bottom-right-radius:5; bottom-left-radius:5; and top-right-radius:10;
//For rounder `UIRectCornerBottomLeft & UIRectCornerBottomRight` I use
UIBezierPath *maskPath0 = [UIBezierPath bezierPathWithRoundedRect:self.messageView.bounds byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight) cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *maskLayer0 = [[CAShapeLayer alloc] init];
maskLayer0.frame = self.bounds;
maskLayer0.path = maskPath0.CGPath;
self.messageView.layer.mask = maskLayer0;
//For rounder `TopRight` I use
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.messageView.bounds byRoundingCorners:(UIRectCornerTopRight) cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.messageView.layer.mask = maskLayer;
//For rounder `TopLeft` I use
UIBezierPath *maskPath2 = [UIBezierPath bezierPathWithRoundedRect:self.messageView.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii:CGSizeMake(20.0, 20.0)];
CAShapeLayer *maskLayer2 = [[CAShapeLayer alloc] init];
maskLayer2.frame = self.bounds;
maskLayer2.path = maskPath2.CGPath;
self.messageView.layer.mask = maskLayer2;
But the result I get is the View with corner radius TopLeft with value 20.
How can I achieve this rounder? Any help would be much appreciated.
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;
Here's a UIBezierPath category that does what you want, following the pattern of some of the existing initializers:
extension UIBezierPath {
public convenience init(roundedRect rect: CGRect, topLeftRadius: CGFloat?, topRightRadius: CGFloat?, bottomLeftRadius: CGFloat?, bottomRightRadius: CGFloat?) {
self.init()
assert(((bottomLeftRadius ?? 0) + (bottomRightRadius ?? 0)) <= rect.size.width)
assert(((topLeftRadius ?? 0) + (topRightRadius ?? 0)) <= rect.size.width)
assert(((topLeftRadius ?? 0) + (bottomLeftRadius ?? 0)) <= rect.size.height)
assert(((topRightRadius ?? 0) + (bottomRightRadius ?? 0)) <= rect.size.height)
// corner centers
let tl = CGPoint(x: rect.minX + (topLeftRadius ?? 0), y: rect.minY + (topLeftRadius ?? 0))
let tr = CGPoint(x: rect.maxX - (topRightRadius ?? 0), y: rect.minY + (topRightRadius ?? 0))
let bl = CGPoint(x: rect.minX + (bottomLeftRadius ?? 0), y: rect.maxY - (bottomLeftRadius ?? 0))
let br = CGPoint(x: rect.maxX - (bottomRightRadius ?? 0), y: rect.maxY - (bottomRightRadius ?? 0))
//let topMidpoint = CGPoint(rect.midX, rect.minY)
let topMidpoint = CGPoint(x: rect.midX, y: rect.minY)
makeClockwiseShape: do {
self.move(to: topMidpoint)
if let topRightRadius = topRightRadius {
self.addLine(to: CGPoint(x: rect.maxX - topRightRadius, y: rect.minY))
self.addArc(withCenter: tr, radius: topRightRadius, startAngle: -CGFloat.pi/2, endAngle: 0, clockwise: true)
}
else {
self.addLine(to: tr)
}
if let bottomRightRadius = bottomRightRadius {
self.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - bottomRightRadius))
self.addArc(withCenter: br, radius: bottomRightRadius, startAngle: 0, endAngle: CGFloat.pi/2, clockwise: true)
}
else {
self.addLine(to: br)
}
if let bottomLeftRadius = bottomLeftRadius {
self.addLine(to: CGPoint(x: rect.minX + bottomLeftRadius, y: rect.maxY))
self.addArc(withCenter: bl, radius: bottomLeftRadius, startAngle: CGFloat.pi/2, endAngle: CGFloat.pi, clockwise: true)
}
else {
self.addLine(to: bl)
}
if let topLeftRadius = topLeftRadius {
self.addLine(to: CGPoint(x: rect.minX, y: rect.minY + topLeftRadius))
self.addArc(withCenter: tl, radius: topLeftRadius, startAngle: CGFloat.pi, endAngle: -CGFloat.pi/2, clockwise: true)
}
else {
self.addLine(to: tl)
}
self.close()
}
}
}
Swift 5 UIView extension version of #Mikhail-Grebionkin's answer:
extension UIView {
func makeCustomRound(topLeft: CGFloat = 0, topRight: CGFloat = 0, bottomLeft: CGFloat = 0, bottomRight: CGFloat = 0) {
let minX = bounds.minX
let minY = bounds.minY
let maxX = bounds.maxX
let maxY = bounds.maxY
let path = UIBezierPath()
path.move(to: CGPoint(x: minX + topLeft, y: minY))
path.addLine(to: CGPoint(x: maxX - topRight, y: minY))
path.addArc(withCenter: CGPoint(x: maxX - topRight, y: minY + topRight), radius: topRight, startAngle:CGFloat(3 * Double.pi / 2), endAngle: 0, clockwise: true)
path.addLine(to: CGPoint(x: maxX, y: maxY - bottomRight))
path.addArc(withCenter: CGPoint(x: maxX - bottomRight, y: maxY - bottomRight), radius: bottomRight, startAngle: 0, endAngle: CGFloat(Double.pi / 2), clockwise: true)
path.addLine(to: CGPoint(x: minX + bottomLeft, y: maxY))
path.addArc(withCenter: CGPoint(x: minX + bottomLeft, y: maxY - bottomLeft), radius: bottomLeft, startAngle: CGFloat(Double.pi / 2), endAngle: CGFloat(Double.pi), clockwise: true)
path.addLine(to: CGPoint(x: minX, y: minY + topLeft))
path.addArc(withCenter: CGPoint(x: minX + topLeft, y: minY + topLeft), radius: topLeft, startAngle: CGFloat(Double.pi), endAngle: CGFloat(3 * Double.pi / 2), clockwise: true)
path.close()
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
}

Resources