iOS Direction Glow - ios

I'm trying to create a "mini-map." It's a circle filled with markers, and I want the border of the circle to glow in places to indicate to the user to indicate that more markers are beyond the minimap in a given direction.
I can give the circle a 'glowing' blue border by drawing, below it, a blue circle with a slightly larger radius. I think that I can make this blue border brighter in some places than others by giving its CALayer a mask. (I've tried giving it a gradient mask, and it works.)
Assuming that I can make the proper calculations to determine how bright a given pixel should be (given the position of markers beyond the minimap's viewport), how do I set the individual pixels of a CALayer? Or is there an easier way to accomplish what I'm looking for besides making a complicated alpha value calculation for each pixel in the circle?
Thanks.

Here's my solution. I drew a series of 1-pixel arcs, each with a different stroke color.
void AddGlowArc(CGContextRef context, CGFloat x, CGFloat y, CGFloat radius, CGFloat peakAngle, CGFloat sideAngle, CGColorRef colorRef){
CGFloat increment = .05;
for (CGFloat angle = peakAngle - sideAngle; angle < peakAngle + sideAngle; angle+=increment){
CGFloat alpha = (sideAngle - fabs(angle - peakAngle)) / sideAngle;
CGColorRef newColor = CGColorCreateCopyWithAlpha(colorRef, alpha);
CGContextSetStrokeColorWithColor(context, newColor);
CGContextAddArc(context, x, y, radius, angle, angle + increment, 0);
CGContextStrokePath(context);
}
}
And then, in DrawRect,
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
AddGlowArc(context, 160, 160, 160, angle, .2, [UIColor colorWithRed:0 green:.76 blue:.87 alpha:1].CGColor);

This was my [much longer] solution which would allow each glow-point layer to be added and animated individually if needed. The small circle is added on the circumference of a larger one and that larger circle is rotated. Enjoyed getting my head around this even though you answered the question
- (void)viewDidLoad
{
[super viewDidLoad];
CGPoint circleCenter = CGPointMake(300.f, 300.f);
CALayer *outerCircle = [self outerCircleToRotateWithCenter:circleCenter andRadius:100.f];
[self.view.layer addSublayer:outerCircle];
CGFloat rotationAngleInDeg = 270.f;
CGFloat rotationAngle = (M_PI * -rotationAngleInDeg)/180.f;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, rotationAngle, 0.f, 0.f, -1.f);
[outerCircle setTransform:transform];
}
Using method:
- (CALayer *)outerCircleToRotateWithCenter:(CGPoint)circleCenter andRadius:(CGFloat )radius {
// outer circle
CAShapeLayer *container = [CAShapeLayer layer];
UIBezierPath *containerCircle = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(0.f, 0.f, radius, radius)];
[container setPath:containerCircle.CGPath];
[container setBounds:CGPathGetBoundingBox(containerCircle.CGPath)];
[container setPosition:circleCenter];
[container setAnchorPoint:CGPointMake(0.5f, 0.5f)];
[container setStrokeColor:[UIColor blackColor].CGColor]; // REMOVE
[container setFillColor:nil];
// smaller circle
CAShapeLayer *circle = [[CAShapeLayer alloc] init];
[container addSublayer:circle];
UIBezierPath *circlePath = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(0.f, 0.f, 25.f, 25.f)];
[circle setPath:circlePath.CGPath];
[circle setBounds:CGPathGetBoundingBox(circlePath.CGPath)];
[circle setFillColor:[UIColor orangeColor].CGColor];
[circle setAnchorPoint:CGPointMake(0.5f, 0.5f)];
[circle setPosition:CGPointMake(radius/2.f, 0.f)];
[circle setOpacity:0.4f];
// shadow
[circle setShadowOpacity:0.8f];
[circle setShadowRadius:4.f];
[circle setShadowOffset:CGSizeMake(0.f, 0.f)];
[circle setShadowColor:[UIColor orangeColor].CGColor];
[circle setShadowPath:circlePath.CGPath];
return container;
}
I'm guessing the smaller circle could be added and rotated in a single transform rather then being a sublayer.

