use a CALayer as a UIImageView - iphone

I would like to create a CALayer bezier path in a circle shape. And I would like to set it like it was a UIImageView (to be able to move it with animation etc.) Is it possible? If so how can I do it? Thank you.

You could always add a CAShapeLayer to your view. During init, do something like (all untested):
CAShapeLayer *circle = [[CAShapeLayer alloc] init];
[self.layer addSublayer:circle];
Then, in drawRect, layoutSubViews, or some other place that seems appropriate to update the layout:
circle.fillColor = [[UIColor redColor].CGColor;
CGMutablePathRef p = CGPathCreateMutable();
CGPathAddEllipseInRect(p, NULL, self.bounds);
circle.path = p;
circle.bounds = self.bounds;
circle.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));

I'm not sure what you mean by a "CALayer bezier path" but you have a couple of options here:
Create a subclass of UIView, where you draw your circle using UIBezierPath methods in the drawRect:. This view can then be added to another view as a subview and animated using standard methods. However, you are warned that this may affect performance, (as it has to keep re-drawing) so if that does turn out to be a problem...
Create a graphics context and draw your circle in there. Take an image from this graphics context and set it as the image for a UIImageView. Animate as in answer (1). This way you aren't redrawing all the time so performance would be better, but try with option 1 first as it is simpler, and if you have simple drawing code it shouldn't affect you too much.
For option 2, you begin a graphics context using UIGraphicsBeginImageContextWithOptions - you then do your drawing, and extract the image using UIGraphicsGetImageFromCurrentImageContext, then end the context using UIGraphicsEndImageContext. Those functions are all documented here
Sample code to do this:
CGFloat radius = 50;
CGSize size = CGSizeMake(radius*2,radius*2);
CGPoint centre = CGPointMake(radius,radius);
UIGraphicsBeginImageContextWithOptions(size,NO, 0.0);
UIBezierPath * solidPath = [UIBezierPath bezierPathWithArcCenter:centre radius:edgeSize/20 startAngle:0 endAngle:2 * M_PI clockwise:YES];
[solidPath closePath];
[[UIColor whiteColor] set];
[solidPath fill];
UIImage *circle = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imageView = [[UIImageView alloc] initWithImage:circle];

Related

How to create an oval shape UIButton

I have to draw an oval shape button, i don't want to set the image in oval shape.
I have read that you can draw different shaper with UIBezierPath but i am unable to draw a proper oval shape.
Here is the code is am using to create a circular shape.
self.layer.borderWidth=1.0;
self.clipsToBounds = YES;
[self setTitleColor:ktextColor forState:UIControlStateNormal];
self.layer.cornerRadius = self.bounds.size.width/2;//half of the width
self.layer.borderColor=[[UIColor whiteColor]CGColor];
Any help is appreciated !!
You can set the delegate for your button's layer and implement the delegate method `displayLayer:'
-(void)displayLayer:(CALayer *)layer
{
UIGraphicsBeginImageContext(layer.frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(0.0, 0.0, CGRectGetWidth(layer.frame), CGRectGetHeight(layer.frame))];
[[UIColor whiteColor] setFill];
[ovalPath fill];
[[UIColor blackColor] setStroke];
ovalPath.lineWidth = 1;
[ovalPath stroke];
UIImage *imageBuffer = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
layer.contents = (id)[imageBuffer CGImage];
}
You can also create a CAShapelayer and than add gesture to the layer as below:
-(void)generateOvalWithSize:(CGSize)size origin:(CGPoint)origin {
CAShapeLayer ovalLayer = [CAShapeLayer layer];
CGMutablePathRef ovalPath = CGPathCreateMutable();
CGPathAddEllipseInRect(ovalPath, NULL, CGRectMake(origin.x, origin.y, size.width, size.height));
CGPathCloseSubpath(ovalPath);
ovalLayer.path = ovalPath;
// Configure the apperence of the circle
ovalLayer.fillColor = [UIColor whiteColor].CGColor;
ovalLayer.strokeColor = [UIColor whiteColor].CGColor;
ovalLayer.lineWidth = 2;
// Add to parent layer
[[self layer] addSublayer:ovalLayer];
}
i have been using these two functions for a while, i haven't come across any issues yet, but if someone sees an issue please let me know.
you can pass anything that is a subclass of UIView (ie. UIButton, UICollectionViewCell, etc.)
+ (void)setCornerRadius:(CGFloat)radius forView:(UIView*)view
{
[view.layer setCornerRadius:radius];
[view.layer setMasksToBounds:YES];
}
+ (void)setBordersForView:(UIView*)view width:(CGFloat)width color:(UIColor*)color
{
view.layer.borderColor = color.CGColor;
view.layer.borderWidth = width;
}
the above yields me the effect below in a collection view:
the collection view cell has a single button which is bound all the way around the edges (auto layout) and takes up the full height and width of the cell.
then i pass in the entire cell (or self.viewForBaseLineLayout if calling from within the cell) as the view parameter in both functions listed above.
for the radius parameter i am passing 20.0f.
(but this value will vary depending on the size the view you are passing; you just have to play around with it)
i know this post is old, but maybe it will help someone new : )
You can use the OBShapedButton class Download Here

