How to Set Layer Cornerradius For Only Bottom-Left, Bottom-Right, and Top-Left Corner

How to set layer cornerRadius for only bottom-left, bottom-right, and top-left corner?

You just need to mask the layer as shown below:

For Swift 3:

let rectShape = CAShapeLayer()
rectShape.bounds = self.myView.frame
rectShape.position = self.myView.center
rectShape.path = UIBezierPath(roundedRect: self.myView.bounds, byRoundingCorners: [.bottomLeft , .bottomRight , .topLeft], cornerRadii: CGSize(width: 20, height: 20)).cgPath

self.myView.layer.backgroundColor = UIColor.green.cgColor
//Here I'm masking the textView's layer with rectShape layer
self.myView.layer.mask = rectShape

Lower Version:

let rectShape = CAShapeLayer()
rectShape.bounds = self.myView.frame
rectShape.position = self.myView.center
rectShape.path = UIBezierPath(roundedRect: self.myView.bounds, byRoundingCorners: .BottomLeft | .BottomRight | .TopLeft, cornerRadii: CGSize(width: 20, height: 20)).CGPath

self.myView.layer.backgroundColor = UIColor.greenColor().CGColor
//Here I'm masking the textView's layer with rectShape layer
self.myView.layer.mask = rectShape

How to set cornerRadius for only top-left and top-right corner of a UIView?

Pay attention to the fact that if you have layout constraints attached to it, you must refresh this as follows in your UIView subclass:

override func layoutSubviews() {
super.layoutSubviews()
roundCorners(corners: [.topLeft, .topRight], radius: 3.0)
}

If you don't do that it won't show up.


And to round corners, use the extension:

extension UIView {
func roundCorners(corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
}



Additional view controller case: Whether you can't or wouldn't want to subclass a view, you can still round a view. Do it from its view controller by overriding the viewWillLayoutSubviews() function, as follows:

class MyVC: UIViewController {
/// The view to round the top-left and top-right hand corners
let theView: UIView = {
let v = UIView(frame: CGRect(x: 10, y: 10, width: 200, height: 200))
v.backgroundColor = .red
return v
}()

override func loadView() {
super.loadView()
view.addSubview(theView)
}

override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()

// Call the roundCorners() func right there.
theView.roundCorners(corners: [.topLeft, .topRight], radius: 30)
}
}

how to set cornerRadius for only bottom-left,bottom-right and top-left corner of a UIView?

You can do it like this:

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.viewOutlet.bounds byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerBottomLeft | UIRectCornerBottomRight) cornerRadii:CGSizeMake(10.0, 10.0)];

CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.view.bounds;
maskLayer.path = maskPath.CGPath;
self.viewOutlet.layer.mask = maskLayer;

Sample Image

Update:

If You need border just create another CAShapeLayer and add it to view's layer as sublayer. Like this (place this code below upper code):

CAShapeLayer *borderLayer = [[CAShapeLayer alloc] init];
borderLayer.frame = self.view.bounds;
borderLayer.path = maskPath.CGPath;
borderLayer.lineWidth = 4.0f;
borderLayer.strokeColor = [UIColor blackColor].CGColor;
borderLayer.fillColor = [UIColor clearColor].CGColor;

[self.viewOutlet.layer addSublayer:borderLayer];

Sample Image

In swift 3.0 like this:

let maskPath = UIBezierPath.init(roundedRect: self.viewOutlet.bounds, byRoundingCorners:[.topLeft, .bottomLeft], cornerRadii: CGSize.init(width: 10.0, height: 10.0))
let maskLayer = CAShapeLayer()
maskLayer.frame = self.viewOutlet.bounds
maskLayer.path = maskPath.cgPath
self.viewOutlet.layer.mask = maskLayer

how to set cornerRadius for only top-left corner of a UILabel?

let path =  UIBezierPath(roundedRect: self.bounds, byRoundingCorners: .topLeft, cornerRadii: CGSize(width: 10.0, height: 10.0))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
myText.layer.mask = maskLayer

You can also use in your UILabel class

override func draw(_ rect: CGRect) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: .topLeft, cornerRadii: CGSize(width: 10.0, height: 10.0))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
self.layer.mask = maskLayer
}

Corner radius only for top and bottom left corner of a view

A simple way to do this is to set the corner radius of your cell's content view, and then to prevent the right side corners of the contents from being rounded you could constrain them to have x trailing space to the content view (where x is your corner radius). This does require you to adjust your layout to account for the extra padding on the right side of your cells though.

Need to clipShape with a Shape that has only top left and bottom left rounded corners

Problem

The cornerRadius modifier already uses clipShape.

extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape( RoundedCorner(radius: radius, corners: corners) )
}
}

Which means that:

.clipShape(Rectangle().cornerRadius(20, [.topLeft, .topRight]))

can also be read as:

.clipShape(Rectangle().clipShape( RoundedCorner(radius: 20, corners: [.topLeft, .topRight]) ))

and you don't really need multiple clipShape modifiers.

Solution

Try the following instead (you don't need to use clipShape - the custom cornerRadius extension already calls it):

.cornerRadius(20, corners: [.topLeft, .topRight])

A more complete example:

struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100)
.cornerRadius(20, corners: [.topLeft, .topRight])
}
}

Alternatively, you can use clipShape explicitly:

.clipShape(RoundedCorner(radius: 20, corners: [.topLeft, .topRight]))

Why is corner radius applied only to top left and right corner for a custom view?

Edit: After clarification of the ultimate goal, here is a simpler way to do it. You can run this directly in a playground page...

import UIKit
import PlaygroundSupport

class ProgressBar: UIView {

var progressView: UIView = {
let v = UIView()
v.backgroundColor = .white
return v
}()

override init(frame: CGRect) {
super.init(frame: frame)
configure()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}

func configure() {
backgroundColor = UIColor(white: 1.0, alpha: 0.1)
layer.cornerRadius = 8.0
clipsToBounds = true

progressView.frame = bounds
progressView.frame.size.width = 0.0
addSubview(progressView)
}

func animate(_ pct: CGFloat) -> Void {
UIView.animate(withDuration: 1.0, animations: {
self.progressView.frame.size.width = self.bounds.size.width * pct
})
}

}

class MyViewController : UIViewController {

var theButton: UIButton = {
let b = UIButton()
b.setTitle("Tap Me", for: .normal)
b.translatesAutoresizingMaskIntoConstraints = false
b.backgroundColor = .red
return b
}()

var myProgressBar = ProgressBar(frame: CGRect(x: 40, y: 300, width: 300, height: 30))

// on button tap, set the progress bar to 80%
@objc func didTap(_ sender: Any?) -> Void {
myProgressBar.animate(0.8)
}

override func loadView() {
let view = UIView()
self.view = view
view.backgroundColor = UIColor.purple

view.addSubview(theButton)
// constrain button to Top: 32 and centerX
NSLayoutConstraint.activate([
theButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 32.0),
theButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
])

// add an action for the button tap
theButton.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)

view.addSubview(myProgressBar)

}

}

// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

Tapping the button will animate the "progress bar" from Zero to 80%

Result:

Sample Image


Original answer:

First: you are adding a "line" going from 0,0 to width,0 ... and you're making it's lineWidth the height of your view.

That gives you this result:

Sample Image

The yellow rectangle is your view, and the white rectangle is your "line". As you see, the center of the line is running along the top of the view, so the top-half of the line is actually outside the view's frame. So when you round the corners of the view, it appears as if only the top corners are being rounded. If you set a background color to your view, you will see that the bottom corners are also being rounded, but you have no "line" there.

If this is what you really want (top bar is white, bottom bar is green):

Sample Image

you want to set the line-widths to 1/2 of the view height, and set the line-centers at 1/4 and 3/4:

class ProgressBar: UIView {

var bottomProgressBar = CAShapeLayer()
var topProgressBar = CAShapeLayer()

override init(frame: CGRect) {
super.init(frame: frame)
configure()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}

func configure() {
bottomProgressBar.strokeColor = UIColor.green.cgColor
bottomProgressBar.opacity = 1
bottomProgressBar.lineWidth = self.frame.height
bottomProgressBar.strokeStart = 0
bottomProgressBar.strokeEnd = 1
self.layer.addSublayer(bottomProgressBar)

topProgressBar.strokeColor = UIColor.white.cgColor
topProgressBar.lineWidth = self.frame.height
topProgressBar.strokeStart = 0
topProgressBar.strokeEnd = 1
self.layer.addSublayer(topProgressBar)

// animate(toValue: 1)
}

override func layoutSubviews() {
super.layoutSubviews()

var path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: self.frame.height * 0.25))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height * 0.25))

topProgressBar.path = path.cgPath

path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: self.frame.height * 0.75))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height * 0.75))

bottomProgressBar.path = path.cgPath

bottomProgressBar.lineWidth = self.frame.height / 2.0 // Update lineWidth
topProgressBar.lineWidth = self.frame.height / 2.0 // Update lineWidth

self.clipsToBounds = true
self.layer.cornerRadius = 8

}

func animate(toValue: Double) {
let animation = CABasicAnimation(keyPath: "strokeEnd")

animation.duration = 3.0
animation.fromValue = 0
animation.toValue = toValue

topProgressBar.strokeEnd = CGFloat(toValue)
topProgressBar.animation(forKey: "animate")
}
}

Of course, if all you want is a single rectangle with rounded corners, then you don't need either sub-layer... simply create a view with a white background, and set the corner radius of its layer...



Related Topics



Leave a reply



Submit