Related

How to set gradient color with angle

I want to set gradient on my button, i have to set simple linear gradient(without angle) with two color, but i don't know how to set angle value on gradient
Angle:- 61
below image define psd gradient overlay effect
Thanks in advance
Try it, maybe it will help
- (CAGradientLayer *)gradientLayerWithColors:(NSArray *)colors angle:(CGFloat)angle {
CAGradientLayer *layer = [CAGradientLayer layer];
layer.colors = colors;
CGFloat x = angle / 360.f;
CGFloat a = pow(sin((2*M_PI*((x+0.75)/2))),2);
CGFloat b = pow(sin((2*M_PI*((x+0.0)/2))),2);
CGFloat c = pow(sin((2*M_PI*((x+0.25)/2))),2);
CGFloat d = pow(sin((2*M_PI*((x+0.5)/2))),2);
layer.startPoint = CGPointMake(a, b);
layer.endPoint = CGPointMake(c, d);
return layer;
}
CAGradientLayer *gradientMask = [CAGradientLayer layer];
gradientMask.frame = CGRectMake(0, 0, myView.bounds.size.width, myView.bounds.size.height);
gradientMask.colors = #[(id)[UIColor redColor].CGColor,
(id)[UIColor greenColor].CGColor];
gradientMask.locations = #[#0.0, #0.10, #0.60, #1.0];
[myView.layer insertSublayer:gradientMask atIndex:0];
adjust location value and frame size accordingly.
This is a slightly modified code generated by PaintCode that solves just this problem:
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: self.view.bounds];
UIBezierPath* rectangleRotatedPath = [rectanglePath copy];
CGAffineTransform transform = CGAffineTransformMakeRotation(yourAngleInRadians);
[rectangleRotatedPath applyTransform: transform];
CGRect rectangleBounds = CGPathGetPathBoundingBox(rectangleRotatedPath.CGPath);
transform = CGAffineTransformInvert(transform);
CGPoint startPoint = CGPointApplyAffineTransform(CGPointMake(CGRectGetMinX(rectangleBounds), CGRectGetMidY(rectangleBounds)), transform);
CGPoint endPoint = CGPointApplyAffineTransform(CGPointMake(CGRectGetMaxX(rectangleBounds), CGRectGetMidY(rectangleBounds)), transform);
The code takes your initial rectangle (black rectangle) and angle (shown as black line), rotates the rectangle (you'll get blue rectangle), then finds bounding box of the rotated rectangle (red rectangle). Takes endpoints on the bounds (red dots) and transforms (rotates) them back using invert transform (blue dots).
Other solution might be finding intersection of your rectangle and a line defined by the angle.

Draw Triangles inside Circle Objective-C [duplicate]

For an iPhone application I want to draw a circle, that is only for an x percentage filled.
Something like this:
I have no problems calculating the radius, the degrees or the radians, that is no problem. Also drawing the circle is already done. But how do I get the iPhone SDK to draw the part that is filled.
I can draw a rectangle that size, but not part of a circle.
I just want to draw that on a a normal context.
Hope someone can give me any pointers here.
A lot of people have showed you how this can be done in Core Graphics but it can also be done with Core Animation which gives the big addition of easily being able to animate the percentage of the pie shape.
The following code will create both the ring and the partly filled layers (even though you said that you already can draw the ring) since its nice to have both the ring and the pie shape to be drawn using the same method.
If you animate the strokeStart or strokeEnd properties of the pieShape layer you will have the percentage animate. As with all Core Animation code you will need to add QuartzCore.framework to your project and include <QuartzCore/QuartzCore.h> in your code.
// Create a white ring that fills the entire frame and is 2 points wide.
// Its frame is inset 1 point to fit for the 2 point stroke width
CGFloat radius = MIN(self.frame.size.width,self.frame.size.height)/2;
CGFloat inset = 1;
CAShapeLayer *ring = [CAShapeLayer layer];
ring.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
ring.fillColor = [UIColor clearColor].CGColor;
ring.strokeColor = [UIColor whiteColor].CGColor;
ring.lineWidth = 2;
// Create a white pie-chart-like shape inside the white ring (above).
// The outside of the shape should be inside the ring, therefore the
// frame needs to be inset radius/2 (for its outside to be on
// the outside of the ring) + 2 (to be 2 points in).
CAShapeLayer *pieShape = [CAShapeLayer layer];
inset = radius/2 + 2; // The inset is updated here
pieShape.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
pieShape.fillColor = [UIColor clearColor].CGColor;
pieShape.strokeColor = [UIColor whiteColor].CGColor;
pieShape.lineWidth = (radius-inset)*2;
// Add sublayers
// NOTE: the following code is used in a UIView subclass (thus self is a view)
// If you instead chose to use this code in a view controller you should instead
// use self.view.layer to access the view of your view controller.
[self.layer addSublayer:ring];
[self.layer addSublayer:pieShape];
Use CGContext's arc functions:
CGContextAddArc(context,
centerX,
centerY,
radius,
startAngleRadians,
endAngleRadians,
clockwise ? 1 : 0);
See the documentation for CGContextAddArc().
Try this:
CGContextMoveToPoint(the center point)
CGContextAddLineToPoint(the starting point of the fill path on the circumference)
CGContextAddArcToPoint(the ending point of the fill path on the circumference)
CGContextAddLineToPoint(the center point)
CGContextFillPath
I implemented a pie progress view that looks similar to what you are doing. It's open source. Hopefully the source code will help.
SSPieProgressView.h source
SSPieProgressView.m source
CircleViewController.h
#import <UIKit/UIKit.h>
#interface CircleViewController : UIViewController
#end
CircleViewController.m
#import "CircleViewController.h"
#import "GraphView.h"
#interface CircleViewController ()
#end
#implementation CircleViewController
- (void)viewDidLoad {
[super viewDidLoad];
GraphView *graphView = [[GraphView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
graphView.backgroundColor = [UIColor whiteColor];
graphView.layer.borderColor = [UIColor redColor].CGColor;
graphView.layer.borderWidth = 1.0f;
[self.view addSubview:graphView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
GraphView.h
#import <UIKit/UIKit.h>
#interface GraphView : UIView
#end
GraphView.m
#import "GraphView.h"
#implementation GraphView
- (void)drawRect:(CGRect)rect {
CGPoint circleCenter = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
[self drawCircleWithCircleCenter:(CGPoint) circleCenter radius:80 firstColor:[UIColor blueColor].CGColor secondeColor:[UIColor redColor].CGColor lineWidth:2 startDegree:0 currentDegree:90];
//[self drawCircleWithCircleCenter2:(CGPoint) circleCenter radius:80 firstColor:[UIColor blueColor].CGColor secondeColor:[UIColor redColor].CGColor lineWidth:2 startDegree:0 currentDegree:90];
}
- (void)drawCircleWithCircleCenter:(CGPoint) circleCenter
radius:(CGFloat)radius
firstColor:(CGColorRef)firstColor
secondeColor:(CGColorRef)secondeColor
lineWidth:(CGFloat)lineWidth
startDegree:(float)startDegree
currentDegree:(float)endDegree {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, lineWidth);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegree], [self radians:endDegree], 0);
CGContextSetFillColorWithColor(context, firstColor);
CGContextFillPath(context);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x, circleCenter.y, radius, [self radians:endDegree], [self radians:startDegree], 0);
CGContextSetFillColorWithColor(context, secondeColor);
CGContextFillPath(context);
}
- (void)drawCircleWithCircleCenter2:(CGPoint) circleCenter
radius:(CGFloat)radius
firstColor:(CGColorRef)firstColor
secondeColor:(CGColorRef)secondeColor
lineWidth:(CGFloat)lineWidth
startDegree:(float)startDegree
currentDegree:(float)endDegree {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, lineWidth);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegree], [self radians:endDegree], 0);
CGContextSetFillColorWithColor(context, firstColor);
CGContextFillPath(context);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x, circleCenter.y, radius, [self radians:endDegree], [self radians:startDegree], 0);
CGContextSetStrokeColorWithColor(context, secondeColor);
CGContextStrokePath(context);
}
-(float) radians:(double) degrees {
return degrees * M_PI / 180;
}
#end
note: you can use one of the 2 methods:
"drawCircleWithCircleCenter" or "drawCircleWithCircleCenter2"
this code if you want to split cell on 2 parts only
if you want to split cell on more than 2 parts you can check this : "Drawing a circle ,filled different parts with different color" and check the answer start with this Phrase "we have 6 class"
Well, since nobody used NSBezierPath so far, I figured I could provide the solution I recently used for the same problem:
-(void)drawRect:(NSRect)dirtyRect
{
double start = -10.0; //degrees
double end = 190.0; //degrees
NSPoint center = NSMakePoint(350, 200);
double radius = 50;
NSBezierPath *sector = [NSBezierPath bezierPath];
[sector moveToPoint:center];
[sector appendBezierPathWithArcWithCenter:center radius:radius startAngle:start endAngle:end];
[sector lineToPoint:center];
[sector fill];
}
Below is a full method I am using that does this with Core Graphics, adapting and expanding on mharper's comment above.
This code is for OSX Cocoa, but could easily be changed to iOS, by modifying how you get the context.
- (void)drawPieShapedCircleWithRadius:(CGFloat)radius
strokeColor:(CGColorRef)strokeColor
fillColor:(CGColorRef)fillColor
lineWidth:(CGFloat)lineWidth
currentDegrees:(float)currentDegrees
startDegrees:(float)startDegrees {
// get the context
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
// Set the color of the circle stroke and fill
CGContextSetStrokeColorWithColor(context, strokeColor);
CGContextSetFillColorWithColor(context, fillColor);
// Set the line width of the circle
CGContextSetLineWidth(context, 1);
// Calculate the middle of the circle
CGPoint circleCenter = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
// Move the bezier to the center of the circle
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y); // move to the center point
// Draw the arc from the start point (hardcoded as the bottom of the circle) to the center
CGContextAddLineToPoint(context, circleCenter.x, circleCenter.y + radius);
// Draw the arc around the circle from the start degrees point to the current degrees point
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegrees], [self radians:startDegrees + currentDegrees], 0);
// Draw the line back into the center of the circle
CGContextAddLineToPoint(context, circleCenter.x, circleCenter.y);
// Fill the circle
CGContextFillPath(context);
// Draw the line around the circle
CGContextStrokePath(context);
}
Try this code in a UIView, Example "MyChartClass"...
- (void)drawRect:(CGRect)rect {
int c=(int)[itemArray count];
CGFloat angleArray[c];
CGFloat offset;
int sum=0;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(context, false);
CGContextSetShouldAntialias(context, false);
for(int i=0;i<[itemArray count];i++) {
sum+=[[itemArray objectAtIndex:i] intValue];
}
for(int i=0;i<[itemArray count];i++) {
angleArray[i]=(float)(([[itemArray objectAtIndex:i] intValue])/(float)sum)*(2*3.14);
CGContextMoveToPoint(context, radius, radius);
if(i==0)
CGContextAddArc(context, radius, radius, radius, 0,angleArray[i], 0);
else
CGContextAddArc(context, radius, radius, radius,offset,offset+angleArray[i], 0);
offset+=angleArray[i];
CGContextSetFillColorWithColor(context, ((UIColor *)[myColorArray objectAtIndex:i]).CGColor);
CGContextClosePath(context);
CGContextFillPath(context);
}
}
Implementation in your UIViewController
MyChartClass *myChartClass=[[MyChartClass alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
myChartClass.backgroundColor = [UIColor clearColor];
myChartClass.itemArray=[[NSArray alloc]initWithObjects:#"75",#"25", nil];
myChartClass.myColorArray=[[NSArray alloc]initWithObjects:[UIColor blackColor],[UIColor whiteColor], nil];
myChartClass.radius=100;
[self.view addSubview:myChartClass];
Regards.

Invert a radial gradient mask in core graphics

I am able to mask a picture with black/white radial gradient in iOS using core graphics. But I am getting result just the opposite I want. I want the picture to be transparent from the region where gradient is applied and shall display the remaining part of the picture. That means a "![hole][1]" in the picture. But here the hole will be created by a gradient mask.
Can anyone please suggest me some way or any hint to invert the radial gradient mask, just like we do in photoshop.
Instead of using mask, I think the best way is to insert the layer. That's what I did for my IOS app.
- (void) addSublayer {
CGPoint center = CGPointMake(self.addButton.frame.origin.x, self.addButton.frame.origin.y);
UIBezierPath* clip = [UIBezierPath bezierPathWithArcCenter:center
radius:CGRectGetMaxX(self.addUnavailabilitySubView.frame) - self.addButton.center.x
startAngle:-1.57
endAngle:1.57
clockwise:YES];
[clip addLineToPoint:center];
[clip closePath];
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
[shapeLayer setMasksToBounds:YES];
shapeLayer.frame = self.view.layer.bounds;
shapeLayer.path = clip.CGPath;
//This gradient layer can be a customised one
CALayer *gradientLayer = [CALayer layer];
gradientLayer.frame = CGRectMake(0, 0, self.view.bounds.size.height, self.view.bounds.size.width);
[self.view.layer insertSublayer:gradientLayer atIndex:0];
}
I can also share my radial gradient layer here :)
-(void)drawInContext:(CGContextRef)ctx {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 }; // End color
CGFloat components[8] = {0.f, 0.f, 0.f, 0.3f, 0.f, 0.f, 0.f, 0.8f};
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, num_locations);
CGFloat myStartRadius, myEndRadius;
CGPoint radialCenter = CGPointMake(100, 100)
myStartRadius = 20;
myEndRadius = 300;
CGContextDrawRadialGradient (ctx, gradient, radialCenter,
myStartRadius, radialCenter, myEndRadius,
kCGGradientDrawsAfterEndLocation);
}