CAShapeLayer sublayer of a UIView in a UIScrollView is extremely slow

I have a custom UIView which is supposed to be a round rectangle which, when you begin editing a UILabel inside, the round rectangle will grow its shape (CABasic animation on a CAShapeLayer mask path and outline path). So to be clear, the custom UIView is organized like this:
MyCustomView has a ClipView.
ClipView uses drawRect to draw the background of the round rectangle. (clipview == view that gets clipped)
Then, I call the following code to do the CAShapeLayer manipulation:
//0. CLIP
shapeLayer = [CAShapeLayer layer];
CGRect shapeRect = self.bounds;
[shapeLayer setBounds:shapeRect];
[shapeLayer setPosition:CGPointMake((shapeRect.size.width)/2.0f, (shapeRect.size.height)/2.0f)];
[shapeLayer setFillColor:[[UIColor colorWithRed:0 green:1 blue:0 alpha:1.0f] CGColor]];
[shapeLayer setStrokeColor:[[UIColor redColor] CGColor] ];
[shapeLayer setLineWidth:1];
[shapeLayer setLineJoin:kCALineJoinRound];
[shapeLayer setOpacity:1];
CGRect shapeLayerPathRect = CGRectMake(0, 0, self.bounds.size.width, 44.0f);
shapeLayer.path = [self roundRectPathForRectangle:shapeLayerPathRect andRadius:CORNER_RADIUS];
[[self.clipView layer] setMask:shapeLayer];
//1. draw the shaded outline of a round rectangle
outlineLayer = [self topDownShadowShapeLayer]; //setup for my CAShapeLayer, involving shadow stuff, line color, etc
outlineLayer.path = [self roundRectPathForRectangle:shapeLayerPathRect andRadius:CORNER_RADIUS];
[self.clipView.layer addSublayer:outlineLayer];
Although the animation the proceeds is the quickest approach (stretching both CAShapeLayers), the entire element (MyCustomView) causes everything to be a lot slower! For example, UIView animations that go on, also when MyCustomView is inside a ScrollView it is VERY jumpy.
I'm mostly concerned about the UIScrollView problem. Why is it so choppy for a UIScrollView to translate these layers around? Is there a good solution / what's another approach that can achieve the same effect?

How to get context of a CALayer plus a few pixels around it?

