can't make round corners for UIView - swift

I have a UIView object inside a horizontal stackView.
I'm calling this function inside LayoutSubviews:
func roundCorners(corners: UIRectCorner, radius: CurveRadius) {
var r = CGFloat()
switch radius {
case .small:
r = self.frame.width / 30
case .medium:
r = self.frame.width / 25
}
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: r, height: r))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
but it just doesn't work. I guess it has something to do with a stackView. can anyone help me out?

extension UIView {
/// Rounds ``UIView`` corners.
/// - Parameters:
/// - maskedCorners: Corners to be rounded.
/// - cornerRadius: Value to be set as corner radius.
func roundCorners(maskedCorners: CACornerMask,
cornerRadius: CGFloat) {
layer.cornerRadius = cornerRadius
layer.maskedCorners = maskedCorners
layer.masksToBounds = true
}
}

I'm not sure if the UIStackView is the problem here, but there's another, more re-usable way to do this using a CACornerMask on the UIView's layer in an extension:
extension UIView {
func roundCorners() {
self.layer.cornerRadius = 10
self.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner]
}
}
When you create your view, just call the roundCorners(), like this:
private lazy var roundCorneredView: UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .red
view.roundCorners()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
Then add that view to your hierarchy in the usual way using addSubview.
CACornerMask docs # Apple

Related

UICollectionViewCell imageView doesn't load without scrolling

I have some cells in my collectionView, oriented vertically.
Every cell has got an image in it's left part and that image loads correctly.
But after I added a rounded top-right corner to the image doesn't automatically load when the screen in on.
Here is the method that is being called in cellForItemAt
func setAvatarImage(imageName: String) {
self.addSubview(avatarImageView)
layoutIfNeeded()
avatarImageView.layer.roundCorners(corners: [.topRight], radius: UIDevice.current.userInterfaceIdiom == .pad ? 85 : 40)
avatarImageView.contentMode = .scaleAspectFit
avatarImageView.image = UIImage(named: imageName)
avatarImageView.snp.makeConstraints { (make) in
make.height.equalToSuperview()
make.width.equalToSuperview().multipliedBy(0.5)
make.left.equalToSuperview()
}
}
Here is also the roundCorners method, CALayer extension, which worked perfectly on every element in my app. Also here it works perfectly but the images are not loading without scrolling up and down in the collectionView:
func roundCorners(corners: UIRectCorner, radius: CGFloat, borderWidth: CGFloat = 0, borderColor: UIColor = .clear) {
let maskPath = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius)
)
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
mask = shape
let borderLayer = CAShapeLayer()
borderLayer.path = maskPath.cgPath
borderLayer.lineWidth = borderWidth
borderLayer.strokeColor = borderColor.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.frame = self.bounds
addSublayer(borderLayer)
}
Solved by adding:
layoutIfNeeded()

How do I create a UIView with top corners rounded, bottom corners normal, and a border around this whole view?

This is what I'm getting with this code
private func setupBorders(){
let path = UIBezierPath(roundedRect: mainTableView.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 20, height: 20))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
mainTableView.layer.mask = maskLayer
mainTableView.layer.borderColor = UIColor.darkGray.cgColor
mainTableView.layer.borderWidth = 1.0
}
MainTableView is a uiview containing the notepad table and the table header. If I can get it to work for any UIView then it will work for this one. Much appreciation to anyone who can help!
Edit: In case its not clear, the problem is the border disappears on the rounded corners.
A mask layer is not enough to solve your requirement, because the layer border will not respect to the layer mask. Instead you should create a view for drawing the backgound and the border, and it should clip its contents along the border, too.
In storyboard drag a UIView to your ViewController, set constrains as you want, link it to NewView and try this,
import UIKit
import QuartzCore
class ViewController: UIViewController{
#IBOutlet var NewView :UIView!
override func viewDidLoad() {
super.viewDidLoad()
let screenSize: CGRect = UIScreen.mainScreen().bounds
let HeightFloat :CGFloat = screenSize.height - 60
let WidthFloat :CGFloat = screenSize.width - 50
let NewRect :CGRect = CGRectMake(10, 20, WidthFloat, HeightFloat)
let maskPath = UIBezierPath(roundedRect: NewRect,
byRoundingCorners: [.TopLeft, .TopRight],
cornerRadii: CGSize(width: 10, height: 10))
let shape = CAShapeLayer()
shape.path = maskPath.CGPath
shape.fillColor = UIColor.clearColor().CGColor
shape.strokeColor = UIColor.darkGrayColor().CGColor
shape.lineWidth = 2
shape.lineJoin = kCALineJoinRound
NewView.layer.mask = shape
}
}
You will get your output,

