I've been trying to draw a simple rectangle and animate the edge color. I tried using the following link and it still doesn't help. I finally fixed it by changing the layer's border color and animating the same. That worked fine.
Is there a KVO for strokeColor because I'm getting it working for the backgroundColor of layer.
I'm on XCode 4.6 running iOS SDK 6.1
This is what I've been doing:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CABasicAnimation *strokeAnim = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
strokeAnim.fromValue = (id) [UIColor redColor].CGColor;
strokeAnim.toValue = (id) [UIColor greenColor].CGColor;
strokeAnim.duration = 3.0;
strokeAnim.repeatCount = 0;
strokeAnim.autoreverses = YES;
[self.layer addAnimation:strokeAnim forKey:#"animateStrokeColor"];
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, squareLength, 0);
CGContextAddLineToPoint(context, squareLength, squareLength);
CGContextAddLineToPoint(context, 0, squareLength);
CGContextAddLineToPoint(context, 0, 0);
CGContextStrokePath(context);
tl;dr; You probably meant #"borderColor"
Unless you did override + (Class)layerClass in your view (which I doubt that you did) your view's layer is going to be a CALayer instance which doesn't have a strokeColor property.
You could use a CAShapeLayer but you shouldn't do that just for the stroke since CALayer has borderColor property which is probably what you were looking for.
So my advice is to animate the borderColor instead.
Not really related to your question:
From the code that you've posted it looks like you are adding the animation inside of your views drawRect: implementation. This method should only be used for actual drawing and I strongly suggest that you move the animation code somewhere else. This method will be called every time that your view redraws itself and that is likely too often for adding a new animation.
After researching this same question, I found out that strokeColor is not an animatable property on a CALayer. I think this has something to do with CALayer getting rendered to a cached bitmap.
The solution is to use a CAShapeLayer instead where strokeColor is animatable.
Related
I've been thinking about this one for a while. Basically, the code below draws a border on a UIView, but, if I add another UIView as a subview to that UIView - it'll appear above the border.
Like this:
How do I (as cleanly as possible), keep the border above all subviews?
Like this:
This is what I have so far. But, like stated above, it doesn't keep the border above all its subviews.
CGPathRef CGPathRect = CGPathCreateWithRect(rect, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGPathRef border = CGPathCreateCopyByStrokingPath(rect.CGPath, NULL, 5.0f, kCGLineCapButt, kCGLineJoinMiter, 0);
CGContextAddPath(context, border);
CGContextSetFillColorWithColor(context, someCGColor);
CGContextDrawPath(context, kCGPathFill);
CGPathRelease(border);
I could create a separate UIView for the border itself, and just insert subviews below that UIView, but that feels rather hackish. If there's a better way - I'd love to hear about it.
I would say use:
view.clipToBounds = YES;
view.layer.borderColor = [UIColor redColor];
view.layer.borderWidth = 2;
view.layer.cornerRadius = 4;
All subviews are clipped and u can keep ur border :).
Hi all I need to realize borders around an UIImageView like these.
Left, top and right border like an half-moon
http://imageshack.com/a/img841/3269/o90p.png
How can i do it ?
You can user CALayer for the same.
CALayer *l = [_btn layer];
[l setMasksToBounds:YES];
[l setCornerRadius:8.0];
and add QuartzCore.framework
You may want to subclass UIView and override the drawRect: method. Then place add your image view as a subview. Your drawRect: method would look something like this (with clipsToBounds set to YES):
- (void)drawRect:(CGRect)rect
{
UIColor *fillColor = [UIColor clearColor];
UIColor *borderColor = [UIColor redColor];
float borderWidth = 2.0f;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, fillColor.CGColor);
CGContextSetLineWidth(context, borderWidth);
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
CGContextMoveToPoint(context, CGRectGetMinX(rect), CGRectGetMidY(rect));
CGContextAddArc(context, CGRectGetMidX(rect), CGRectGetMidY(rect), rect.size.height/2, 2*M_PI, M_PI, 1);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}
Alternatively you can look into masking with a semicircle image or creating a CAShapeLayer for the image view.
You need to:
Create a CAShapeLayer
Set its path to be a CGPathRef based on view.bounds but with only two rounded corners (probably by using [UIBezierPath
bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:])
Set your view.layer.mask to be the CAShapeLayer
Just take in account that this have a bad effect on performance. This might help
I'm using this to animate an object along a path. I'd like to know how to get the position of the object at any point during the animation to check for a collision with another object. Any ideas how to go about this? Thanks.
- (void) animateCicleAlongPath {
//Prepare the animation - we use keyframe animation for animations of this complexity
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationCubic;
[pathAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration = 7.0;
//Lets loop continuously for the demonstration
pathAnimation.repeatCount = 1;
//Setup the path for the animation - this is very similar as the code the draw the line
//instead of drawing to the graphics context, instead we draw lines on a CGPathRef
CGPoint endPoint = CGPointMake(endpointx, endpointy);
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL,startpointx,startpointy);
// CGPathMoveToPoint(curvedPath, NULL, 10, 10);
CGPathAddQuadCurveToPoint(curvedPath,NULL, controlpointx, controlpointy, endpointx, endpointy);
//Now we have the path, we tell the animation we want to use this path - then we release the path
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
//We will now draw a circle at the start of the path which we will animate to follow the path
//We use the same technique as before to draw to a bitmap context and then eventually create
//a UIImageView which we add to our view
UIGraphicsBeginImageContext(CGSizeMake(20,20));
CGContextRef ctx = UIGraphicsGetCurrentContext();
//Set context variables
CGContextSetLineWidth(ctx, 1.5);
CGContextSetFillColorWithColor(ctx, [UIColor greenColor].CGColor);
CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
//Draw a circle - and paint it with a different outline (white) and fill color (green)
CGContextAddEllipseInRect(ctx, CGRectMake(1, 1, 18, 18));
CGContextDrawPath(ctx, kCGPathFillStroke);
UIImage *circle = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *circleView = [[UIImageView alloc] initWithImage:circle];
circleView.frame = CGRectMake(1, 1, 20, 20);
[throwingView addSubview:circleView];
//Add the animation to the circleView - once you add the animation to the layer, the animation starts
[circleView.layer addAnimation:pathAnimation forKey:#"moveTheSquare"];
}
I believe you have two problems here:
Check the collision
Find out where to verify the collision
Checking the collision
A CALayer has a a model layer and a presentation layer. The presentation layer give you the visual information you need. Basically this layer is responsible to have the information about where the layer is on the screen. You can get it doing: circleView.layer.presentationLayer
Find out where to verify the collision
You can do this with a NSTimer that runs every 1/60 seconds. However, this is not a good solution because the NSTimer only guarantees the minimum time it will take to perform a selector. This way you can have cases where you are going to check your colision after the object already colided.
You can, however, use CADisplayLink. This way you will get a call before the layer is render in the screen.
I am drawing Circle using UIBezierPath. I want to draw it on CGLayer so that I can cache circle and draw text on it after some event(by calling setNeedsDisplay). How should I draw UIBezierPath on CGContextRef. My code below
- (void)drawRect:(CGRect)rect
{
// Drawing code
static CGLayerRef sTextLayer = NULL;
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect textBounds = CGRectMake(0, 0, 200, 100);
if (sTextLayer == NULL) {
sTextLayer = CGLayerCreateWithContext(ctx, textBounds.size, NULL);
CGContextRef textCtx = CGLayerGetContext(sTextLayer);
CGContextSetRGBFillColor (textCtx, 1.0, 0.0, 0.0, 1);
UIGraphicsPushContext(textCtx);
// Draw circle
UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:textBounds];
[[UIColor blackColor] setFill];
circle.lineWidth = 2.0;
[circle fill];
UIGraphicsPopContext();
}
if (self.drawString) {
UIFont *font = [UIFont systemFontOfSize:13.0];
NSString *string = #"HAPPY BIRTHDAY";
[string drawInRect:textBounds withFont:font];
}
}
Get the CGPath from the bezier path and then draw using CGContextAddPath and CGContextStrokePath. You may also want to use CGContextSetStrokeColorWithColor.
You can either:
Avoid the use of the CGLayerRef altogether and just have drawRect draw the UIBezierPath with fill method (or as Wain suggests, draw the circle using Core Graphics functions).
Add a CAShapeLayer, setting its path to the CGPathRef of the UIBezierPath, and then add the text as a CATextLayer or a UILabel subview (in which case, you don't need a drawRect implementation at all).
I infer that you're worried about the efficiency of redrawing the circle, but when you render the text, it probably needs to re-render the circle anyway. You could always save a reference to the UIBezierPath or do a snapshot, but I'm not sure that's worth it. You can benchmark it and see.
I have a label in my view. Now I want to draw a line above the label, which means you can see the label is under the line.
So far I can draw a line using quartz2D but always under the label. Is there any way to solve my problems?
You can create a CAShapeLayer like this:
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.frame = self.label.bounds;
lineLayer.strokeColor = [UIColor redColor].CGColor;
CGRect rect = CGRectMake(0, CGRectGetMidY(lineLayer.bounds), lineLayer.bounds.size.width, 2);
lineLayer.path = [UIBezierPath bezierPathWithRect:rect].CGPath;
And then add it to the UILabel like this:
[self.label.layer addSublayer:lineLayer];
To be honest, the easiest thing to do is to create a 2x2 pixel image called line#2x.png, and have the bottom 2 pixels black, the top 2 transparent, then use it as the background image for an image view. Stretch the image view to whatever width you need by using it as a pattern image. The 1x image should be a 1x1px, all black.
UIView *lineView = [[UIView alloc] initWithFrame:frame]; // Whatever frame the line needs
// Add the line image as a pattern
UIColor *patternColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"line.png"]];
lineView.backgroundColor = patternColor;
[self.view addSubview:lineView];
If this is a label you will be using a lot, you could make a sub-class of UILabel and override the drawRect function.
- (void) drawRect:(CGRect)r
{
[super drawRect:r];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextMoveToPoint(context, 1.0, 1.0);
CGContextAddLineToPoint(context, self.bounds.size.width - 1.0, 1.0);
CGContextStrokePath(context);
CGContextRestoreGState(context);
}
The advantage here is that the line will be "baked" into the view and only be draw once. Other methods such as CAShapeLayer or UIView will be re-rendered every frame.
For bonus points, you can make the color and line width properties :)