I have implemented a highlight function in my app. This highlight is being drawn in UIImage so that it can be saved as a PNG Representation. Everything is working perfectly but recently I realized somewhat a very confusing issue. Sometimes when I am highlighting, the drawings are being distorted. Here is what it looks like:
Whenever I move my finger to highlight the characters, the drawn highlights are stretching to the left. Another one:
In this one, every time I move my finger to highlight, the drawn highlights are moving upward!
This is all very confusing for me. This happens from time to time, and sometimes to certain pages only. Sometimes it works well just like this:
I am very confused on why is this happening. Can anyone tell me or at least give me an idea on why is this happening? Please help me.
THE CODE:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
currPoint = [[touches anyObject]locationInView:self];
for (int r = 0; r < [rectangles count]; r++)
{
CGRect rect = [[rectangles objectAtIndex:r]CGRectValue];
//Get the edges of the rectangles
CGFloat xEdge = rect.origin.x + rect.size.width;
CGFloat yEdge = rect.origin.y + rect.size.height;
if ((currPoint.x > rect.origin.x && currPoint.x < xEdge) && (currPoint.y < rect.origin.y && currPoint.y > yEdge))
{
imgView.image = [self drawRectsToImage:imgView.image withRectOriginX:rect.origin.x originY:rect.origin.y rectWidth:rect.size.width rectHeight:rect.size.height];
break;
}
}
}
//The function that draws the highlight
- (UIImage *)drawRectsToImage:(UIImage *)image withRectOriginX:(CGFloat)x originY:(CGFloat)y rectWidth:(CGFloat)width rectHeight:(CGFloat)ht
{
UIGraphicsBeginImageContext(self.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[image drawInRect:self.bounds];
CGContextSetStrokeColorWithColor(context, [UIColor clearColor].CGColor);
CGRect rect = CGRectMake(x, y, width, ht);
CGContextAddRect(context, rect);
CGContextSetCMYKFillColor(context, 0, 0, 1, 0, 0.5);
CGContextFillRect(context, rect);
UIImage *ret = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return ret;
}
I can't tell you exactly why these artifacts occur, but...
I don't think it's a good idea to render an image in a touch handler. Touch handling should do as little as possible.
You might want to try using CoreAnimation's CALayer:
Set your image as background image like this (assuming your class is a subclass of UIView) or use an actual UIImageView:
self.layer.contents = (id)image.CGImage;
When you detect that another rectangle rect has been touched, add the highlight as a sublayer above your image background:
CALayer *highlightLayer = [CALayer layer];
highlightLayer.frame = rect;
highlightLayer.backgroundColor = [UIColor yellowColor].CGColor;
highlightLayer.opacity = 0.25f;
[self.layer addSublayer:highlightLayer];
The shouldRasterize layer property may help to improve performance if necessary:
self.layer.shouldRasterize = YES;
In order do use all this, use the QuartzCore framework and import <QuartzCore/QuartzCore.h> in your implementation file.
You can create a PNG representation of your layer hierarchy by rendering the self.layer to an image context, then get the image and your PNG representation.
Related
I am attempting to draw a stroked circle by using a CAShapeLayer and setting a circular path on it. However, this method is consistently less accurate when rendered to the screen than using borderRadius or drawing the path in a CGContextRef directly.
Here are the results of all three methods:
Notice that the third is poorly rendered, especially inside the stroke on the top and bottom.
I have set the contentsScale property to [UIScreen mainScreen].scale.
Here is my drawing code for these three circles. What’s missing to make the CAShapeLayer draw smoothly?
#interface BCViewController ()
#end
#interface BCDrawingView : UIView
#end
#implementation BCDrawingView
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
self.backgroundColor = nil;
self.opaque = YES;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
[[UIColor whiteColor] setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), NULL);
[[UIColor redColor] setStroke];
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 1);
[[UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds, 4, 4)] stroke];
}
#end
#interface BCShapeView : UIView
#end
#implementation BCShapeView
+ (Class)layerClass
{
return [CAShapeLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
self.backgroundColor = nil;
CAShapeLayer *layer = (id)self.layer;
layer.lineWidth = 1;
layer.fillColor = NULL;
layer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds, 4, 4)].CGPath;
layer.strokeColor = [UIColor redColor].CGColor;
layer.contentsScale = [UIScreen mainScreen].scale;
layer.shouldRasterize = NO;
}
return self;
}
#end
#implementation BCViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *borderView = [[UIView alloc] initWithFrame:CGRectMake(24, 104, 36, 36)];
borderView.layer.borderColor = [UIColor redColor].CGColor;
borderView.layer.borderWidth = 1;
borderView.layer.cornerRadius = 18;
[self.view addSubview:borderView];
BCDrawingView *drawingView = [[BCDrawingView alloc] initWithFrame:CGRectMake(20, 40, 44, 44)];
[self.view addSubview:drawingView];
BCShapeView *shapeView = [[BCShapeView alloc] initWithFrame:CGRectMake(20, 160, 44, 44)];
[self.view addSubview:shapeView];
UILabel *borderLabel = [UILabel new];
borderLabel.text = #"CALayer borderRadius";
[borderLabel sizeToFit];
borderLabel.center = CGPointMake(borderView.center.x + 26 + borderLabel.bounds.size.width/2.0, borderView.center.y);
[self.view addSubview:borderLabel];
UILabel *drawingLabel = [UILabel new];
drawingLabel.text = #"drawRect: UIBezierPath";
[drawingLabel sizeToFit];
drawingLabel.center = CGPointMake(drawingView.center.x + 26 + drawingLabel.bounds.size.width/2.0, drawingView.center.y);
[self.view addSubview:drawingLabel];
UILabel *shapeLabel = [UILabel new];
shapeLabel.text = #"CAShapeLayer UIBezierPath";
[shapeLabel sizeToFit];
shapeLabel.center = CGPointMake(shapeView.center.x + 26 + shapeLabel.bounds.size.width/2.0, shapeView.center.y);
[self.view addSubview:shapeLabel];
}
#end
EDIT: For those who cannot see the difference, I've drawn circles on top of each other and zoomed in:
Here I've drawn a red circle with drawRect:, and then drawn an identical circle with drawRect: again in green on top of it. Note the limited bleed of red. Both of these circles are "smooth" (and identical to the cornerRadius implementation):
In this second example, you'll see the issue. I've drawn once using a CAShapeLayer in red, and again on top with a drawRect: implementation of the same path, but in green. Note that you can see a lot more inconsistency with more bleed from the red circle underneath. It's clearly being drawn in a different (and worse) fashion.
Who knew there are so many ways to draw a circle?
TL;DR: If you want to use CAShapeLayer and still get smooth circles, you'll need to use shouldRasterize and rasterizationScale carefully.
Original
Here's your original CAShapeLayer and a diff from the drawRect version. I made a screenshot off my iPad Mini with Retina Display, then massaged it in Photoshop, and blew it up to 200%. As you can clearly see, the CAShapeLayer version has visible differences, especially on the left and right edges (darkest pixels in the diff).
Rasterize at screen scale
Let's rasterize at screen scale, which should be 2.0 on retina devices. Add this code:
layer.rasterizationScale = [UIScreen mainScreen].scale;
layer.shouldRasterize = YES;
Note that rasterizationScale defaults to 1.0 even on retina devices, which accounts for the fuzziness of default shouldRasterize.
The circle is now a little smoother, but the bad bits (darkest pixels in the diff) have moved to the top and bottom edges. Not appreciably better than no rasterizing!
Rasterize at 2x screen scale
layer.rasterizationScale = 2.0 * [UIScreen mainScreen].scale;
layer.shouldRasterize = YES;
This rasterizes the path at 2x screen scale, or up to 4.0 on retina devices.
The circle is now visibly smoother, the diffs are much lighter and spread out evenly.
I also ran this in Instruments: Core Animation and didn't see any major differences in the Core Animation Debug Options. However it may be slower since it's downscaling not just blitting an offscreen bitmap to the screen. You may also need to temporarily set shouldRasterize = NO while animating.
What doesn't work
Set shouldRasterize = YES by itself. On retina devices, this looks fuzzy because rasterizationScale != screenScale.
Set contentScale = screenScale. Since CAShapeLayer doesn't draw into contents, whether or not it is rasterizing, this doesn't affect the rendition.
Credit to Jay Hollywood of Humaan, a sharp graphic designer who first pointed it out to me.
Ah, i ran into the same problem some time ago (it was still iOS 5 then iirc), and I wrote the following comment in the code:
/*
ShapeLayer
----------
Fixed equivalent of CAShapeLayer.
CAShapeLayer is meant for animatable bezierpath
and also doesn't cache properly for retina display.
ShapeLayer converts its path into a pixelimage,
honoring any displayscaling required for retina.
*/
A filled circle underneath a circleshape would bleed its fillcolor. Depending on the colors this would be very noticeable. And during userinteraction the shape would render even worse, which let me to conclude that the shapelayer would always render with a scalefactor of 1.0, regardless of the layer scalefactor, because it is meant for animation purposes.
i.e. you only use a CAShapeLayer if you have a specific need for animatable changes to the shape of the bezierpath, not to any of the other properties that are animatable through the usual layer properties.
I eventually decided to write a simple ShapeLayer that would cache its own result, but you might try implementing the displayLayer: or the drawLayer:inContext:
Something like:
- (void)displayLayer:(CALayer *)layer
{
UIImage *image = nil;
CGContextRef context = UIImageContextBegin(layer.bounds.size, NO, 0.0);
if (context != nil)
{
[layer renderInContext:context];
image = UIImageContextEnd();
}
layer.contents = image;
}
I haven't tried that, but would be interesting to know the result...
I know this is an older question, but for those who are trying to work in the drawRect method and still having trouble, one small tweak that helped me immensely was using the correct method to fetch the UIGraphicsContext. Using the default:
let newSize = CGSize(width: 50, height: 50)
UIGraphicsBeginImageContext(newSize)
let context = UIGraphicsGetCurrentContext()
would result in blurry circles no matter which suggestion I followed from the other answers. What finally did it for me was realizing that the default method for getting an ImageContext sets the scaling to non-retina. To get an ImageContext for a retina display you need to use this method:
let newSize = CGSize(width: 50, height: 50)
UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
let context = UIGraphicsGetCurrentContext()
from there using the normal drawing methods worked fine. Setting the last option to 0 will tell the system to use the scaling factor of the device’s main screen. The middle option false is used to tell the graphics context whether or not you'll be drawing an opaque image (true means the image will be opaque) or one that needs an alpha channel included for transparencies. Here are the appropriate Apple Docs for more context: https://developer.apple.com/reference/uikit/1623912-uigraphicsbeginimagecontextwitho?language=objc
I guess CAShapeLayer is backed by a more performant way of rendering its shapes and takes some shortcuts. Anyway CAShapeLayer can be a little bit slow on the main thread. Unless you need to animate between different paths I would suggest render asynchronously to a UIImage on a background thread.
Use this method to draw UIBezierPath
/*********draw circle where double tapped******/
- (UIBezierPath *)makeCircleAtLocation:(CGPoint)location radius:(CGFloat)radius
{
self.circleCenter = location;
self.circleRadius = radius;
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:self.circleCenter
radius:self.circleRadius
startAngle:0.0
endAngle:M_PI * 2.0
clockwise:YES];
return path;
}
And draw like this
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [[self makeCircleAtLocation:location radius:50.0] CGPath];
shapeLayer.strokeColor = [[UIColor whiteColor] CGColor];
shapeLayer.fillColor = nil;
shapeLayer.lineWidth = 3.0;
// Add CAShapeLayer to our view
[gesture.view.layer addSublayer:shapeLayer];
I have simple UIView hierarchy:
container View:
→ Map View
→ Custom View
My custom view partially overlaps the Map View. I can see a map, but I can't interact with the map e.g. zoom, scroll etc.
How can I archive partially map overlap and interaction in the same time?
Feel free to ask me if you didn't understand something.
EDIT
I want to disable black areas to interaction, but allow interaction in the circle i.e. in the center of my UIView with black overlay areas.
I'm assuming you have something like this:
You want to be able to touch the red area, and touch the map where the yellow area is, but it is being blocked by the yellow subview?
If so, subclass the yellow subview and override the -pointInside: method, which allows you to specify whether a touched point will collide with the view, or fall back to a view behind it.
- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
return [[UIBezierPath bezierPathWithOvalInRect:self.bounds] containsPoint:point];
}
if the view only partly covers the view below it, apply a mask to tell IOS that
e.g. from my github fork of XBPageCurl
- (void)applyCornerMaskAsNeeded {
//
//create mask
//
UIImage *cornerImage = [UIImage imageNamed:#"corner_view_mask.png"]; //this is black//white/alpha
CGRect b = self.layer.bounds;
CGRect rtCornerRect=CGRectZero;
UIGraphicsBeginImageContextWithOptions(b.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
//white bg
CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGContextFillRect(context, b);
//draw corner image mask
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, cornerImage.size.height);
CGContextConcatCTM(context, flipVertical);
CGContextSetBlendMode(context, kCGBlendModeCopy);
rtCornerRect = [self cornerRectFor:XBPageDragViewCornerTopRight withSize:cornerImage.size.width];
CGContextDrawImage(context, rtCornerRect, cornerImage.CGImage);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//
//apply mask
//
CALayer *l = [CALayer layer];
l.frame = b;
l.contents = (id)image.CGImage;
l.name = #"XBPageDragViewCornersMask";
[self.layer setMask:l];
self.layer.masksToBounds = YES;
}
another example is in the apple docs but this should already be pretty clear:
you draw mask. black, alpha
you apple the mask to your view's layer
done
My question is how to animate the drawing process when drawing with CGContextRef. Is it possible? Assuming it is, how?
I have two code snippets that I would like to animate. First one draws a progress bar and the second one draws a simple line chart. The drawing is done inside a subclass of UIView.
Progress bar is nice and easy. But I want it to sort of draw itself out from the left. I am pretty sure that this will require using something other than UIRectFill but I dont know how to accomplish it.
- (void)drawProgressLine
{
[bgColor set];
UIRectFill(self.bounds);
[graphColor set];
UIRectFill(CGRectMake(0, 0, self.frame.size.width / 100 * [[items objectAtIndex:0] floatValue], self.frame.size.height));
}
The line chart is a bit more complex. I would really really like it to start drawing itself from the left line by line slowly completing itself towards the right but if that is too much how can I just slowly fade it in? The code:
- (void)drawLineChart
{
[bgColor set];
UIRectFill(self.bounds);
[graphColor set];
if (items.count < 2) return;
CGRect bounds = CGRectMake(0, 50, self.bounds.size.width, self.bounds.size.height - 100);
float max = -1;
for (GraphItem *item in items)
if (item.value > max)
max = item.value;
float xStep = (self.frame.size.width) / (items.count - 1);
for (int i = 0; i < items.count; i++)
{
if (i == items.count - 1) break;
float itemHeight = bounds.origin.y + bounds.size.height - ((GraphItem*)[items objectAtIndex:i]).value / max * bounds.size.height;
float nextItemHeight = bounds.origin.y + bounds.size.height - ((GraphItem*)[items objectAtIndex:i + 1]).value / max * bounds.size.height;
CGPoint start = CGPointMake(xStep * i, itemHeight);
CGPoint stop = CGPointMake(xStep * (i + 1), nextItemHeight);
[self drawLineFromPoint:start toPoint:stop lineWidth:1 color:graphColor shadow:YES];
}
}
Pretty simple I guess. If important the drawLineFromPoint..... is implemented like:
- (void)drawLineFromPoint:(CGPoint)startPoint toPoint:(CGPoint)endPoint lineWidth:(CGFloat)width color:(UIColor *)color shadow:(BOOL)shadow
{
if (shadow)
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[4] = {0.0, 0.0, 0.0, 1.0};
CGColorRef shadowColor = CGColorCreate(colorSpace, components);
CGContextSetShadowWithColor(UIGraphicsGetCurrentContext(), CGSizeMake(1,1), 2.0, shadowColor);
}
CGContextBeginPath(context);
CGContextSetLineWidth(context, width);
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
CGContextAddLineToPoint(context, endPoint.x, endPoint.y);
CGContextClosePath(context);
[color setStroke];
CGContextStrokePath(context);
CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);
}
I hope I made myself clear cause its 1 am in my country and this post is the last thing that stands between me and my bed. Cheers, Jan.
It sounds like you don't understand the UIKit view drawing cycle. Do you understand that each time you want to change the appearance of your custom-drawn view, you need to send it setNeedsDisplay? And then you need to redraw it entirely in your drawRect: method? Your drawing doesn't appear on screen until drawRect: returns, and after that you cannot draw more in that view until it receives another drawRect: message. If you want the contents of the view to be animated, you will need to send setNeedsDisplay to the view periodically (say, every 1/30th or 1/60th of a second, using either an NSTimer or a CADisplayLink).
It seems like you got the progress bar handled, so here is what I suggest for the graph drawing. Just create and debug your code once to draw the entire graph. Then, use a clip rect that you animate the width of, so that the clip rect starts out skinny and then extends in width until the whole graph becomes visible (from left to right). That will give the user the idea that whatever lines you have are "drawing" from the left to the right, but the actual code is very simple as the animation steps just modify the clip rect to make it wider for each "step". See this question for more info on the CoreGraphics calls: How to set up a clipping rectangle or area
i am working on a project in which i have to cropped image and save cropped image in photo library. I have got success in cropping image but problem is that when i cropped image and show it ,it is stretched .i am also using aspect fit mode. But still its stretched.Here is the code
-(void)cropLassoArea {
isLassoSelected = NO;
lassoStyle = 0;
CGRect frame;
frame = mainImageView.frame;
mainImageView.frame.size = CroppedImage.size;
for (int i = 0; i < [lassoArray count]; i++) {
CAShapeLayer *layer = [lassoArray objectAtIndex:i];
CGPathRef myPath = layer.path;
UIGraphicsBeginImageContext(mainImageView.frame.size);
[mainImageView setContentMode:UIViewContentModeCenter];
[mainImageView.image drawInRect:CGRectMake(0, 0,CroppedImage.size.width,CroppedImage.size.height)];
context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);
CGContextAddPath(context, myPath);
CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [UIColor whiteColor].CGColor);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextDrawPath(context, kCGPathFill);
CGContextEOFillPath(context);
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext();
CGContextSaveGState(context);
UIGraphicsEndImageContext();
}
}
I would suggest you to download files from this location and I suggest you to go through their tutorial. Its easy and it won't stretch your image at all. I have used these files in one of my working project. You just need to import their header file and call following lines
UIImage *tmpImage = [yourImageView.image resizedImage:CGSizeMake(desiredWidth,DesiredHeight) interpolationQuality:kCGInterpolationHigh];
yourImageView.contentMode = UIViewContentModeCenter;
[yourImageView setImage:tmpImage];
Hope it helps.
How can you mask a square image into an image with round corners?
You can use CoreGraphics to create a path for a round rectangle with this code snippet:
static void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWidth, float ovalHeight)
{
float fw, fh;
if (ovalWidth == 0 || ovalHeight == 0) {
CGContextAddRect(context, rect);
return;
}
CGContextSaveGState(context);
CGContextTranslateCTM (context, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextScaleCTM (context, ovalWidth, ovalHeight);
fw = CGRectGetWidth (rect) / ovalWidth;
fh = CGRectGetHeight (rect) / ovalHeight;
CGContextMoveToPoint(context, fw, fh/2);
CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1);
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1);
CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
CGContextClosePath(context);
CGContextRestoreGState(context);
}
And then call CGContextClip(context); to clip it to the rectangle path. Now any drawing done, including drawing an image, will be clipped to the round rectangle shape.
As an example, assuming "image" is a UIImage, and this is in a drawRect: method:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
addRoundedRectToPath(context, self.frame, 10, 10);
CGContextClip(context);
[image drawInRect:self.frame];
CGContextRestoreGState(context);
Here is an even easier method that is available in iPhone 3.0 and up. Every View-based object has an associated layer. Each layer can have a corner radius set, this will give you just what you want:
UIImageView * roundedView = [[UIImageView alloc] initWithImage: [UIImage imageNamed:#"wood.jpg"]];
// Get the Layer of any view
CALayer * layer = [roundedView layer];
[layer setMasksToBounds:YES];
[layer setCornerRadius:10.0];
// You can even add a border
[layer setBorderWidth:4.0];
[layer setBorderColor:[[UIColor blueColor] CGColor]];
To use these methods you might need to add:
#import <QuartzCore/QuartzCore.h>
I realize this is old news but just to boil it down a bit:
There are two possible questions here: (1) how do I apply rounded corners to a UIView (such as a UIImageView), which will be displayed on screen, and (2) how do I mask a square image (that is, a UIImage) to produce a new image with rounded corners.
For (1), the easiest course is to use CoreAnimation and set the view.layer.cornerRadius property
// Because we're using CoreAnimation, we must include QuartzCore.h
// and link QuartzCore.framework in a build phases
#import <QuartzCore/QuartzCore.h>
// start with an image
UIImage * fooImage = [UIImage imageNamed:#"foo.png"];
// put it in a UIImageView
UIView * view = [UIImageView alloc] initWithImage:fooImage];
// round its corners. This mask now applies to the view's layer's *background*
view.layer.cornerRadius = 10.f
// enable masksToBounds, so the mask applies to its foreground, the image
view.layer.masksToBounds = YES;
For (2), the best way is to use the UIKit graphics operations:
// start with an image
UIImage * fooImage = [UIImage imageNamed:#"foo.png"];
CGRect imageRect = CGRectMake(0, 0, fooImage.size.width, fooImage.size.height);
// set the implicit graphics context ("canvas") to a bitmap context for images
UIGraphicsBeginImageContextWithOptions(imageRect.size,NO,0.0);
// create a bezier path defining rounded corners
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:10.f];
// use this path for clipping in the implicit context
[path addClip];
// draw the image into the implicit context
[fooImage drawInRect:imageRect];
// save the clipped image from the implicit context into an image
UIImage *maskedImage = UIGraphicsGetImageFromCurrentImageContext();
// cleanup
UIGraphicsEndImageContext();
What's tricky about problem (2) is that you might think you could do the whole operation using the view.layer.mask property in CoreAnimation. But you can't because the CALayer renderInContext: method, which you'd use to generate a UIImage from the masked layer, seems to ignore the mask. Worse, the documentation for renderInContext: doesn't mention this, and only alludes to the behavior for OSX 10.5.
Some further context: the above approach to (2) is using UIKit's wrappers around more basic CoreGraphics functionality. You can do the same thing using the CoreGraphics calls directly – that is what the chosen answer is doing -- but then you need build the rounded rect bezier path manually from curves and lines and you also need to compensate for the fact that CoreGraphics uses a drawing coordinate system which is flipped with respect to UIKit's.
See this Post - Very simple answer
How to set round corners in UI images in iphone
UIImageView * roundedView = [[UIImageView alloc] initWithImage: [UIImage imageNamed:#"wood.jpg"]];
// Get the Layer of any view
CALayer * l = [roundedView layer];
[l setMasksToBounds:YES];
[l setCornerRadius:10.0];
Very simple.
self.profileImageView.layer.cornerRadius = self.profileImageView.frame.size.width / 2;
self.profileImageView.clipsToBounds = YES;
For every view, there is a bundled layer property. So the first line of the above is to set the corner radius of the layer object (i.e. an instance of CALayer class). To make a circular image from a squared image, the radius is set to the half of the width of UIImageView. For instance, if the width of squared image is 100 pixels. The radius is set to 50 pixels. Secondly, you have to set the clipsToBounds property to YES in order to make the layer works.
Both the methods work but the differences shows up depending on where you use it.
For Ex: If you have a table view with the cells showing an image along with other labels, etc., and you use layer to set the cornerRadius, the scrolling will take a big hit. It gets jerky.
I faced this issue when I was using Layer for an image in a table view cell and was trying to figure out the cause of that jerkiness only to find that CALayer was the culprit.
Used the first solution of doing the stuff in drawRect explained by NilObject. That works like a charm with scrolling being smooth as silk.
On the other hand, if you want to use this in static views like popover view, etc., layer is the easiest way to do it.
As I said, both the methods work well just that you need to decide based on where you want to use it.
I use this method.
+ (UIImage *)imageWithColor:(UIColor *)color andSize:(CGSize)size;
{
UIImage *img = nil;
CGRect rect = CGRectMake(0, 0, size.width, size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context,
color.CGColor);
CGContextFillRect(context, rect);
img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
Building off of algal, here are a couple methods that are nice to put in an UIImage category:
- (UIImage *) roundedCornerImageWithRadius:(CGFloat)radius
{
CGRect imageRect = CGRectMake(0, 0, self.size.width, self.size.height);
UIGraphicsBeginImageContextWithOptions(imageRect.size,NO,0.0); //scale 0 yields better results
//create a bezier path defining rounded corners and use it for clippping
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:radius];
[path addClip];
// draw the image into the implicit context
[self drawInRect:imageRect];
// get image and cleanup
UIImage *roundedCornerImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return roundedCornerImage;
}
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size andCornerRadius:(CGFloat)radius
{
UIImage *image = nil;
if (size.width == 0 || size.height == 0) {
size = CGSizeMake(1.0, 1.0);
}
CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(rect.size,NO,0.0); //yields sharper results than UIGraphicsBeginImageContext(rect.size)
CGContextRef context = UIGraphicsGetCurrentContext();
if (context)
{
CGContextSetFillColorWithColor(context, [color CGColor]);
if (radius > 0.0) {
//create a bezier path defining rounded corners and use it for clippping
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius];
[path addClip];
CGContextAddPath(context, path.CGPath);
}
CGContextFillRect(context, rect);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
return image;
}