Swift - UIView with shadow different corner radiuses [duplicate]

Is there a way to set cornerRadius for only top-left and top-right corner of a UIView?
I tried the following, but it end up not seeing the view anymore.
UIView *view = [[UIView alloc] initWithFrame:frame];
CALayer *layer = [CALayer layer];
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:frame byRoundingCorners:(UIRectCornerTopLeft|UIRectCornerTopRight) cornerRadii:CGSizeMake(3.0, 3.0)];
layer.shadowPath = shadowPath.CGPath;
view.layer.mask = layer;
I am not sure why your solution did not work but the following code is working for me. Create a bezier mask and apply it to your view. In my code below I was rounding the bottom corners of the _backgroundView with a radius of 3 pixels. self is a custom UITableViewCell:
UIBezierPath *maskPath = [UIBezierPath
bezierPathWithRoundedRect:self.backgroundImageView.bounds
byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight)
cornerRadii:CGSizeMake(20, 20)
];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.backgroundImageView.layer.mask = maskLayer;
Swift 2.0 version with some improvements:
let path = UIBezierPath(roundedRect:viewToRound.bounds, byRoundingCorners:[.TopRight, .BottomLeft], cornerRadii: CGSizeMake(20, 20))
let maskLayer = CAShapeLayer()
maskLayer.path = path.CGPath
viewToRound.layer.mask = maskLayer
Swift 3.0 version:
let path = UIBezierPath(roundedRect:viewToRound.bounds,
byRoundingCorners:[.topRight, .bottomLeft],
cornerRadii: CGSize(width: 20, height: 20))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
viewToRound.layer.mask = maskLayer
Swift extension here
And finally… there is CACornerMask in iOS11!
With CACornerMask it can be done pretty easy:
let view = UIView()
view.clipsToBounds = true
view.layer.cornerRadius = 10
view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] // Top right corner, Top left corner respectively
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)
}
}
Here is a Swift version of #JohnnyRockex answer
extension UIView {
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
}
view.roundCorners([.topLeft, .bottomRight], radius: 10)
Note
If you're using Auto Layout, you'll need to subclass your UIView and call roundCorners in the view's layoutSubviews for optimal effect.
class View: UIView {
override func layoutSubviews() {
super.layoutSubviews()
self.roundCorners([.topLeft, .bottomLeft], radius: 10)
}
}
Swift code example here: https://stackoverflow.com/a/35621736/308315
Not directly. You will have to:
Create a CAShapeLayer
Set its path to be a CGPathRef based on view.bounds but with only two rounded corners (probably by using +[UIBezierPath bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:])
Set your view.layer.mask to be the CAShapeLayer
Here is a short method implemented like this:
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *openInMaps = [UIButton new];
[openInMaps setFrame:CGRectMake(15, 135, 114, 70)];
openInMaps = (UIButton *)[self roundCornersOnView:openInMaps onTopLeft:NO topRight:NO bottomLeft:YES bottomRight:NO radius:5.0];
}
- (UIView *)roundCornersOnView:(UIView *)view onTopLeft:(BOOL)tl topRight:(BOOL)tr bottomLeft:(BOOL)bl bottomRight:(BOOL)br radius:(float)radius {
if (tl || tr || bl || br) {
UIRectCorner corner = 0;
if (tl) {corner = corner | UIRectCornerTopLeft;}
if (tr) {corner = corner | UIRectCornerTopRight;}
if (bl) {corner = corner | UIRectCornerBottomLeft;}
if (br) {corner = corner | UIRectCornerBottomRight;}
UIView *roundedView = view;
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:roundedView.bounds byRoundingCorners:corner cornerRadii:CGSizeMake(radius, radius)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = roundedView.bounds;
maskLayer.path = maskPath.CGPath;
roundedView.layer.mask = maskLayer;
return roundedView;
}
return view;
}
In Swift 4.1 and Xcode 9.4.1
In iOS 11 this single line is enough:
detailsSubView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]//Set your view here
See the complete code:
//In viewDidLoad
if #available(iOS 11.0, *) {
detailsSubView.clipsToBounds = false
detailsSubView.layer.cornerRadius = 10
detailsSubView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
} else {
//For lower versions
}
But for lower versions
let rectShape = CAShapeLayer()
rectShape.bounds = detailsSubView.frame
rectShape.position = detailsSubView.center
rectShape.path = UIBezierPath(roundedRect: detailsSubView.bounds, byRoundingCorners: [.topLeft , .topRight], cornerRadii: CGSize(width: 20, height: 20)).cgPath
detailsSubView.layer.mask = rectShape
Complete code is.
if #available(iOS 11.0, *) {
detailsSubView.clipsToBounds = false
detailsSubView.layer.cornerRadius = 10
detailsSubView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
} else {
let rectShape = CAShapeLayer()
rectShape.bounds = detailsSubView.frame
rectShape.position = detailsSubView.center
rectShape.path = UIBezierPath(roundedRect: detailsSubView.bounds, byRoundingCorners: [.topLeft , .topRight], cornerRadii: CGSize(width: 20, height: 20)).cgPath
detailsSubView.layer.mask = rectShape
}
If you are using AutoResizing in storyboard write this code in viewDidLayoutSubviews().
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11.0, *) {
detailsSubView.clipsToBounds = false
detailsSubView.layer.cornerRadius = 10
detailsSubView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
} else {
let rectShape = CAShapeLayer()
rectShape.bounds = detailsSubView.frame
rectShape.position = detailsSubView.center
rectShape.path = UIBezierPath(roundedRect: detailsSubView.bounds, byRoundingCorners: [.topLeft , .topRight], cornerRadii: CGSize(width: 20, height: 20)).cgPath
detailsSubView.layer.mask = rectShape
}
}
There is a super simple way of doing it. I found it on here.
view.clipsToBounds = true
view.layer.cornerRadius = 24
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
It uses the stock cornerRadius property on the CALayer of a view. You just need to define the corners. layerMinXMinYCorner is top left layerMaxXMinYCorner is top right.
This would be the simplest answer:
yourView.layer.cornerRadius = 8
yourView.layer.masksToBounds = true
yourView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
Swift 4 Swift 5 easy way in 1 line
Usage:
//MARK:- Corner Radius of only two side of UIViews
self.roundCorners(view: yourview, corners: [.bottomLeft, .topRight], radius: 12.0)
Function:
//MARK:- Corner Radius of only two side of UIViews
func roundCorners(view :UIView, corners: UIRectCorner, radius: CGFloat){
let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
view.layer.mask = mask
}
In Objective-C
Usage:
[self.verticalSeparatorView roundCorners:UIRectCornerTopLeft | UIRectCornerTopRight radius:10.0];
Function used in a Category (only one corner):
-(void)roundCorners: (UIRectCorner) corners radius:(CGFloat)radius {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];
CAShapeLayer *mask = [[CAShapeLayer alloc] init];
mask.path = path.CGPath;
self.layer.mask = mask;
}
iOS 11 , Swift 4
And you can try this code:
if #available(iOS 11.0, *) {
element.clipsToBounds = true
element.layer.cornerRadius = CORNER_RADIUS
element.layer.maskedCorners = [.layerMaxXMaxYCorner]
} else {
// Fallback on earlier versions
}
And you can using this in table view cell.
My solution for rounding specific corners of UIView and UITextFiels in swift is to use
.layer.cornerRadius
and
layer.maskedCorners
of actual UIView or UITextFields.
Example:
fileprivate func inputTextFieldStyle() {
inputTextField.layer.masksToBounds = true
inputTextField.layer.borderWidth = 1
inputTextField.layer.cornerRadius = 25
inputTextField.layer.maskedCorners = [.layerMaxXMaxYCorner,.layerMaxXMinYCorner]
inputTextField.layer.borderColor = UIColor.white.cgColor
}
And by using
.layerMaxXMaxYCorner
and
.layerMaxXMinYCorner
, I can specify top right and bottom right corner of the UITextField to be rounded.
You can see the result here:
Try this code,
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:( UIRectCornerTopLeft | UIRectCornerTopRight) cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.view.bounds;
maskLayer.path = maskPath.CGPath;
view.layer.mask = maskLayer;
Emma: .TopRight and .BottomRight are not working for you perhaps because the call to view.roundCorners is done BEFORE final view bounds are calculated. Note that the Bezier Path derives from the view bounds at the time it is called. For example, if auto layout will narrow the view, the round corners on the right side might be outside the view.
Try to call it in viewDidLayoutSubviews, where the view's bound is final.
Swift 4
extension UIView {
func roundTop(radius:CGFloat = 5){
self.clipsToBounds = true
self.layer.cornerRadius = radius
if #available(iOS 11.0, *) {
self.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
} else {
// Fallback on earlier versions
}
}
func roundBottom(radius:CGFloat = 5){
self.clipsToBounds = true
self.layer.cornerRadius = radius
if #available(iOS 11.0, *) {
self.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
} else {
// Fallback on earlier versions
}
}
}
Simple extension
extension UIView {
func roundCorners(corners: UIRectCorner, radius: CGFloat) {
if #available(iOS 11, *) {
self.clipsToBounds = true
self.layer.cornerRadius = radius
var masked = CACornerMask()
if corners.contains(.topLeft) { masked.insert(.layerMinXMinYCorner) }
if corners.contains(.topRight) { masked.insert(.layerMaxXMinYCorner) }
if corners.contains(.bottomLeft) { masked.insert(.layerMinXMaxYCorner) }
if corners.contains(.bottomRight) { masked.insert(.layerMaxXMaxYCorner) }
self.layer.maskedCorners = masked
}
else {
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
}
}
Usage:
view.roundCorners(corners: [.bottomLeft, .bottomRight], radius: 12)
Use this extension, it'll cover everything.
extension UIView {
func roundTopCorners(radius: CGFloat = 10) {
self.clipsToBounds = true
self.layer.cornerRadius = radius
if #available(iOS 11.0, *) {
self.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
} else {
self.roundCorners(corners: [.topLeft, .topRight], radius: radius)
}
}
func roundBottomCorners(radius: CGFloat = 10) {
self.clipsToBounds = true
self.layer.cornerRadius = radius
if #available(iOS 11.0, *) {
self.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
} else {
self.roundCorners(corners: [.bottomLeft, .bottomRight], radius: radius)
}
}
private 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
}
}
and then use it like this:-
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.yourView.roundTopCorners()
}
Note:- I'll recommend you to don't put this code inside viewDidLayoutSubviews(), because whenever view updates, you'll get call inside it. So use viewDidAppear(), it'll work like a charm.
Here is best way Swift 5:
import UIKit
extension UIView {
func roundCorners(radius: CGFloat = 10, corners: UIRectCorner = .allCorners) {
self.clipsToBounds = true
self.layer.cornerRadius = radius
if #available(iOS 11.0, *) {
var arr: CACornerMask = []
let allCorners: [UIRectCorner] = [.topLeft, .topRight, .bottomLeft, .bottomRight, .allCorners]
for corn in allCorners {
if(corners.contains(corn)){
switch corn {
case .topLeft:
arr.insert(.layerMinXMinYCorner)
case .topRight:
arr.insert(.layerMaxXMinYCorner)
case .bottomLeft:
arr.insert(.layerMinXMaxYCorner)
case .bottomRight:
arr.insert(.layerMaxXMaxYCorner)
case .allCorners:
arr.insert(.layerMinXMinYCorner)
arr.insert(.layerMaxXMinYCorner)
arr.insert(.layerMinXMaxYCorner)
arr.insert(.layerMaxXMaxYCorner)
default: break
}
}
}
self.layer.maskedCorners = arr
} else {
self.roundCornersBezierPath(corners: corners, radius: radius)
}
}
private func roundCornersBezierPath(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
}
}
A way to do this programmatically would be to create a UIView over the top part of the UIView that has the rounded corners. Or you could hide the top underneath something.
// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds
byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight)
cornerRadii:CGSizeMake(7.0, 7.0)];
// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = cell.stripBlackImnageView.bounds;
maskLayer.path = maskPath.CGPath;
// Set the newly created shapelayer as the mask for the image view's layer
view.layer.mask = maskLayer;
The easiest way would be to make a mask with a rounded corner layer.
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = CGRectMake(0,0,maskWidth ,maskHeight);
maskLayer.contents = (__bridge id)[[UIImage imageNamed:#"maskImageWithRoundedCorners.png"] CGImage];
aUIView.layer.mask = maskLayer;
And don't forget to:
#import <QuartzCore/QuartzCore.h>
All of the answers already given are really good and valid (especially Yunus idea of using the mask property).
However I needed something a little more complex because my layer could often change sizes which mean I needed to call that masking logic every time and this was a little bit annoying.
I used swift extensions and computed properties to build a real cornerRadii property which takes care of auto updating the mask when layer is layed out.
This was achieved using Peter Steinberg great Aspects library for swizzling.
Full code is here:
extension CALayer {
// This will hold the keys for the runtime property associations
private struct AssociationKey {
static var CornerRect:Int8 = 1 // for the UIRectCorner argument
static var CornerRadius:Int8 = 2 // for the radius argument
}
// new computed property on CALayer
// You send the corners you want to round (ex. [.TopLeft, .BottomLeft])
// and the radius at which you want the corners to be round
var cornerRadii:(corners: UIRectCorner, radius:CGFloat) {
get {
let number = objc_getAssociatedObject(self, &AssociationKey.CornerRect) as? NSNumber ?? 0
let radius = objc_getAssociatedObject(self, &AssociationKey.CornerRadius) as? NSNumber ?? 0
return (corners: UIRectCorner(rawValue: number.unsignedLongValue), radius: CGFloat(radius.floatValue))
}
set (v) {
let radius = v.radius
let closure:((Void)->Void) = {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: v.corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.CGPath
self.mask = mask
}
let block: #convention(block) Void -> Void = closure
let objectBlock = unsafeBitCast(block, AnyObject.self)
objc_setAssociatedObject(self, &AssociationKey.CornerRect, NSNumber(unsignedLong: v.corners.rawValue), .OBJC_ASSOCIATION_RETAIN)
objc_setAssociatedObject(self, &AssociationKey.CornerRadius, NSNumber(float: Float(v.radius)), .OBJC_ASSOCIATION_RETAIN)
do { try aspect_hookSelector("layoutSublayers", withOptions: .PositionAfter, usingBlock: objectBlock) }
catch _ { }
}
}
}
I wrote a simple blog post explaining this.
A lovely extension to reuse Yunus Nedim Mehel solution
Swift 2.3
extension UIView {
func roundCornersWithLayerMask(cornerRadii: CGFloat, corners: UIRectCorner) {
let path = UIBezierPath(roundedRect: bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: cornerRadii, height: cornerRadii))
let maskLayer = CAShapeLayer()
maskLayer.path = path.CGPath
layer.mask = maskLayer
} }
Usage
let view = UIView()
view.roundCornersWithLayerMask(10,[.TopLeft,.TopRight])
If you're looking for an interface builder only solution there is one for iOS 11 and higher. See my answer here: https://stackoverflow.com/a/58626264
This is how you can set a corner radius for each corner of a button with Xamarin in C#:
var maskPath = UIBezierPath.FromRoundedRect(MyButton.Bounds, UIRectCorner.BottomLeft | UIRectCorner.BottomRight,
new CGSize(10.0, 10.0));
var maskLayer = new CAShapeLayer
{
Frame = MyButton.Bounds,
Path = maskPath.CGPath
};
MyButton.Layer.Mask = maskLayer;
For SwiftUI
I found these solutions you can check from here https://stackoverflow.com/a/56763282/3716103
I highly recommend the first one
Option 1: Using Path + GeometryReader
(more info on GeometryReader: https://swiftui-lab.com/geometryreader-to-the-rescue/)
struct ContentView : View {
var body: some View {
Text("Hello World!")
.foregroundColor(.white)
.font(.largeTitle)
.padding(20)
.background(RoundedCorners(color: .blue, tl: 0, tr: 30, bl: 30, br: 0))
}
}
RoundedCorners
struct RoundedCorners: View {
var color: Color = .white
var tl: CGFloat = 0.0
var tr: CGFloat = 0.0
var bl: CGFloat = 0.0
var br: CGFloat = 0.0
var body: some View {
GeometryReader { geometry in
Path { path in
let w = geometry.size.width
let h = geometry.size.height
// Make sure we do not exceed the size of the rectangle
let tr = min(min(self.tr, h/2), w/2)
let tl = min(min(self.tl, h/2), w/2)
let bl = min(min(self.bl, h/2), w/2)
let br = min(min(self.br, h/2), w/2)
path.move(to: CGPoint(x: w / 2.0, y: 0))
path.addLine(to: CGPoint(x: w - tr, y: 0))
path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false)
path.addLine(to: CGPoint(x: w, y: h - be))
path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false)
path.addLine(to: CGPoint(x: bl, y: h))
path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false)
path.addLine(to: CGPoint(x: 0, y: tl))
path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)
}
.fill(self.color)
}
}
}
RoundedCorners_Previews
struct RoundedCorners_Previews: PreviewProvider {
static var previews: some View {
RoundedCorners(color: .pink, tl: 40, tr: 40, bl: 40, br: 40)
}
}
Use this extension for set corner round and round border with round corners
use like this :
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
myView.roundCornersWithBorder(corners: [.topLeft, .topRight], radius: 8.0)
myView.roundCorners(corners: [.topLeft, .topRight], radius: 8.0)
}
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
}
func roundCornersWithBorder(corners: UIRectCorner, radius: CGFloat) {
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: radius, height: radius)).cgPath
layer.mask = maskLayer
// Add border
let borderLayer = CAShapeLayer()
borderLayer.path = maskLayer.path // Reuse the Bezier path
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor(red:3/255, green:33/255, blue:70/255, alpha: 0.15).cgColor
borderLayer.lineWidth = 2
borderLayer.frame = bounds
layer.addSublayer(borderLayer)
}
}
After change bit of code #apinho In swift 4.3 working fine
extension UIView {
func roundCornersWithLayerMask(cornerRadii: CGFloat, corners: UIRectCorner) {
let path = UIBezierPath(roundedRect: bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: cornerRadii, height: cornerRadii))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
layer.mask = maskLayer
}
}
To use this function for you view
YourViewName. roundCornersWithLayerMask(cornerRadii: 20,corners: [.topLeft,.topRight])
Another version of Stephane's answer.
import UIKit
class RoundCornerView: UIView {
var corners : UIRectCorner = [.topLeft,.topRight,.bottomLeft,.bottomRight]
var roundCornerRadius : CGFloat = 0.0
override func layoutSubviews() {
super.layoutSubviews()
if corners.rawValue > 0 && roundCornerRadius > 0.0 {
self.roundCorners(corners: corners, radius: roundCornerRadius)
}
}
private 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
}
}
In Swift 4.2, Create it via #IBDesignable like this:
#IBDesignable
class DesignableViewCustomCorner: UIView {
#IBInspectable var cornerRadious: CGFloat = 0 {
didSet {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadious, height: cornerRadious))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
}
}

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

