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;
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];
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:
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:
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):
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
String Length in Swift 1.2 and Swift 2.0
How to Convert a Base64String to String in Swift
Wrong Specialized Generic Function Gets Called in Swift 3 from an Indirect Call
Property Initializers Run Before 'Self' Is Available
How to Make a Custom Initializer For a Uiviewcontroller Subclass in Swift
Difference Between Flatmap and Compactmap in Swift
Swift - Sort Array of Objects With Multiple Criteria
Number of Words in a Swift String For Word Count Calculation
Tabview Resets Navigation Stack When Switching Tabs
Why Is Equatable Not Defined For Optional Arrays
Swift 2 ( Executefetchrequest ): Error Handling
Constant Unassigned Optional Will Not Be Nil by Default
JavaScript Synchronous Native Communication to Wkwebview
Any Reason Not Use Use a Singleton "Variable" in Swift
How to See Which Version of Swift I'M Using