iPhone Core Animation - Drawing a Circle - iphone

I wish to create an animation. The best way I can explain this is if you can imagine drawing a circle. It starts at the top or 12 o clock and draws clockwise all the way around until it becomes a complete circle over the space of 10 or so seconds.
The closet I have come to this is to draw a point rotating around a center point using Core Animation (using the sample code here). But I am at a loss on how to draw the circle? Any suggestions are very welcome.
Many Thanks :)

What you really should do is to animate the stroke of a CAShapeLayer where the path is a circle. This will be accelerated using Core Animation and is less messy then to draw part of a circle in -drawRect:.
The code below will create a circle shape layer in the center of the screen and animate the stroke of it clockwise so that it looks as if it is being drawn. You can of course use any shape you'd like. (You can read this article on Ole Begemanns blog to learn more about how to animate the stroke of a shape layer.)
Note: that the stoke properties are not the same as the border properties on the layer. To change the width of the stroke you should use "lineWidth" instead of "borderWitdh" etc.
// Set up the shape of the circle
int radius = 100;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor blackColor].CGColor;
circle.lineWidth = 5;
// Add to parent layer
[self.view.layer addSublayer:circle];
// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 10.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Add the animation to the circle
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];

Here is another solution to the problem, based off #David's answer. This approach lets you set the direction of the circle's animation, and offers slightly more control. Edit: I've written a blog post on how to draw a circle with Swift, which I'll try to keep up to date with the betas. Check there if the code below doesn't work for you.
let radius = 100.0
// Create the circle layer
var circle = CAShapeLayer()
// Set the center of the circle to be the center of the view
let center = CGPointMake(CGRectGetMidX(self.frame) - radius, CGRectGetMidY(self.frame) - radius)
let fractionOfCircle = 3.0 / 4.0
let twoPi = 2.0 * Double(M_PI)
// The starting angle is given by the fraction of the circle that the point is at, divided by 2 * Pi and less
// We subtract M_PI_2 to rotate the circle 90 degrees to make it more intuitive (i.e. like a clock face with zero at the top, 1/4 at RHS, 1/2 at bottom, etc.)
let startAngle = Double(fractionOfCircle) / Double(twoPi) - Double(M_PI_2)
let endAngle = 0.0 - Double(M_PI_2)
let clockwise: Bool = true
// `clockwise` tells the circle whether to animate in a clockwise or anti clockwise direction
circle.path = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: clockwise).CGPath
// Configure the circle
circle.fillColor = UIColor.blackColor().CGColor
circle.strokeColor = UIColor.redColor().CGColor
circle.lineWidth = 5
// When it gets to the end of its animation, leave it at 0% stroke filled
circle.strokeEnd = 0.0
// Add the circle to the parent layer
self.layer.addSublayer(circle)
// Configure the animation
var drawAnimation = CABasicAnimation(keyPath: "strokeEnd")
drawAnimation.repeatCount = 1.0
// Animate from the full stroke being drawn to none of the stroke being drawn
drawAnimation.fromValue = NSNumber(double: fractionOfCircle)
drawAnimation.toValue = NSNumber(float: 0.0)
drawAnimation.duration = 30.0
drawAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// Add the animation to the circle
circle.addAnimation(drawAnimation, forKey: "drawCircleAnimation")

I got a solution to this problem, code below. I seem to have messed up in my explanation of the problem at hand so rewrote it as another question that got answered with the correct solution, see here
thanks everyone for their advice!
-(void)drawRect:(CGRect)rect
{
// Drawing code
CGRect allRect = self.bounds;
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw background
CGContextSetRGBStrokeColor(context, self.strokeValueRed, self.strokeValueGreen, self.strokeValueBlue, self.strokeValueAlpha); // white
CGContextSetLineWidth(context, 5);
// Draw progress
CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2);
CGFloat radius = (allRect.size.width - 4) / 2;
CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
CGContextStrokePath(context);
}

Ok I have a partial fix. I found the following code that draws segments of a circle. Its basically a UIView subclass that gets its drawRect fired every time its progress should be updated. The only question I have now is how would I change it so that instead of drawing sections or slices it would just draw the circle stroke?
-(void)drawRect:(CGRect)rect
{
// Drawing code
CGRect allRect = self.bounds;
CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw background
CGContextSetRGBStrokeColor(context, self.strokeValueRed, self.strokeValueGreen, self.strokeValueBlue, self.strokeValueAlpha); // white
CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 0.1f); // translucent white
CGContextSetLineWidth(context, self.lineWidth);
CGContextFillEllipseInRect(context, circleRect);
CGContextStrokeEllipseInRect(context, circleRect);
// Draw progress
CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2);
CGFloat radius = (allRect.size.width - 4) / 2;
CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 1.0f); // white
CGContextMoveToPoint(context, center.x, center.y);
CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
CGContextClosePath(context);
CGContextFillPath(context);
}