How to set corner radius only only bottom-left, bottom-right, and top-left corners of a textview?
let rectShape = CAShapeLayer()
rectShape.backgroundColor = UIColor.redColor().CGColor
rectShape.bounds = messages.frame
rectShape.position = messages.center
rectShape.path = UIBezierPath(roundedRect: messages.bounds, byRoundingCorners: .BottomLeft | .TopRight, cornerRadii: CGSize(width: 20, height: 20)).CGPath
messages.layer.addSublayer(rectShape)
this code creates two rects. I dont know why.
(swift 4/iOS 11) Just simply say for bottom:
yourView.clipsToBounds = true
yourView.layer.cornerRadius = 10
yourView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
for Up:
yourView.clipsToBounds = true
yourView.layer.cornerRadius = 10
yourView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
in your case:
yourView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]
Hope this help :)
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
Swift 3
extension UIView {
func roundCorners(_ corners:UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
}
Use like this
YourView.roundCorners([.topLeft, .bottomLeft], radius: 10)
A better answer for both iOS 11 and iOS 10 bottom corner would be
if #available(iOS 11.0, *){
view.clipsToBounds = true
view.layer.cornerRadius = 10
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
}else{
let rectShape = CAShapeLayer()
rectShape.bounds = view.frame
rectShape.position = view.center
rectShape.path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: [.bottomLeft , .bottomRight], cornerRadii: CGSize(width: 20, height: 20)).cgPath
view.layer.backgroundColor = UIColor.green.cgColor
view.layer.mask = rectShape
}
in case this didnt work on iOS 10 and below, try running the code in viewDidLayoutSubviews() of your viewcontroller class like this
override func viewDidLayoutSubviews() {
if #available(iOS 11.0, *){
}else{
let rectShape = CAShapeLayer()
rectShape.bounds = view.frame
rectShape.position = view.center
rectShape.path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: [.bottomLeft , .bottomRight], cornerRadii: CGSize(width: 20, height: 20)).cgPath
view.layer.backgroundColor = UIColor.green.cgColor
view.layer.mask = rectShape
}
Swift 4+
func roundCorners(with CACornerMask: CACornerMask, radius: CGFloat) {
self.layer.cornerRadius = radius
self.layer.maskedCorners = [CACornerMask]
}
How to use
//Top right
roundCorners(with: [.layerMaxXMinYCorner], radius: 20)
//Top left
roundCorners(with: [.layerMinXMinYCorner], radius: 20)
//Bottom right
roundCorners(with: [.layerMaxXMaxYCorner], radius: 20)
//Bottom left
roundCorners(with: [.layerMinXMaxYCorner], radius: 20)
OR
Another way using CACornerMask
extension UIView{
enum RoundCornersAt{
case topRight
case topLeft
case bottomRight
case bottomLeft
}
//multiple corners using CACornerMask
func roundCorners(corners:[RoundCornersAt], radius: CGFloat) {
self.layer.cornerRadius = radius
self.layer.maskedCorners = [
corners.contains(.topRight) ? .layerMaxXMinYCorner:.init(),
corners.contains(.topLeft) ? .layerMinXMinYCorner:.init(),
corners.contains(.bottomRight) ? .layerMaxXMaxYCorner:.init(),
corners.contains(.bottomLeft) ? .layerMinXMaxYCorner:.init(),
]
}
}
You can use like below
myView.roundCorners(corners: [.topLeft,.bottomLeft], radius: 20)
OR
Multiple corners using UIRectCorner
func roundedCorners(corners : UIRectCorner, radius : CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
How to use
roundedCorners(corners: [.topLeft, .topRight], radius: 20)
For subview & POPUPs [Swift 5]
override func layoutSublayers(of layer: CALayer) {
searchBarPopup.clipsToBounds = true
searchBarPopup.layer.cornerRadius = 10
searchBarPopup.layer.maskedCorners = [ .layerMaxXMinYCorner, .layerMinXMinYCorner]
}
output:
Swift 4
override func viewDidLoad() {
let topRight = UIView(frame: CGRect(x: 120, y: 200, width: 120, height: 120))
topRight.roundedTop()
topRight.backgroundColor = .red
self.view.center = topRight.center
self.view.addSubview(topRight)
super.viewDidLoad()
}
Output :
extension on UIView Swift 4 : Reference Link
Here is an extension for iOS 11+
import Foundation
import UIKit
extension UIView {
func roundCorners(_ corners: CACornerMask, radius: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
self.layer.maskedCorners = corners
self.layer.cornerRadius = radius
self.layer.borderWidth = borderWidth
self.layer.borderColor = borderColor.cgColor
}
}
Usage:-
self.yourView.roundCorners([.layerMaxXMaxYCorner, .layerMaxXMinYCorner], radius: 20.0, borderColor: UIColor.green, borderWidth: 1)
Swift 5+
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner]
Add RoundedCornerView.swift file to your project
Add an UIView to your ViewController
Change Custom Class in Identity Inspector to RoundedCornerView
Go to Attribute Inspector pick Corner Radius (CGFloat) and On the corners you want rounded.
RoundedCornerView.swift
import UIKit
#IBDesignable
class RoundedCornerView: UIView {
var cornerRadiusValue : CGFloat = 0
var corners : UIRectCorner = []
#IBInspectable public var cornerRadius : CGFloat {
get {
return cornerRadiusValue
}
set {
cornerRadiusValue = newValue
}
}
#IBInspectable public var topLeft : Bool {
get {
return corners.contains(.topLeft)
}
set {
setCorner(newValue: newValue, for: .topLeft)
}
}
#IBInspectable public var topRight : Bool {
get {
return corners.contains(.topRight)
}
set {
setCorner(newValue: newValue, for: .topRight)
}
}
#IBInspectable public var bottomLeft : Bool {
get {
return corners.contains(.bottomLeft)
}
set {
setCorner(newValue: newValue, for: .bottomLeft)
}
}
#IBInspectable public var bottomRight : Bool {
get {
return corners.contains(.bottomRight)
}
set {
setCorner(newValue: newValue, for: .bottomRight)
}
}
func setCorner(newValue: Bool, for corner: UIRectCorner) {
if newValue {
addRectCorner(corner: corner)
} else {
removeRectCorner(corner: corner)
}
}
func addRectCorner(corner: UIRectCorner) {
corners.insert(corner)
updateCorners()
}
func removeRectCorner(corner: UIRectCorner) {
if corners.contains(corner) {
corners.remove(corner)
updateCorners()
}
}
func updateCorners() {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: cornerRadiusValue, height: cornerRadiusValue))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
}
github link : RoundedCornerView
The best solution I could come up with all of the above solution is this.
extension UIView {
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
if #available(iOS 11, *) {
var cornerMask = CACornerMask()
if(corners.contains(.topLeft)){
cornerMask.insert(.layerMinXMinYCorner)
}
if(corners.contains(.topRight)){
cornerMask.insert(.layerMaxXMinYCorner)
}
if(corners.contains(.bottomLeft)){
cornerMask.insert(.layerMinXMaxYCorner)
}
if(corners.contains(.bottomRight)){
cornerMask.insert(.layerMaxXMaxYCorner)
}
self.layer.cornerRadius = radius
self.layer.maskedCorners = cornerMask
} else {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
}
}
I believe that this way the implementation is easy while selecting the sides.
view.roundCorners([.bottomLeft, .bottomRight, .topLeft], radius: 16)
extension UIView {
func roundCorners(_ corners: CACornerMask, radius: CGFloat) {
if #available(iOS 11, *) {
self.layer.cornerRadius = radius
self.layer.maskedCorners = corners
} else {
var cornerMask = UIRectCorner()
if(corners.contains(.layerMinXMinYCorner)){
cornerMask.insert(.topLeft)
}
if(corners.contains(.layerMaxXMinYCorner)){
cornerMask.insert(.topRight)
}
if(corners.contains(.layerMinXMaxYCorner)){
cornerMask.insert(.bottomLeft)
}
if(corners.contains(.layerMaxXMaxYCorner)){
cornerMask.insert(.bottomRight)
}
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: cornerMask, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
}
}
issue solved right top and right bottom work now
Tested code in iOS 9 , 10 , 11 version
extension UIView {
func roundCorners(_ corners:UIRectCorner,_ cormerMask:CACornerMask, radius: CGFloat) {
if #available(iOS 11.0, *){
self.clipsToBounds = false
self.layer.cornerRadius = radius
self.layer.maskedCorners = cormerMask
}else{
let rectShape = CAShapeLayer()
rectShape.bounds = self.frame
rectShape.position = self.center
rectShape.path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)).cgPath
self.layer.mask = rectShape
}
}
}
As #kazi.munshimun gave a view-only setup suggestion, it is wonderful, however I didn't want create a whole view for this, and I was looking for a one-only field for cornerRadius.
So I came up with the idea of flag combinations where for example BottomLeft(4) + BottomRight(8) = 12
Therefore, it needs on your view to set up the number combination from 0 (none) to 15 (all of corners) + clipsToBounds enabled. And that's it ! Entirely customizable through file.
This solution example works for iOS 11.0+ (Swift 4+) It can be adapted for lower versions :
extension UIView {
enum CornerEnum: Int {
case None = 0
case TopLeft = 1
case TopRight = 2
case BottomLeft = 4
case BottomRight = 8
}
#IBInspectable
var radiusCorners: Int {
set {
var corners = CACornerMask()
if(newValue & CornerEnum.TopLeft.rawValue != 0){
corners.insert(.layerMinXMinYCorner)
}
if(newValue & CornerEnum.TopRight.rawValue != 0){
corners.insert(.layerMaxXMinYCorner)
}
if(newValue & CornerEnum.BottomLeft.rawValue != 0){
corners.insert(.layerMinXMaxYCorner)
}
if(newValue & CornerEnum.BottomRight.rawValue != 0){
corners.insert(.layerMaxXMaxYCorner)
}
layer.maskedCorners = corners
}
get { return self.radiusCorners }
}
}

