Different cornerRadius for each corner Swift 3 - iOS
Solution 1
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)
Solution 2
Do you want to add unique corner value for each corner?
Do you want to add a border after that?
I've got a solution will look like this:
First, add a UIBezierPath
extension I made:
extension UIBezierPath {
convenience init(shouldRoundRect rect: CGRect, topLeftRadius: CGSize = .zero, topRightRadius: CGSize = .zero, bottomLeftRadius: CGSize = .zero, bottomRightRadius: CGSize = .zero){
self.init()
let path = CGMutablePath()
let topLeft = rect.origin
let topRight = CGPoint(x: rect.maxX, y: rect.minY)
let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY)
let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY)
if topLeftRadius != .zero{
path.move(to: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y))
} else {
path.move(to: CGPoint(x: topLeft.x, y: topLeft.y))
}
if topRightRadius != .zero{
path.addLine(to: CGPoint(x: topRight.x-topRightRadius.width, y: topRight.y))
path.addCurve(to: CGPoint(x: topRight.x, y: topRight.y+topRightRadius.height), control1: CGPoint(x: topRight.x, y: topRight.y), control2:CGPoint(x: topRight.x, y: topRight.y+topRightRadius.height))
} else {
path.addLine(to: CGPoint(x: topRight.x, y: topRight.y))
}
if bottomRightRadius != .zero{
path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y-bottomRightRadius.height))
path.addCurve(to: CGPoint(x: bottomRight.x-bottomRightRadius.width, y: bottomRight.y), control1: CGPoint(x: bottomRight.x, y: bottomRight.y), control2: CGPoint(x: bottomRight.x-bottomRightRadius.width, y: bottomRight.y))
} else {
path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y))
}
if bottomLeftRadius != .zero{
path.addLine(to: CGPoint(x: bottomLeft.x+bottomLeftRadius.width, y: bottomLeft.y))
path.addCurve(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius.height), control1: CGPoint(x: bottomLeft.x, y: bottomLeft.y), control2: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius.height))
} else {
path.addLine(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y))
}
if topLeftRadius != .zero{
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y+topLeftRadius.height))
path.addCurve(to: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y) , control1: CGPoint(x: topLeft.x, y: topLeft.y) , control2: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y))
} else {
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y))
}
path.closeSubpath()
cgPath = path
}
}
Then, add this UIView
extension:
extension UIView{
func roundCorners(topLeft: CGFloat = 0, topRight: CGFloat = 0, bottomLeft: CGFloat = 0, bottomRight: CGFloat = 0) {//(topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) {
let topLeftRadius = CGSize(width: topLeft, height: topLeft)
let topRightRadius = CGSize(width: topRight, height: topRight)
let bottomLeftRadius = CGSize(width: bottomLeft, height: bottomLeft)
let bottomRightRadius = CGSize(width: bottomRight, height: bottomRight)
let maskPath = UIBezierPath(shouldRoundRect: bounds, topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius)
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
layer.mask = shape
}
}
Finally, call method
myView.roundCorners(topLeft: 10, topRight: 20, bottomLeft: 30, bottomRight: 40)
And add border. Apparently, layer.borderRadius won't work properly, so create a border using CAShapeLayer
and previously created path.
let borderLayer = CAShapeLayer()
borderLayer.path = (myView.layer.mask! as! CAShapeLayer).path! // Reuse the Bezier path
borderLayer.strokeColor = UIColor.red.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.lineWidth = 5
borderLayer.frame = myView.bounds
myView.layer.addSublayer(borderLayer)
Voila!
Solution 3
A slightly improved and simplified answer based on @Kirill Dobryakov's. Curves can leave very small but noticeable irregularities, when you look at it and you know it's not ideally round (try e.g. view side 40 and radius 20). I have no idea how it's even possible, but anyway, the most reliable way is to use arcs which make ideal round corners, and also an @IBDesigneable
component for you:
extension UIBezierPath {
convenience init(shouldRoundRect rect: CGRect, topLeftRadius: CGFloat, topRightRadius: CGFloat, bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat){
self.init()
let path = CGMutablePath()
let topLeft = rect.origin
let topRight = CGPoint(x: rect.maxX, y: rect.minY)
let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY)
let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY)
if topLeftRadius != 0 {
path.move(to: CGPoint(x: topLeft.x + topLeftRadius, y: topLeft.y))
} else {
path.move(to: topLeft)
}
if topRightRadius != 0 {
path.addLine(to: CGPoint(x: topRight.x - topRightRadius, y: topRight.y))
path.addArc(tangent1End: topRight, tangent2End: CGPoint(x: topRight.x, y: topRight.y + topRightRadius), radius: topRightRadius)
}
else {
path.addLine(to: topRight)
}
if bottomRightRadius != 0 {
path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y - bottomRightRadius))
path.addArc(tangent1End: bottomRight, tangent2End: CGPoint(x: bottomRight.x - bottomRightRadius, y: bottomRight.y), radius: bottomRightRadius)
}
else {
path.addLine(to: bottomRight)
}
if bottomLeftRadius != 0 {
path.addLine(to: CGPoint(x: bottomLeft.x + bottomLeftRadius, y: bottomLeft.y))
path.addArc(tangent1End: bottomLeft, tangent2End: CGPoint(x: bottomLeft.x, y: bottomLeft.y - bottomLeftRadius), radius: bottomLeftRadius)
}
else {
path.addLine(to: bottomLeft)
}
if topLeftRadius != 0 {
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y + topLeftRadius))
path.addArc(tangent1End: topLeft, tangent2End: CGPoint(x: topLeft.x + topLeftRadius, y: topLeft.y), radius: topLeftRadius)
}
else {
path.addLine(to: topLeft)
}
path.closeSubpath()
cgPath = path
}
}
@IBDesignable
open class VariableCornerRadiusView: UIView {
private func applyRadiusMaskFor() {
let path = UIBezierPath(shouldRoundRect: bounds, topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius)
let shape = CAShapeLayer()
shape.path = path.cgPath
layer.mask = shape
}
@IBInspectable
open var topLeftRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
@IBInspectable
open var topRightRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
@IBInspectable
open var bottomLeftRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
@IBInspectable
open var bottomRightRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
override open func layoutSubviews() {
super.layoutSubviews()
applyRadiusMaskFor()
}
}
Related videos on Youtube
Vinodha Sundaramoorthy
Updated on June 04, 2022Comments
-
Vinodha Sundaramoorthy almost 2 years
I want to set different corner radius for a view in Swift -3 , I am able to set the radius for the each corner to the same value like the one mentioned in the following post ,how to set cornerRadius for only top-left and top-right corner of a UIView?
Is there a way I can set the corner radius in the following format ? Radius top-left: 18 Radius top-right: 18 Radius bottom-right: 3 Radius bottom-left: 18
-
Vinodha Sundaramoorthy over 7 yearsWorks just as expected.Thanks a lot.
-
Ali Ihsan URAL almost 6 yearsIt doesn't work with border . Is there anyway ? Thanks
-
Adeel Miraj almost 5 yearsI don't know why this answer has such low score. This is THE best answer. Covers each and every aspect.
-
mojuba over 4 yearsCan be simplified a bit, for example instead of
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y))
if can bepath.addLine(to: topLeft)
and many similar lines. -
xaphod about 4 yearsI got weird behavior (ie. only top left corner being rounded), turned out I was setting the initial values correctly, but then during animation / layout the path needs to be set again. Since I was doing this from a
UIViewController
I overrodeviewDidLayoutSubviews
and called this again. Fixed the issue -
mojuba about 4 years@xaphod a bit strange because my component also overrides
layoutSubviews()
which should do the job. Probably something specific to your situation. -
xaphod about 4 yearsForgot to mention i’m unable to modify the target view’s class so I’m not using that part of your code.
-
Maulik shah almost 4 yearsanswer is good but i am trying to apply shadow.. then shadow not apply..
-
Maulik shah almost 4 yearsanswer is good but i am trying to apply shadow.. then shadow not apply.. @ArashAfsharpour
-
Arash Afsharpour almost 4 years@Maulikshah shadow on a UIView? or a button?
-
Nicolai Harbo almost 3 yearsRemember to set the corner radius in didLayoutSubviews() - otherwise you might experience weird behaviour :)
-
Jen Jose almost 3 yearsWorks like a magic! go for it guys, only issue with adding shadows
-
Raja Saad about 2 yearsthis does not work for all the corners.