Related

QuartzCore - Adding Rounded Corners to a Polygon

I'm attempting to draw a translucent black rectangle with a triangle cut out of the center.
I have this working perfectly with the code below. However, I'd like to round the corners of the triangle.
I've tried a couple of different techniques for adding rounded corners such as setting CGContextSetLineCap for a stroke and by creating my path with quartz and using CGContextAddArcToPoint, but I've failed to get anything to work.
As I mentioned, the code below works for a triangle cutout. How can I round the corners of the triangle?
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint position = CGPointMake(rect.size.width/2, rect.size.height * .7);
CGSize size = CGSizeMake(rect.size.width*.8, rect.size.height*.5);
CGFloat firstPointX = (position.x - (size.width / 2));
CGFloat firstPointY = (position.y - (size.height));
CGPoint firstPoint = CGPointMake(firstPointX, firstPointY);
CGFloat secondPointX = position.x + (size.width / 2);
CGFloat secondPointY = position.y - (size.height);
CGPoint secondPoint = CGPointMake(secondPointX, secondPointY);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:position];
[path addLineToPoint:firstPoint];
[path moveToPoint:firstPoint];
[path addLineToPoint:secondPoint];
[path addLineToPoint:position];
[path closePath];
CGContextAddRect(context, rect);
CGContextAddPath(context, path.CGPath);
CGContextEOClip(context);
UIColor *translucentColor = [UIColor colorWithWhite:0 alpha:0.5];
CGContextSetFillColorWithColor(context, translucentColor.CGColor);
CGContextFillRect(context, rect);
UIGraphicsEndImageContext();
EDIT: Here's an example of what I'm trying to accomplish.
I think you want to use CGContextAddArc. It draws part of a circle with a line leading up to it if necessary. Here's the code to draw a rounded box that fills a UIView. For a triangle you'd have 3 lines instead of 4. Two on the top and one in the middle on the bottom. This is from a roundBoxView class I have:
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect boxRect = self.bounds;
float bRadius = self.cornerRadius;
float shrink = self.strokeThickness/2;
CGContextBeginPath(context);
// instead of this gray you could later add options for other colors
CGContextSetGrayFillColor(context, 0.0f, self.backgroundOpacity);
CGContextMoveToPoint(context, CGRectGetMinX(boxRect)+shrink + bRadius, CGRectGetMinY(boxRect)+shrink);
CGContextAddArc(context, CGRectGetMaxX(boxRect)-shrink - bRadius, CGRectGetMinY(boxRect)+shrink + bRadius, bRadius, 3 * (float)M_PI / 2, 0, 0);
CGContextAddArc(context, CGRectGetMaxX(boxRect)-shrink - bRadius, CGRectGetMaxY(boxRect)-shrink - bRadius, bRadius, 0, (float)M_PI / 2, 0);
CGContextAddArc(context, CGRectGetMinX(boxRect)+shrink + bRadius, CGRectGetMaxY(boxRect)-shrink - bRadius, bRadius, (float)M_PI / 2, (float)M_PI, 0);
CGContextAddArc(context, CGRectGetMinX(boxRect)+shrink + bRadius, CGRectGetMinY(boxRect)+shrink + bRadius, bRadius, (float)M_PI, 3 * (float)M_PI / 2, 0);
CGContextClosePath(context);
CGContextFillPath(context);
Of course instead of the boxRect being the full bounds you could pass that to your method and draw it using whatever bounds you want. To make it a triangle you would only have the 3 lines instead of two and you might have to do some math to figure out the start and end angles. On a box those angles are always 90 degrees (here given is icky radians) but on a triangle you'll either have to calc the angles on the fly or have a preset aspect ratio on the triangle so you can use preset start and stop angles. In the example as pictured with 3 equal angles you'd do 120 degrees or M_PI / 1.5 sized steps.

How can I create a part of a filled circle using Core Graphics/Quartz

