Related
Hi i want to create a quarter transparent hole at right bottom on overlay UIView.
i am able to solve it using below code. But it does not look right as i am creating a rectangle outside the bond of view.
What i have tried:
#implementation PartialTransparentView
- (id)initWithBottomRightCornerRadiusForView:(UIView *)view withRadius:(CGFloat)radius
{
[self commonInitWithRect:CGRectMake(view.frame.size.width - radius, view.frame.size.height - radius, radius*2, radius*2)];
self = [super initWithFrame:CGRectMake(0, 0, 5000, 5000)];//**it does not look right to me**
if (self) {
// Initialization code
self.opaque = NO;
}
return self;
}
-(void)commonInitWithRect:(CGRect)rect{
backgroundColor = [UIColor colorWithWhite:1 alpha:0.75];
rectToBeSurrounded = rect;
}
- (void)drawRect:(CGRect)rect {
[backgroundColor setFill];
UIRectFill(rect);
CGFloat x = rectToBeSurrounded.origin.x;
CGFloat y = rectToBeSurrounded.origin.y;
CGFloat width = rectToBeSurrounded.size.width;
CGFloat height = rectToBeSurrounded.size.height;
//create outer square
CGFloat outerX = (x - width/2);
CGFloat outerY = y - height/2;
CGFloat outerWidth = 2*width;
CGFloat outerHeight = outerWidth;
//create outer square
CGRect outerRect = CGRectMake(outerX, outerY, outerWidth, outerHeight);
CGRect holeRectIntersection = CGRectIntersection( outerRect, rect );
CGContextRef context = UIGraphicsGetCurrentContext();
if( CGRectIntersectsRect( holeRectIntersection, rect ) )
{
CGContextAddEllipseInRect(context, holeRectIntersection);
CGContextClip(context);
CGContextClearRect(context, holeRectIntersection);
CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
CGContextFillRect( context, holeRectIntersection);
}
}
Now i am using above code as :
PartialTransparentView *transparentView = [[PartialTransparentView alloc] initWithBottomRightCornerRadiusForView:self.view withRadius:50];
[self.view addSubview:transparentView];
Result as expected:
i know my solution will break if i have to acheive same thing but on top left of view.
what i am looking for just provide center (x, y) and radius for circle and get desired results.
Thanks
Basd on Mr.T
UIView *transparentView = [[UIView alloc] initWithFrame:self.view.frame];
[transparentView setBackgroundColor:[UIColor colorWithWhite:1 alpha:0.75]];
[self.view addSubview:transparentView];
circleView *acircleView = [[circleView alloc] initWithFrame:CGRectMake(50, 50, 60, 60)];
[acircleView setBackgroundColor:[UIColor grayColor]];
[transparentView addSubview:acircleView];
and circleView.m
- (void)drawRect:(CGRect)rect {
// Drawing code
//// Oval Drawing
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(50, 50, 60, 60)];
[UIColor.grayColor setFill];
[ovalPath fill];
}
output:
My suggestions is to add the transparent view as a separate view on your view controller. This can be either done on storyboard,so that you can set the background color and the alpha value to give the transparent effect!!!
Now create another view to make the circle and add it to transparent view, , and move this view on the transparent view according to your need!!!
Create the circle using bezier path:
circleView.m
- (void)drawRect:(CGRect)frame {
//// Oval Drawing
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(CGRectGetMinX(frame), CGRectGetMinY(frame), 60, 60)];
[UIColor.grayColor setFill];
[ovalPath fill];
}
For testing purpose, I have created a circle view on my IB and created an outlet property in my view controller.
HEre is the screenshot.
Now to move the circle, I can simply change the frame of the circle view, wherever I need.
For example, If I want to move it to top left, I simply do:
-(void)moveCircleViewwithX:(float) x withY:(float) y{
_cView.frame=CGRectMake(x, y, _cView.frame.size.width, _cView.frame.size.height);
}
The result will be:
Update
Put the following the drawRect method of transparentView:
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect transparentPart = self.seeRect; //this is the rect of the circle view
CGContextAddEllipseInRect(ctx,transparentPart); //make the circle shape
CGContextClip(ctx);
CGContextClearRect(ctx, transparentPart);
and in your view controller:
when you want to apply the mask i.e the transparent for both circle and for the transparent layer:
-(void)applyMask{
[_cView setCircleColor:[UIColor clearColor]]; //circle view bezier path color
[_cView setNeedsDisplay];
[_tView setSeeRect:_cView.frame]; //set the transparency cut on transparency view
[_tView setNeedsDisplay];
}
Once you do this, you will get the transparency view!!!
You can move the circle by simply calling
[self moveCircleViewwithX:-30 withY:10]; //top left corner
and you can apply the transparency mask by simply calling:
[self applyMask];
So, the final result after you call the applyMask method will be:
In my application - there are four buttons named as follows:
Top - left
Bottom - left
Top - right
Bottom - right
Above the buttons there is an image view (or a UIView).
Now, suppose a user taps on - top - left button. Above image / view should be rounded at that particular corner.
I am having some difficulty in applying rounded corners to the UIView.
Right now I am using the following code to apply the rounded corners to each view:
// imgVUserImg is a image view on IB.
imgVUserImg.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"any Url Here"];
CALayer *l = [imgVUserImg layer];
[l setMasksToBounds:YES];
[l setCornerRadius:5.0];
[l setBorderWidth:2.0];
[l setBorderColor:[[UIColor darkGrayColor] CGColor]];
Above code is applying the roundness to each of corners of supplied View. Instead I just wanted to apply roundness to selected corners like - top / top+left / bottom+right etc.
Is it possible? How?
Starting in iOS 3.2, you can use the functionality of UIBezierPaths to create an out-of-the-box rounded rect (where only corners you specify are rounded). You can then use this as the path of a CAShapeLayer, and use this as a mask for your view's layer:
// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds
byRoundingCorners:UIRectCornerTopLeft
cornerRadii:CGSizeMake(10.0, 10.0)];
// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageView.bounds;
maskLayer.path = maskPath.CGPath;
// Set the newly created shape layer as the mask for the image view's layer
imageView.layer.mask = maskLayer;
And that's it - no messing around manually defining shapes in Core Graphics, no creating masking images in Photoshop. The layer doesn't even need invalidating. Applying the rounded corner or changing to a new corner is as simple as defining a new UIBezierPath and using its CGPath as the mask layer's path. The corners parameter of the bezierPathWithRoundedRect:byRoundingCorners:cornerRadii: method is a bitmask, and so multiple corners can be rounded by ORing them together.
EDIT: Adding a shadow
If you're looking to add a shadow to this, a little more work is required.
Because "imageView.layer.mask = maskLayer" applies a mask, a shadow will not ordinarily show outside of it. The trick is to use a transparent view, and then add two sublayers (CALayers) to the view's layer: shadowLayer and roundedLayer. Both need to make use of the UIBezierPath. The image is added as the content of roundedLayer.
// Create a transparent view
UIView *theView = [[UIView alloc] initWithFrame:theFrame];
[theView setBackgroundColor:[UIColor clearColor]];
// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:theView.bounds
byRoundingCorners:UIRectCornerTopLeft
cornerRadii:CGSizeMake(10.0f, 10.0f)];
// Create the shadow layer
CAShapeLayer *shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:theView.bounds];
[shadowLayer setMasksToBounds:NO];
[shadowLayer setShadowPath:maskPath.CGPath];
// ...
// Set the shadowColor, shadowOffset, shadowOpacity & shadowRadius as required
// ...
// Create the rounded layer, and mask it using the rounded mask layer
CALayer *roundedLayer = [CALayer layer];
[roundedLayer setFrame:theView.bounds];
[roundedLayer setContents:(id)theImage.CGImage];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
[maskLayer setFrame:theView.bounds];
[maskLayer setPath:maskPath.CGPath];
roundedLayer.mask = maskLayer;
// Add these two layers as sublayers to the view
[theView.layer addSublayer:shadowLayer];
[theView.layer addSublayer:roundedLayer];
I used the answer over at How do I create a round cornered UILabel on the iPhone? and the code from How is a rounded rect view with transparency done on iphone? to make this code.
Then I realized I'd answered the wrong question (gave a rounded UILabel instead of UIImage) so I used this code to change it:
http://discussions.apple.com/thread.jspa?threadID=1683876
Make an iPhone project with the View template. In the view controller, add this:
- (void)viewDidLoad
{
CGRect rect = CGRectMake(10, 10, 200, 100);
MyView *myView = [[MyView alloc] initWithFrame:rect];
[self.view addSubview:myView];
[super viewDidLoad];
}
MyView is just a UIImageView subclass:
#interface MyView : UIImageView
{
}
I'd never used graphics contexts before, but I managed to hobble together this code. It's missing the code for two of the corners. If you read the code, you can see how I implemented this (by deleting some of the CGContextAddArc calls, and deleting some of the radius values in the code. The code for all corners is there, so use that as a starting point and delete the parts that create corners you don't need. Note that you can make rectangles with 2 or 3 rounded corners too if you want.
The code's not perfect, but I'm sure you can tidy it up a little bit.
static void addRoundedRectToPath(CGContextRef context, CGRect rect, float radius, int roundedCornerPosition)
{
// all corners rounded
// CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);
// CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);
// CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius,
// radius, M_PI / 4, M_PI / 2, 1);
// CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius,
// rect.origin.y + rect.size.height);
// CGContextAddArc(context, rect.origin.x + rect.size.width - radius,
// rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);
// CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + radius);
// CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + radius,
// radius, 0.0f, -M_PI / 2, 1);
// CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);
// CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius,
// -M_PI / 2, M_PI, 1);
// top left
if (roundedCornerPosition == 1) {
CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);
CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);
CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius,
radius, M_PI / 4, M_PI / 2, 1);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width,
rect.origin.y + rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y);
}
// bottom left
if (roundedCornerPosition == 2) {
CGContextMoveToPoint(context, rect.origin.x, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width,
rect.origin.y + rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);
CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius,
-M_PI / 2, M_PI, 1);
}
// add the other corners here
CGContextClosePath(context);
CGContextRestoreGState(context);
}
-(UIImage *)setImage
{
UIImage *img = [UIImage imageNamed:#"my_image.png"];
int w = img.size.width;
int h = img.size.height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
CGContextBeginPath(context);
CGRect rect = CGRectMake(0, 0, w, h);
addRoundedRectToPath(context, rect, 50, 1);
CGContextClosePath(context);
CGContextClip(context);
CGContextDrawImage(context, rect, img.CGImage);
CGImageRef imageMasked = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
[img release];
return [UIImage imageWithCGImage:imageMasked];
}
alt text http://nevan.net/skitch/skitched-20100224-092237.png
Don't forget that you'll need to get the QuartzCore framework in there for this to work.
I have used this code in many places in my code and it works 100% correctly. You can change any corder by changed one property "byRoundingCorners:UIRectCornerBottomLeft"
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerBottomLeft cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = view.bounds;
maskLayer.path = maskPath.CGPath;
view.layer.mask = maskLayer;
[maskLayer release];
In iOS 11, we can now round some corners only
let view = UIView()
view.clipsToBounds = true
view.layer.cornerRadius = 8
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
CALayer extension with Swift 3+ syntax
extension CALayer {
func round(roundedRect rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadii: CGSize) -> Void {
let bp = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: cornerRadii)
let sl = CAShapeLayer()
sl.frame = self.bounds
sl.path = bp.cgPath
self.mask = sl
}
}
It can be used like:
let layer: CALayer = yourView.layer
layer.round(roundedRect: yourView.bounds, byRoundingCorners: [.bottomLeft, .topLeft], cornerRadii: CGSize(width: 5, height: 5))
Stuarts example for rounding specific corners works great. If you want to round multiple corners like top left and right this is how to do it
// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageview
byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight
cornerRadii:CGSizeMake(10.0, 10.0)];
// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageview.bounds;
maskLayer.path = maskPath.CGPath;
// Set the newly created shape layer as the mask for the image view's layer
imageview.layer.mask = maskLayer;
there is an easier and faster answer that may work depending on your needs and also works with shadows. you can set maskToBounds on the superlayer to true, and offset the child layers so that 2 of their corners are outside the superlayer bounds, effectively cutting the rounded corners on 2 sides away.
of course this only works when you want to have only 2 rounded corners on the same side and the content of the layer looks the same when you cut off a few pixels from one side. works great for having bar charts rounded only on the top side.
Thanks for sharing. Here I'd like to share the solution on swift 2.0 for further reference on this issue. (to conform the UIRectCorner's protocol)
let mp = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.bottomLeft, .TopLeft], cornerRadii: CGSize(width: 10, height: 10))
let ml = CAShapeLayer()
ml.frame = self.bounds
ml.path = mp.CGPath
self.layer.mask = ml
See this related question. You'll have to draw your own rectangle to a CGPath with some rounded corners, add the CGPath to your CGContext and then clip to it using CGContextClip.
You can also draw the rounded rect with alpha values to an image and then use that image to create a new layer which you set as your layer's mask property (see Apple's documentation).
Half a decade late, but I think the current way people do this isn't 100% right. Many people have had the issue that using the UIBezierPath + CAShapeLayer method interferes with Auto-layout, especially when it is set on the Storyboard. No answers go over this, so I decided to add my own.
There is a very easy way to circumvent that: Draw the rounded corners in the drawRect(rect: CGRect) function.
For example, if I wanted top rounded corners for a UIView, I'd subclass UIView and then use that subclass wherever appropriate.
import UIKit
class TopRoundedView: UIView {
override func drawRect(rect: CGRect) {
super.drawRect(rect)
var maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: UIRectCorner.TopLeft | UIRectCorner.TopRight, cornerRadii: CGSizeMake(5.0, 5.0))
var maskLayer = CAShapeLayer()
maskLayer.frame = self.bounds
maskLayer.path = maskPath.CGPath
self.layer.mask = maskLayer
}
}
This is the best way to conquer the issue and doesn't take any time at all to adapt to.
Rounding only some corners won't play nice with auto resizing or auto layout.
So another option is to use regular cornerRadius and hide the corners you don't want under another view or outside its superview bounds making sure it is set to clip its contents.
To add to to the answer and the addition, I created a simple, reusable UIView in Swift. Depending on your use case, you might want to make modifications (avoid creating objects on every layout etc.), but I wanted to keep it as simple as possible. The extension allows you to apply this to other view's (ex. UIImageView) easier if you do not like subclassing.
extension UIView {
func roundCorners(_ roundedCorners: UIRectCorner, toRadius radius: CGFloat) {
roundCorners(roundedCorners, toRadii: CGSize(width: radius, height: radius))
}
func roundCorners(_ roundedCorners: UIRectCorner, toRadii cornerRadii: CGSize) {
let maskBezierPath = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: roundedCorners,
cornerRadii: cornerRadii)
let maskShapeLayer = CAShapeLayer()
maskShapeLayer.frame = bounds
maskShapeLayer.path = maskBezierPath.cgPath
layer.mask = maskShapeLayer
}
}
class RoundedCornerView: UIView {
var roundedCorners: UIRectCorner = UIRectCorner.allCorners
var roundedCornerRadii: CGSize = CGSize(width: 10.0, height: 10.0)
override func layoutSubviews() {
super.layoutSubviews()
roundCorners(roundedCorners, toRadii: roundedCornerRadii)
}
}
Here's how you would apply it to a UIViewController:
class MyViewController: UIViewController {
private var _view: RoundedCornerView {
return view as! RoundedCornerView
}
override func loadView() {
view = RoundedCornerView()
}
override func viewDidLoad() {
super.viewDidLoad()
_view.roundedCorners = [.topLeft, .topRight]
_view.roundedCornerRadii = CGSize(width: 10.0, height: 10.0)
}
}
Wrapping up Stuart's answer, you can have rounding corner method as the following:
#implementation UIView (RoundCorners)
- (void)applyRoundCorners:(UIRectCorner)corners radius:(CGFloat)radius {
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
}
#end
So to apply rounding corner, you simply do:
[self.imageView applyRoundCorners:UIRectCornerTopRight|UIRectCornerTopLeft radius:10];
I'd suggest defining a layer's mask. The mask itself should be a CAShapeLayer object with a dedicated path. You can use the next UIView extension (Swift 4.2):
extension UIView {
func round(corners: UIRectCorner, with radius: CGFloat) {
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius)
).cgPath
layer.mask = maskLayer
}
}
I am applying corner radius to a UIView i.e. UIRectCornerTopLeft and UIRectCornerTopRight. When I apply this, the border is gone at the corners. How to avoid this?
This is how I apply border to UIView:
[self.middleView addRoundedCorners:UIRectCornerTopLeft|UIRectCornerTopRight withRadii:CGSizeMake(4, 4)];
self.middleView.layer.borderWidth = 0.5f;
self.middleView.layer.borderColor = [[UIColor colorWith8BitRed:0 green:0 blue:0 alpha:0.25]
And this is a category I am using for applying optional rounded corners:
#import "UIView+Roundify.h"
#implementation UIView (Roundify)
- (void)addRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii {
CALayer *tMaskLayer = [self maskForRoundedCorners:corners withRadii:radii];
self.layer.mask = tMaskLayer;
}
- (CALayer*)maskForRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii {
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.bounds;
UIBezierPath *roundedPath = [UIBezierPath bezierPathWithRoundedRect:
maskLayer.bounds byRoundingCorners:corners cornerRadii:radii];
maskLayer.fillColor = [[UIColor whiteColor] CGColor];
maskLayer.backgroundColor = [[UIColor clearColor] CGColor];
maskLayer.path = [roundedPath CGPath];
return maskLayer;
}
Try below code it work
Your view which you want to rounded TopLeft and TopRight
UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(50, 100, 100, 100)];
[view1 setBackgroundColor:[UIColor grayColor]];
[self.view addSubview:view1];
Set Corner as below code
UIBezierPath *maskPath;
maskPath = [UIBezierPath bezierPathWithRoundedRect:view1.bounds byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerTopRight) cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.view.bounds;
maskLayer.path = maskPath.CGPath;
view1.layer.mask = maskLayer;
OUTPUT IS:
Found this piece of code. Have not actually tried it, but seems like it is what you need.
- (void)drawDashedBorderAroundView:(UIView *)v {
//border definitions
CGFloat cornerRadius = 10;
CGFloat borderWidth = 2;
NSInteger dashPattern1 = 8;
NSInteger dashPattern2 = 8;
UIColor *lineColor = [UIColor orangeColor];
//drawing
CGRect frame = v.bounds;
CAShapeLayer *_shapeLayer = [CAShapeLayer layer];
//creating a path
CGMutablePathRef path = CGPathCreateMutable();
//drawing a border around a view
CGPathMoveToPoint(path, NULL, 0, frame.size.height - cornerRadius);
CGPathAddLineToPoint(path, NULL, 0, cornerRadius);
CGPathAddArc(path, NULL, cornerRadius, cornerRadius, cornerRadius, M_PI, -M_PI_2, NO);
CGPathAddLineToPoint(path, NULL, frame.size.width - cornerRadius, 0);
CGPathAddArc(path, NULL, frame.size.width - cornerRadius, cornerRadius, cornerRadius, -M_PI_2, 0, NO);
CGPathAddLineToPoint(path, NULL, frame.size.width, frame.size.height - cornerRadius);
CGPathAddArc(path, NULL, frame.size.width - cornerRadius, frame.size.height - cornerRadius, cornerRadius, 0, M_PI_2, NO);
CGPathAddLineToPoint(path, NULL, cornerRadius, frame.size.height);
CGPathAddArc(path, NULL, cornerRadius, frame.size.height - cornerRadius, cornerRadius, M_PI_2, M_PI, NO);
//path is set as the _shapeLayer object's path
_shapeLayer.path = path;
CGPathRelease(path);
_shapeLayer.backgroundColor = [[UIColor clearColor] CGColor];
_shapeLayer.frame = frame;
_shapeLayer.masksToBounds = NO;
[_shapeLayer setValue:[NSNumber numberWithBool:NO] forKey:#"isCircle"];
_shapeLayer.fillColor = [[UIColor clearColor] CGColor];
_shapeLayer.strokeColor = [lineColor CGColor];
_shapeLayer.lineWidth = borderWidth;
_shapeLayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:dashPattern1], [NSNumber numberWithInt:dashPattern2], nil];
_shapeLayer.lineCap = kCALineCapRound;
//_shapeLayer is added as a sublayer of the view, the border is visible
[v.layer addSublayer:_shapeLayer];
v.layer.cornerRadius = cornerRadius;
}
This piece of code adds a dashed line, but you can modify that by _shapeLayer.lineDashPattern.
Unless there is some specific requirement which we're not aware of, the bezier path and mask are unnecessary if all you're trying to do is round the corners and have a border. I would normally just do this:
myView.layer.borderWidth=2;
myView.layer.cornerRadius=5;
Is it that you only want the top corners rounded that you need to not use the layer rounding? If so, why not use that and then overlay a thin view to cover the bottom bit? A bit fiddly, but I find that the more you can rely on the standard controls to draw themselves rather than having to step into core graphics, the better.
Edit: ok, given that it needs to have the bottom corners not rounded, how about if you had a category on UIView with 2 subviews: 1 with 4 rounded corners and another layed over the top (self bringSubviewToFront) which simply covers the rounded view's "footer" with a non-rounded strip, ie a view with equal width and tiny height which is equal to the rounded corner radius. If you have a solid color background then just make both subviews the same; if you have some texture or image background, make them both transparent and put the texture/image on the super view (the parent view who is using your category's specific layout method). Then finally, put the border on that same superview. Should work, let me know what you think.
Here's sample code:
//Called by VC:
HICircleView *circleView = [[HICircleView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
// init of circle view
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
CAShapeLayer *borderLayer = [CAShapeLayer layer];
borderLayer.fillColor = [UIColor whiteColor].CGColor;
borderLayer.path = [UIBezierPath bezierPathWithOvalInRect:self.frame].CGPath;
borderLayer.strokeColor = [[UIColor redColor] CGColor];
borderLayer.lineWidth = 5;
[self.layer addSublayer:borderLayer];
}
return self;
}
OK, thanks for the answer. to shift i:
CGRect rect = CGRectMake(3, 3, self.frame.size.width, self.frame.size.height);
borderLayer.path = [UIBezierPath bezierPathWithOvalInRect:rect].CGPath;
And made 6 the line width.
Setting the lineWidth draws a line, where the actual path is exactly in the middle of the drawn line.
If you want the drawn line to line up with something, you will have to shift the path by half the lineWidth.
You can shift the path by using - (void)applyTransform:(CGAffineTransform)transform on UIBezierPath and apply a translate transform.
If you want a drawn path to be contained in a certain area, shifting the path doesn't help. In that case just create a smaller path. If you want to draw a 100ptx100pt rect with a line width of 5, you have to draw a path in a 95pt*95pt rect (2.5pt space on either side).
You'd rather choose your view's bounds property for calculation. Frame property won't work properly if it's origin is greater than (0,0). You can use CGRectInsets to adjust your circle's rectangle instead of performing transform calculations. This will automatically position the rectangle centered within the original rectangle.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
CAShapeLayer *borderLayer = [CAShapeLayer layer];
borderLayer.fillColor = [UIColor whiteColor].CGColor;
CGFloat lineWidth = 5;
CGRect rect = CGRectInset(self.bounds, lineWidth / 2, lineWidth / 2);
borderLayer.path = [UIBezierPath bezierPathWithOvalInRect:rect].CGPath;
borderLayer.strokeColor = [[UIColor redColor] CGColor];
borderLayer.lineWidth = lineWidth;
[self.layer addSublayer:borderLayer];
}
return self;
}
A little while ago I posted a question about rounding just two corners of a view, and got a great response, but am having problems implementing it. Here is my drawRect: method:
- (void)drawRect:(CGRect)rect {
//[super drawRect:rect]; <------Should I uncomment this?
int radius = 5;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);
CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius, radius, M_PI, M_PI / 2, 1);
CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);
CGContextClosePath(context);
CGContextClip(context);
}
The method is being called, but doesn't seem to affect the outcome of the view. Any ideas why?
CACornerMask introduced in iOS 11, which help to define topleft, topright, bottomleft, bottom right in view layer. Below is example to use.
Here I try to rounded only two top corner:
myView.clipsToBounds = true
myView.layer.cornerRadius = 10
myView.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
FYI Ref:
as far as I know, if you also need to mask the subviews, you could use CALayer masking. There are 2 ways to do this. The first one is a bit more elegant, the second one is a workaround :-) but it's also fast. Both are based on CALayer masking. I've used both methods in a couple of projects last year then I hope you can find something useful.
Solution 1
First of all, I created this function to generate an image mask on the fly (UIImage) with the rounded corner I need. This function essentially needs 5 parameters: the bounds of the image and 4 corner radius (top-left, top-right, bottom-left and bottom-right).
static inline UIImage* MTDContextCreateRoundedMask( CGRect rect, CGFloat radius_tl, CGFloat radius_tr, CGFloat radius_bl, CGFloat radius_br ) {
CGContextRef context;
CGColorSpaceRef colorSpace;
colorSpace = CGColorSpaceCreateDeviceRGB();
// create a bitmap graphics context the size of the image
context = CGBitmapContextCreate( NULL, rect.size.width, rect.size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast );
// free the rgb colorspace
CGColorSpaceRelease(colorSpace);
if ( context == NULL ) {
return NULL;
}
// cerate mask
CGFloat minx = CGRectGetMinX( rect ), midx = CGRectGetMidX( rect ), maxx = CGRectGetMaxX( rect );
CGFloat miny = CGRectGetMinY( rect ), midy = CGRectGetMidY( rect ), maxy = CGRectGetMaxY( rect );
CGContextBeginPath( context );
CGContextSetGrayFillColor( context, 1.0, 0.0 );
CGContextAddRect( context, rect );
CGContextClosePath( context );
CGContextDrawPath( context, kCGPathFill );
CGContextSetGrayFillColor( context, 1.0, 1.0 );
CGContextBeginPath( context );
CGContextMoveToPoint( context, minx, midy );
CGContextAddArcToPoint( context, minx, miny, midx, miny, radius_bl );
CGContextAddArcToPoint( context, maxx, miny, maxx, midy, radius_br );
CGContextAddArcToPoint( context, maxx, maxy, midx, maxy, radius_tr );
CGContextAddArcToPoint( context, minx, maxy, minx, midy, radius_tl );
CGContextClosePath( context );
CGContextDrawPath( context, kCGPathFill );
// Create CGImageRef of the main view bitmap content, and then
// release that bitmap context
CGImageRef bitmapContext = CGBitmapContextCreateImage( context );
CGContextRelease( context );
// convert the finished resized image to a UIImage
UIImage *theImage = [UIImage imageWithCGImage:bitmapContext];
// image is retained by the property setting above, so we can
// release the original
CGImageRelease(bitmapContext);
// return the image
return theImage;
}
Now you just need few lines of code. I put stuff in my viewController viewDidLoad method because it's faster but you can use it also in your custom UIView with the layoutSubviews method in example.
- (void)viewDidLoad {
// Create the mask image you need calling the previous function
UIImage *mask = MTDContextCreateRoundedMask( self.view.bounds, 50.0, 50.0, 0.0, 0.0 );
// Create a new layer that will work as a mask
CALayer *layerMask = [CALayer layer];
layerMask.frame = self.view.bounds;
// Put the mask image as content of the layer
layerMask.contents = (id)mask.CGImage;
// set the mask layer as mask of the view layer
self.view.layer.mask = layerMask;
// Add a backaground color just to check if it works
self.view.backgroundColor = [UIColor redColor];
// Add a test view to verify the correct mask clipping
UIView *testView = [[UIView alloc] initWithFrame:CGRectMake( 0.0, 0.0, 50.0, 50.0 )];
testView.backgroundColor = [UIColor blueColor];
[self.view addSubview:testView];
[testView release];
[super viewDidLoad];
}
Solution 2
This solution is a bit more "dirty". Essentially you could create a mask layer with the rounded corner you need (all corners). Then you should increase the height of the mask layer by the value of the corner radius. In this way the bottom rounded corners are hidden and you can only see the upper rounded corner. I put the code just in the viewDidLoad method because it's faster but you can use it also in your custom UIView with the layoutSubviews method in example.
- (void)viewDidLoad {
// set the radius
CGFloat radius = 50.0;
// set the mask frame, and increase the height by the
// corner radius to hide bottom corners
CGRect maskFrame = self.view.bounds;
maskFrame.size.height += radius;
// create the mask layer
CALayer *maskLayer = [CALayer layer];
maskLayer.cornerRadius = radius;
maskLayer.backgroundColor = [UIColor blackColor].CGColor;
maskLayer.frame = maskFrame;
// set the mask
self.view.layer.mask = maskLayer;
// Add a backaground color just to check if it works
self.view.backgroundColor = [UIColor redColor];
// Add a test view to verify the correct mask clipping
UIView *testView = [[UIView alloc] initWithFrame:CGRectMake( 0.0, 0.0, 50.0, 50.0 )];
testView.backgroundColor = [UIColor blueColor];
[self.view addSubview:testView];
[testView release];
[super viewDidLoad];
}
Hope this helps. Ciao!
Combing through the few answers & comments, I found out that using UIBezierPath bezierPathWithRoundedRect and CAShapeLayer the simplest and most straight forward way. It might not be appropriate for very complex cases, but for occasional rounding of corners, it works fast and smoothly for me.
I had created a simplified helper that sets the appropriate corner in the mask:
-(void) setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners
{
UIBezierPath* rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer* shape = [[CAShapeLayer alloc] init];
[shape setPath:rounded.CGPath];
view.layer.mask = shape;
}
To use it, simply call with the appropriate UIRectCorner enum, e.g.:
[self setMaskTo:self.photoView byRoundingCorners:UIRectCornerTopLeft|UIRectCornerBottomLeft];
Please note that for me, I use it to round corners of photos in a grouped UITableViewCell, the 10.0 radius works fine for me, if need to just change the value as appropriate.
EDIT: just notice a previously answered very similarly as this one (link). You can still use this answer as a added convenience function if needed.
EDIT: Same code as UIView extension in Swift 3
extension UIView {
func maskByRoundingCorners(_ masks:UIRectCorner, withRadii radii:CGSize = CGSize(width: 10, height: 10)) {
let rounded = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: masks, cornerRadii: radii)
let shape = CAShapeLayer()
shape.path = rounded.cgPath
self.layer.mask = shape
}
}
To use it, simple call maskByRoundingCorner on any UIView:
view.maskByRoundingCorners([.topLeft, .bottomLeft])
I couldn't fit this all in a comment to #lomanf's answer. So I'm adding it as an answer.
Like #lomanf said, you need to add a layer mask to prevent sublayers from drawing outside of your path's bounds. It's a lot easier to do now, though. As long as you're targeting iOS 3.2 or higher, you don't need to create an image with quartz and set it as the mask. You can simply create a CAShapeLayer with a UIBezierPath and use that as the mask.
Also, when using layer masks, make sure that the layer you're masking is not part of any layer hierarchy when you add the mask. Otherwise the behavior is undefined. If your view is already in the hierarchy, you need to remove it from its superview, mask it, then put it back where it was.
CAShapeLayer *maskLayer = [CAShapeLayer layer];
UIBezierPath *roundedPath =
[UIBezierPath bezierPathWithRoundedRect:maskLayer.bounds
byRoundingCorners:UIRectCornerTopLeft |
UIRectCornerBottomRight
cornerRadii:CGSizeMake(16.f, 16.f)];
maskLayer.fillColor = [[UIColor whiteColor] CGColor];
maskLayer.backgroundColor = [[UIColor clearColor] CGColor];
maskLayer.path = [roundedPath CGPath];
//Don't add masks to layers already in the hierarchy!
UIView *superview = [self.view superview];
[self.view removeFromSuperview];
self.view.layer.mask = maskLayer;
[superview addSubview:self.view];
Due to the way Core Animation rendering works, masking is a relatively slow operation. Each mask requires an extra rendering pass. So use masks sparingly.
One of the best parts of this approach is that you no longer need to create a custom UIView and override drawRect:. This should make your code simpler, and maybe even faster.
I've taken Nathan's example and created a category on UIView to allow one to adhere to DRY principles. Without further ado:
UIView+Roundify.h
#import <UIKit/UIKit.h>
#interface UIView (Roundify)
-(void)addRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii;
-(CALayer*)maskForRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii;
#end
UIView+Roundify.m
#import "UIView+Roundify.h"
#implementation UIView (Roundify)
-(void)addRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii {
CALayer *tMaskLayer = [self maskForRoundedCorners:corners withRadii:radii];
self.layer.mask = tMaskLayer;
}
-(CALayer*)maskForRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii {
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.bounds;
UIBezierPath *roundedPath = [UIBezierPath bezierPathWithRoundedRect:
maskLayer.bounds byRoundingCorners:corners cornerRadii:radii];
maskLayer.fillColor = [[UIColor whiteColor] CGColor];
maskLayer.backgroundColor = [[UIColor clearColor] CGColor];
maskLayer.path = [roundedPath CGPath];
return maskLayer;
}
#end
To call:
[myView addRoundedCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
withRadii:CGSizeMake(20.0f, 20.0f)];
To expand a little on P.L's answer I rewrote the method like so as it wasn't rounding certain objects such as UIButton correctly
- (void)setMaskTo:(id)sender byRoundingCorners:(UIRectCorner)corners withCornerRadii:(CGSize)radii
{
// UIButton requires this
[sender layer].cornerRadius = 0.0;
UIBezierPath *shapePath = [UIBezierPath bezierPathWithRoundedRect:[sender bounds]
byRoundingCorners:corners
cornerRadii:radii];
CAShapeLayer *newCornerLayer = [CAShapeLayer layer];
newCornerLayer.frame = [sender bounds];
newCornerLayer.path = shapePath.CGPath;
[sender layer].mask = newCornerLayer;
}
And call it by
[self setMaskTo:self.continueButton byRoundingCorners:UIRectCornerBottomLeft|UIRectCornerBottomRight withCornerRadii:CGSizeMake(3.0, 3.0)];
If you want to do it in Swift you could use an extension of a UIView. By doing so, all subclasses will be able to use the following method:
import QuartzCore
extension UIView {
func roundCorner(corners: UIRectCorner, radius: CGFloat) {
let maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = maskPath.CGPath
layer.mask = maskLayer
}
}
Example usage:
self.anImageView.roundCorner(.topRight, radius: 10)
Extending the accepted answer, let us add backward compatibility to it. Prior to iOS 11, view.layer.maskedCorners is not available. So we can do like this
if #available(iOS 11.0, *) {
myView.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
} else {
myView.maskByRoundingCorners([.topLeft, .topRight])
}
extension UIView{
func maskByRoundingCorners(_ masks:UIRectCorner, withRadii radii:CGSize = CGSize(width: 10, height: 10)) {
let rounded = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: masks, cornerRadii: radii)
let shape = CAShapeLayer()
shape.path = rounded.cgPath
self.layer.mask = shape
}
}
We have written maskByRoundingCorners as an UIView extension so that it improves code reuse.
Credits to #SachinVsSachin and #P.L :) I have combined their codes to make it better.
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(5, 5, self.bounds.size.width-10, self.bounds.size.height-10)
byRoundingCorners:UIRectCornerAllCorners
cornerRadii:CGSizeMake(12.0, 12.0)];
change "AllCorners" according to your need.
All the solutions provided achieves the goal. But, UIConstraints can blow this up sometimes.
For example, the bottom corners needs to be rounded. If height or
bottom spacing constraint are set to the UIView that needs to be rounded, the
code snippets that rounds the corners needs to be moved to
viewDidLayoutSubviews method.
Highlighting:
UIBezierPath *maskPath = [UIBezierPath
bezierPathWithRoundedRect:roundedView.bounds byRoundingCorners:
(UIRectCornerTopRight | UIRectCornerBottomRight) cornerRadii:CGSizeMake(16.0, 16.0)];
The code snippet above will only round the top right corner if this code set in viewDidLoad. Because roundedView.bounds is going to change after the constraints updates the UIView.
Create a mask and set it on the view's layer
Starting with your code, you might go with something like the snippet below.
I'm not sure if this is the sort of result you're after. Worth noting, too, that if/when the system calls drawRect: again, asking for only part of the rect to be redrawn, this is going to behave very strangely. Nevan's approach, noted above, might be a better way to go.
// make sure the view's background is set to [UIColor clearColor]
- (void)drawRect:(CGRect)rect
{
CGFloat radius = 10.0;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, rect.size.width/2, rect.size.height/2);
CGContextRotateCTM(context, M_PI); // rotate so image appears right way up
CGContextTranslateCTM(context, -rect.size.width/2, -rect.size.height/2);
CGContextBeginPath(context);
CGContextMoveToPoint(context, rect.origin.x, rect.origin.y);
CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius, radius, M_PI, M_PI / 2, 1);
CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
CGContextClip(context);
// now do your drawing, e.g. draw an image
CGImageRef anImage = [[UIImage imageNamed:#"image.jpg"] CGImage];
CGContextDrawImage(context, rect, anImage);
}
A slightly hacky, but relatively simple (no subclassing, masking, etc) way to this is to have two UIViews. Both with clipToBounds = YES. Set rounded corners on the child view, then position it within the parent view so the corners you want straight are cropped.
UIView* parent = [[UIView alloc] initWithFrame:CGRectMake(10,10,100,100)];
parent.clipsToBounds = YES;
UIView* child = [[UIView alloc] new];
child.clipsToBounds = YES;
child.layer.cornerRadius = 3.0f;
child.backgroundColor = [UIColor redColor];
child.frame = CGRectOffset(parent.bounds, +4, -4);
[parent addSubView:child];
Doesn't support the case where you want two diagonally opposite corners rounded.
Bezier path is the anwer, if you need additional code this one worked for me: https://stackoverflow.com/a/13163693/936957
UIBezierPath *maskPath;
maskPath = [UIBezierPath bezierPathWithRoundedRect:_backgroundImageView.bounds
byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight)
cornerRadii:CGSizeMake(3.0, 3.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
_backgroundImageView.layer.mask = maskLayer;
[maskLayer release];
UIBezierPath solution.
- (void) drawRect:(CGRect)rect {
[super drawRect:rect];
//Create shape which we will draw.
CGRect rectangle = CGRectMake(2,
2,
rect.size.width - 4,
rect.size.height - 4);
//Create BezierPath with round corners
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rectangle
byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
cornerRadii:CGSizeMake(10.0, 10.0)];
//Set path width
[maskPath setLineWidth:2];
//Set color
[[UIColor redColor] setStroke];
//Draw BezierPath to see it
[maskPath stroke];
}
This can only work if some things are set correctly:
clipsToBounds must be set to YES
opaque has to be NO
backgroundColor should be "clearColor" (i am not fully sure on this)
contentMode has to be "UIContentModeRedraw" as drawRect is not called often if it's not
[super drawRect:rect] has to be called after the CGContextClip
Your view may not contain arbitrary subviews (not sure on this)
Be sure to set "needsDisplay:" at least once to trigger your drawrect
I realize that you're trying to round the top two corners of a UITableView, but for some reason I've found that the best solution is to use:
self.tableView.layer.cornerRadius = 10;
Programmatically it should round all four corners, but for some reason it only rounds the top two. **Please see the screenshot below to see the effect of the code I've written above.
I hope this helps!
You probably have to clip to bounds. Add the line
self.clipsToBounds = YES
somewhere in the code to set that property.