I am creating a layer with a shadow effect. Instead of adding it as a sublayer to my view's layer, I would like to draw it. But I am having problems being able to draw it with the shadow effect. The problem is that the context size it based on the layer size. But how do I set the rect for the context?!
CALayer *layer = [CALayer layer];
layer.frame = ...;
layer.backgroundColor = [UIColor redColor].CGColor;
layer.cornerRadius = 2.0;
layer.masksToBounds = NO;
layer.shadowColor = [[UIColor whiteColor] CGColor];
layer.shadowOffset = CGSizeMake(0, 1);
layer.shadowRadius = 0.5;
layer.shadowOpacity = 0.2;
[self.layer addSublayer:layer];
UIGraphicsBeginImageContext(layer.bounds.size);
[layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[viewImage drawAtPoint:CGPointMake(0, 0)];
[layer removeFromSuperlayer];
With the code above, what I am getting is the box, but without a few extra pixels of padding with the shadow (on all four sides). If I increase the size of the ImageContext, all I get is more height and width, but still starting from x=0 and y=0, where I would want it to start from x=-5, y=-5 or something like that.
Thanks a bunch!
Make your context 10 pixels larger in both the x and the y.
Then before you draw your layer do a CGContextTranslateCTM(context,5,5);
Hope this helps!
I'm not sure if the following is your problem, but it just caught my eye.
[self.layer addSublayer:layer];
UIGraphicsBeginImageContext(layer.bounds.size);
[layer renderInContext:UIGraphicsGetCurrentContext()];
It looks like you have a property on this file called layer, which you are adding the shadow layer you've created to. You then get the context based on the current CALayer, as opposed to self.layer.
I typically use CALayer to modify UIImageView, and do not have a need to add sub layers, so perhaps that code is fine. Just wanted to point it out, please do let me know if which way is accurate, as I always like to learn.
~Good Luck

Drawing a transparent circle on top of a UIImage - iPhone SDK

I am having a lot of trouble trying to find out how to draw a transparent circle on top of a UIImage within my UIImageView. Google-ing gives me clues, but I still can't find a working example.
Are there any examples that anyone knows of that demonstrate this?
Easiest way is simply to create a semi-transparent square UIView, then set the cornerRadius of its layer to be half of its width/height. Something like:
UIView *squareView = [[UIView alloc] initWithFrame:CGRectMake(0,0,100,100)];
squareView.alpha = 0.5;
squareView.layer.cornerRadius = 50;
...
[squareView release];
This has got to be the simplest solution:
CGFloat r = 150;
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0,0,1.5*r,1.5*r)];
lbl.text = #"●";
lbl.transform = CGAffineTransformMakeTranslation(0.0f, -r/6);
lbl.textAlignment = UITextAlignmentCenter;
lbl.backgroundColor = [UIColor clearColor];
lbl.textColor = [UIColor redColor];
lbl.font = [UIFont systemFontOfSize:2*r];
lbl.alpha = 0.5;
lbl.center = self.view.center;
[self.view addSubview:lbl];
One way would be to add a CAShapeLayer with a circular path, either directly to the layer of the UIImageView or as the layer of a new UIView that is added to the UIImageView.
If you actually want to modify the image, then create a mutable copy of it by drawing it into a CGBitmapContext then creating a new image from the modified bitmap.
CGPathRef circlePath = CGPathCreateMutable();
CGPathAddEllipseInRect( circlePath , NULL , CGRectMake( 0,0,20,20 ) );
CAShapeLayer *circle = [[CAShapeLayer alloc] init];
circle.path = circlePath;
circle.opacity = 0.5;
[myImageView.layer addSublayer:circle];
CGPathRelease( circlePath );
[circle release];
You can implement a custom sub-class of UIView that draws your image and then the circle in the drawRect method:
#interface CircleImageView : UIView {
UIImage * m_image;
CGRect m_viewRect;
// anything else you need in this view?
}
Implementation of drawRect:
- (void)drawRect:(CGRect)rect {
// first draw the image
[m_image drawInRect:m_viewRect blendMode:kCGBlendModeNormal alpha:1.0];
// then use quartz to draw the circle
CGContextRef context = UIGraphicsGetCurrentContext ()
// stroke and fill black with a 0.5 alpha
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 0.5);
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 0.5);
// now draw the circle
CGContextFillEllipseInRect (context, m_viewRect);
}
You will need to set up the m_viewRect and m_image member functions on init.

"Masking" an animation? iPhone SDK

I have been looking into ways of masking images on the iPhone. I came across this solution
http://iosdevelopertips.com/cocoa/how-to-mask-an-image.html
which works great for still images. What I want to do is mask an animation in a UIImageView. From what I have read I don't think this is possible whilst also achieving a decent frame rate.
This leads me to ask whether the following is possible, could I "clip" the images inside the UIImageView? ie not re-size the UIImageView to the size of the images so some parts are chopped off?
I haven't tried this for performance, but you can use a Core Animation layer as a mask. You can define a path to use in a CAShapeLayer and fill the shape. Then specify the layer mask of your UIImageView's layer. Something like:
CGMutablePathRef path = CGPathCreateMutable();
// build the path by adding points
// ...
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
[shapeLayer setPath:path];
[shapeLayer setFillColor:[[UIColor blackColor] CGColor]];
// Set shape layer bounds and position
// ...
// Set the mask for the image view's layer
[[imageView layer] setMask:shapeLayer];
Keep in mind that this isn't actually creating a new image as the link you reference does. This just creates a mask for display over top of your image view--which may not be what you want.
I searched high and low and finally found a solution with practically no limitations at all. So, here you go:
UIImageView *maskeeImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"MaskeeImage.png"]];
[maskeeImage setAnimationRepeatCount:-1];
[maskeeImage setAnimationImages:[[NSArray alloc] initWithObjects:[UIImage imageNamed:#"MaskeeImage1.png"], [UIImage imageNamed:#"MaskeeImage2.png"], [UIImage imageNamed:#"MaskeeImage3.png"], nil]];
[maskeeImage startAnimating];
CALayer *maskeeLayer = [maskeeImage layer];
maskeeLayer = CGRectMake(0, 0, 768, 1004);
[[[self view] layer] addSublayer:maskeeLayer];
UIImage *maskImage = [UIImage imageNamed:#"ImageMask.png"];
CALayer *maskLayer = [CALayer layer];
maskLayer.contents = (id) myImageMask.CGImage;
maskLayer.frame = CGRectMake(0, 0, 768, 1004);
[maskeeLayer setMask:maskLayer];
There you go! It's actually really easy, once you know how. I tried to show a few different options; Using UIImageViews or UIImages, Animations (Which can also be used for the mask).
To sum it all up, you basically have to set the mask property on your view's CALayer. Every UIView subclass has a CALayer attached to it, so you aren't restricted at all in terms of where you get your mask or maskee from.
Hope this helped. Cheers, Dylan.