I have made a subclass of UIView and I am trying to draw part of a circle in my drawRect method.
I have tried using bezierPathWithArcCenter and filling it but that only result in a pie shape (image 3) and that's not what i'm after. I want to draw what you see in image 1 and 2.
Maybe I can clip a full circle somehow? The area around the circle needs to be transparent.
TompaLompas answer pointed me in the right direction (with the arc drawing part). However the complete solution and answer is like this:
#define DEGREES_TO_RADIANS(degrees) ((M_PI * degrees)/ 180)
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef ctx = UIGraphicsGetCurrentContext();
int radius = self.frame.size.width / 2;
CGPoint center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
//Image 2
CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextAddArc(ctx, center.x, center.y, radius, DEGREES_TO_RADIANS(225), DEGREES_TO_RADIANS(315), NO);
CGContextDrawPath(ctx, kCGPathFill);
}
try overriding drawRect with this:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
float radius = 50.0f;
float x_left = rect.origin.x;
float x_left_center = x_left + radius;
float y_top = rect.origin.y;
float y_top_center = y_top + radius;
/* Begin path */
CGFloat white[4] = {0.0f, 204.0f/255.0f, 1.0f, 0.8f};
CGContextSetFillColor(context, white);
CGContextSetLineWidth(context, 1.0);
CGContextBeginPath(context);
CGContextMoveToPoint(context, x_left, y_top_center);
CGContextAddArcToPoint(context, x_left, y_top, x_left_center, y_top, radius);
CGContextAddLineToPoint(context,x_left, y_top + radius);
CGContextFillPath(context);
}
It will draw a rotated image number 2

CATransform3D rotate causes half of image to disappear

I'm using the following code to rotate an image, but half the image (down the y-axis) that has been rotated "out of" the page, disappears. How to fix? heading is in radians.
CALayer *layer = myUIImageView.layer;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / 500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, heading, 0.0f, 1.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;
The solution to this was to set the zPosition property of all my layers appropriately. Thanks is due to #Brad Larson, who suggested this solution in a comment here. It seems that, when you start using CATransform3D, the normal zindex view hierarchy established by addsubview is thrown out the window.
layer.anchorPoint = CGPointMake(0.0, 0.0);
Setting the anchorPoint to {0.0, 0.0} works as well (as Liam already pointed out).
Here's the full code snippet for changing the anchorPoint without changing the layer's position on screen:
layer.anchorPoint = CGPointMake( 0.0, 0.0 );
CGPoint position = layer.position;
CGSize size = layer.bounds.size;
CGFloat posX = position.x - size.width / 2.0;
CGFloat posY = position.y - size.height / 2.0;
layer.position = CGPointMake( posX, posY );

How to draw a rounded rectangle in Core Graphics / Quartz 2D?