UIBezierPath and applytransform

I am trying to implement a custom UIView which is basically a pie menu (something like a cake divided into slices).
For that I am trying to draw a circle and a series of lines from the center, like the rays in a chart's wheel.
I have successfully drawn the circle and and I would now like to draw the lines dividing the circle in slices.
This is what I have so far:
-(void)drawRect:(CGRect)rect{
[[UIColor blackColor] setStroke];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat minDim = (rect.size.width < rect.size.height) ? rect.size.width : rect.size.height;
CGRect circleRect = CGRectMake(0, rect.size.height/2-minDim/2, minDim, minDim);
CGContextAddEllipseInRect(ctx, circleRect);
CGContextSetFillColor(ctx, CGColorGetComponents([[UIColor yellowColor] CGColor]));
CGContextFillPath(ctx);
CGPoint start = CGPointMake(0, rect.size.height/2);
CGPoint end = CGPointMake(rect.size.width, rect.size.height/2);
for (int i = 0; i < MaxSlices(6); i++){
CGFloat degrees = 1.0*i*(180/MaxSlices(6));
CGAffineTransform rot = CGAffineTransformMakeRotation(degreesToRadians(degrees));
UIBezierPath *path = [self pathFrom:start to:end];
[path applyTransform:rot];
}
}
- (UIBezierPath *) pathFrom:(CGPoint) start to:(CGPoint) end{
UIBezierPath* aPath = [UIBezierPath bezierPath];
aPath.lineWidth = 5;
[aPath moveToPoint:start];
[aPath addLineToPoint:end];
[aPath closePath];
[aPath stroke];
return aPath;
}
The problem is that applyTransform on the path doesn't seem to do anything. The first path gest drawn correctly the and the following ones are unaffected by the rotation. Basically what I see is just one path. Check the screen shot here http://img837.imageshack.us/img837/9757/iossimulatorscreenshotf.png
Thank you for your help!
You're drawing the path (with stroke) before you transform it. A path is just a mathematical representation. It isn't the line "on the screen." You can't move what you've already drawn by modifying the data about it.
Just move the [aPath stroke] out of pathFrom:to: and put it after the applyTransform:.

Round two corners in UIView

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.

Resources