How do I create a circle with CALayer?

I have the code below tested, but when I give it constraints it becomes a little small circle:
override func drawRect(rect: CGRect) {
var path = UIBezierPath(ovalInRect: rect)
fillColor.setFill()
path.fill()
//set up the width and height variables
//for the horizontal stroke
let plusHeight:CGFloat = 300.0
let plusWidth:CGFloat = 450.0
//create the path
var plusPath = UIBezierPath()
//set the path's line width to the height of the stroke
plusPath.lineWidth = plusHeight
//move the initial point of the path
//to the start of the horizontal stroke
plusPath.moveToPoint(CGPoint(
x:self.bounds.width/2 - plusWidth/2 + 0.5,
y:self.bounds.height/2 + 0.5))
//add a point to the path at the end of the stroke
plusPath.addLineToPoint(CGPoint(
x:self.bounds.width/2 + plusWidth/2 + 0.5,
y:self.bounds.height/2 + 0.5))
}
Change radius and fillColor as you want. :)
import Foundation
import UIKit
class CircleLayerView: UIView {
var circleLayer: CAShapeLayer!
override func draw(_ rect: CGRect) {
super.draw(rect)
if circleLayer == nil {
circleLayer = CAShapeLayer()
let radius: CGFloat = 150.0
circleLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 2.0 * radius, height: 2.0 * radius), cornerRadius: radius).cgPath
circleLayer.position = CGPoint(x: self.frame.midX - radius, y: self.frame.midY - radius)
circleLayer.fillColor = UIColor.blue.cgColor
self.layer.addSublayer(circleLayer)
}
}
}
The rect being passed into drawRect is the area that needs to be updated, not the size of the drawing. In your case, you would probably just ignore the rect being passed in and set the circle to the size you want.
//// Oval Drawing
var ovalPath = UIBezierPath(ovalInRect: CGRectMake(0, 0, 300, 300))
UIColor.whiteColor().setFill()
ovalPath.fill()
The only correct way to do it:
private lazy var whiteCoin: CAShapeLayer = {
let c = CAShapeLayer()
c.path = UIBezierPath(ovalIn: bounds).cgPath
c.fillColor = UIColor.white.cgColor
layer.addSublayer(c)
return c
}()
That literally makes a layer, as you wanted.
In iOS you must correctly resize any layers you are in charge of, when the view is resized/redrawn.
How do you do that? It is the very raison d'etre of layoutSubviews.
class ProperExample: UIView {
open override func layoutSubviews() {
super.layoutSubviews()
whiteCoin.frame = bounds
}
}
It's that simple.
class ProperExample: UIView {
open override func layoutSubviews() {
super.layoutSubviews()
whiteCoin.frame = bounds
}
private lazy var whiteCoin: CAShapeLayer = {
let c = CAShapeLayer()
c.path = UIBezierPath(ovalIn: bounds).cgPath
c.fillColor = UIColor.white.cgColor
layer.addSublayer(c)
return c
}()
}