I need to draw an outline for a rounded rectangle. I know I can make lines and arcs, but maybe there is also a function for rounded rects?
Instead of making your own path out of lines and arcs, you can use
[UIBezierPath bezierPathWithRoundedRect:cornerRadius:]
or
[UIBezierPath bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:]
(the second one lets you specify which corners are rounded)
Available in iOS 3.2 or later.
There is no prepackaged way to this, you must combine arcs in order to do this, apples quartzdemo project shows the code to do this, here is a reference Quartz Demo and here is the code they provide
// As a bonus, we'll combine arcs to create a round rectangle!
// Drawing with a white stroke color
CGContextRef context=UIGraphicsGetCurrentContext()
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
// If you were making this as a routine, you would probably accept a rectangle
// that defines its bounds, and a radius reflecting the "rounded-ness" of the rectangle.
CGRect rrect = CGRectMake(210.0, 90.0, 60.0, 60.0);
CGFloat radius = 10.0;
// NOTE: At this point you may want to verify that your radius is no more than half
// the width and height of your rectangle, as this technique degenerates for those cases.
// In order to draw a rounded rectangle, we will take advantage of the fact that
// CGContextAddArcToPoint will draw straight lines past the start and end of the arc
// in order to create the path from the current position and the destination position.
// In order to create the 4 arcs correctly, we need to know the min, mid and max positions
// on the x and y lengths of the given rectangle.
CGFloat minx = CGRectGetMinX(rrect), midx = CGRectGetMidX(rrect), maxx = CGRectGetMaxX(rrect);
CGFloat miny = CGRectGetMinY(rrect), midy = CGRectGetMidY(rrect), maxy = CGRectGetMaxY(rrect);
// Next, we will go around the rectangle in the order given by the figure below.
// minx midx maxx
// miny 2 3 4
// midy 1 9 5
// maxy 8 7 6
// Which gives us a coincident start and end point, which is incidental to this technique, but still doesn't
// form a closed path, so we still need to close the path to connect the ends correctly.
// Thus we start by moving to point 1, then adding arcs through each pair of points that follows.
// You could use a similar tecgnique to create any shape with rounded corners.
// Start at 1
CGContextMoveToPoint(context, minx, midy);
// Add an arc through 2 to 3
CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
// Add an arc through 4 to 5
CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
// Add an arc through 6 to 7
CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
// Add an arc through 8 to 9
CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
// Close the path
CGContextClosePath(context);
// Fill & stroke the path
CGContextDrawPath(context, kCGPathFillStroke);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:bubbleBounds cornerRadius:15.0];
CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
[bezierPath stroke];
Here is a function I wrote that rounds the input rect using a corner radius.
CGMutablePathRef createRoundedCornerPath(CGRect rect, CGFloat cornerRadius) {
// create a mutable path
CGMutablePathRef path = CGPathCreateMutable();
// get the 4 corners of the rect
CGPoint topLeft = CGPointMake(rect.origin.x, rect.origin.y);
CGPoint topRight = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y);
CGPoint bottomRight = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
CGPoint bottomLeft = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height);
// move to top left
CGPathMoveToPoint(path, NULL, topLeft.x + cornerRadius, topLeft.y);
// add top line
CGPathAddLineToPoint(path, NULL, topRight.x - cornerRadius, topRight.y);
// add top right curve
CGPathAddQuadCurveToPoint(path, NULL, topRight.x, topRight.y, topRight.x, topRight.y + cornerRadius);
// add right line
CGPathAddLineToPoint(path, NULL, bottomRight.x, bottomRight.y - cornerRadius);
// add bottom right curve
CGPathAddQuadCurveToPoint(path, NULL, bottomRight.x, bottomRight.y, bottomRight.x - cornerRadius, bottomRight.y);
// add bottom line
CGPathAddLineToPoint(path, NULL, bottomLeft.x + cornerRadius, bottomLeft.y);
// add bottom left curve
CGPathAddQuadCurveToPoint(path, NULL, bottomLeft.x, bottomLeft.y, bottomLeft.x, bottomLeft.y - cornerRadius);
// add left line
CGPathAddLineToPoint(path, NULL, topLeft.x, topLeft.y + cornerRadius);
// add top left curve
CGPathAddQuadCurveToPoint(path, NULL, topLeft.x, topLeft.y, topLeft.x + cornerRadius, topLeft.y);
// return the path
return path;
}
How to use the function, assuming you subclass UIView and override drawRect:
- (void)drawRect:(CGRect)rect {
// constants
const CGFloat outlineStrokeWidth = 20.0f;
const CGFloat outlineCornerRadius = 15.0f;
const CGColorRef whiteColor = [[UIColor whiteColor] CGColor];
const CGColorRef redColor = [[UIColor redColor] CGColor];
// get the context
CGContextRef context = UIGraphicsGetCurrentContext();
// set the background color to white
CGContextSetFillColorWithColor(context, whiteColor);
CGContextFillRect(context, rect);
// inset the rect because half of the stroke applied to this path will be on the outside
CGRect insetRect = CGRectInset(rect, outlineStrokeWidth/2.0f, outlineStrokeWidth/2.0f);
// get our rounded rect as a path
CGMutablePathRef path = createRoundedCornerPath(insetRect, outlineCornerRadius);
// add the path to the context
CGContextAddPath(context, path);
// set the stroke params
CGContextSetStrokeColorWithColor(context, redColor);
CGContextSetLineWidth(context, outlineStrokeWidth);
// draw the path
CGContextDrawPath(context, kCGPathStroke);
// release the path
CGPathRelease(path);
}
Example output:
If you want To have rounded corners on any UIView (or subclass) the easy way is to set the cornerRadius property on the view's layer. See Preview rounded image in iphone
CGPathCreateWithRoundedRect() will do what you want.
CGPathRef CGPathCreateWithRoundedRect(
CGRect rect,
CGFloat cornerWidth,
CGFloat cornerHeight,
const CGAffineTransform *transform
);
Available starting in iOS 7.0
Swift:
let rect: CGRect = ...
let path = UIBezierPath(roundedRect: rect, cornerRadius: 5.0)
context.addPath(path.cgPath)
context.setStrokeColor(UIColor.clear.cgColor)
context.drawPath(using: .fillStroke)
Maybe... three? years late, but these days I'm using this without issues.
#import CoreGraphics;
#interface YourViewController ()
#property (weak, nonatomic) IBOutlet UIButton *theButton;
#end
- (void)viewDidLoad
{
[super viewDidLoad];
self.theButton.layer.cornerRadius = 5.0f;
self.theButton.layer.masksToBounds = YES;
// Another useful ones
// Scaling the view (width, height)
self.theButton.transform = CGAfflineTransformMakeScale(1.50f, 1.50f);
// Setting an alpha value (transparency) - nice with Activity Indicator subviews
self.theButton.alpha = 0.8f;
}
Swift 4.2
let lineWidth: CGFloat = 5.0
let path = UIBezierPath(roundedRect: rect.insetBy(dx: lineWidth/2.0, dy: lineWidth/2.0), cornerRadius: 10。0)
path.lineWidth = lineWidth
UIColor.green.setStroke()
path.stroke()

Rounded UIView using CALayers - only some corners - How?

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
}
}