Adding a CALayer to MKOverlayRenderer? - ios

In iOS 7, MKOverlayView was replaced by MKOverlayRenderer. Before, I was able to add a UIImageView as a subview to MKOverlayView, and access the CALayer of the MKOverlayView. Now, without UIView gone in MKOverlayRenderer, I'm not sure on how to add a custom CALayer (I have a CAKeyFrameAnimation that goes through a series of pictures rapidly). How would I add a UIImageView into MKOverlayRenderer? How would I add a CALayer in a CGContextRef (used by MKOverlayRenderer)?
I tried doing it on my own, but it doesn't go through the images at all.
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
CGContextSaveGState(context);
CGRect test = [self rectForMapRect:self.overlay.boundingMapRect];
// Clip the drawing space to the map rect
CGRect clipRect = [self rectForMapRect:mapRect];
CGContextClipToRect(context, clipRect);
// CoreGraphics' coordinate system is flipped, so we need to transform it first
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -test.size.height);
// Draw the portion of the image in the map rect
// CGContextDrawImage(context, test, [image CGImage]);
CGContextRestoreGState(context);
CALayer *sublayer = [CALayer layer];
sublayer.frame = test;
NSArray *radarImages = [NSArray arrayWithObjects:(id)image.CGImage, image1.CGImage, image2.CGImage, image3.CGImage, image4.CGImage, image5.CGImage, image6.CGImage, image7.CGImage, image8.CGImage, image9.CGImage, image10.CGImage, image11.CGImage, image12.CGImage, image13.CGImage, image14.CGImage, image15.CGImage, image16.CGImage, image17.CGImage, image18.CGImage,image19.CGImage,image20.CGImage,image21.CGImage, image22.CGImage, image23.CGImage, image24.CGImage, nil];
anim = [CAKeyframeAnimation animation];
[anim setKeyPath:#"contents"];
[anim setCalculationMode:kCAAnimationDiscrete];
[anim setValues:radarImages];
[anim setRepeatCount:INFINITY];
[anim setDuration:2.6];
[self drawLayer:sublayer inContext:context];
[sublayer addAnimation:anim forKey:nil];
[sublayer renderInContext:context];
[sublayer setNeedsDisplay];
}
Any help is appreciated, thanks!

Try adding the following code
[[[self view] layer] addSublayer: sublayer];
and use NSMutableArray instead of NSArray.

Related

Objective- C: Draw border on an UIImage

I currently have the following image on which I am trying to set a border. It consists of an UIImageView with an image inside (a transparent.png)
When I try to set the border for my image (see code), it gives a border to the UIImage, but it doesn't 'snap' around my image. Is it possible to achieve that effect?
See image current implementation here.
- (UIImage*)imageWithBorderFromImage:(UIImage*)source;
{
CGSize size = [source size];
UIGraphicsBeginImageContext(size);
CGRect rect = CGRectMake(0, 0, size.width, size.height);
[source drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1.0, 0.5, 1.0, 1.0);
CGContextStrokeRect(context, rect);
UIImage *testImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return testImg;
}
Try Adding a layer behind UIImageView and add a border to it that will do the trick
#define kBorderWidth 3.0.
#define kCornerRadius 8.0
CALayer *borderLayer = [CALayer layer];
CGRect borderFrame = CGRectMake(0, 0, (imageView.frame.size.width), (imageView.frame.size.height));
[borderLayer setBackgroundColor:[[UIColor clearColor] CGColor]];
[borderLayer setFrame:borderFrame];
[borderLayer setCornerRadius:kCornerRadius];
[borderLayer setBorderWidth:kBorderWidth];
[borderLayer setBorderColor:[[UIColor redColor] CGColor]];
[imageView.layer addSublayer:borderLayer];
And don't forget to import QuartzCore/QuartzCore.h
This example will draw a boarder on the layer, but change it's frame slightly to make the border around the layer.
Depending on your needs, if you don't want it to be as accurate as possible, a quick and dirty solution could be something like this:
- (UIImage *)borderedImageFromImage:(UIImage *)source andColor:(UIColor *)borderColor{
CGFloat scale = 0.95;//this determines how big the border will be, the smaller it is the bigger the border
UIImage *borderImage = [source imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIGraphicsBeginImageContextWithOptions(source.size, NO, source.scale);
[borderColor set];
[borderImage drawInRect:CGRectMake(0, 0, source.size.width, source.size.height)];
[source drawInRect:CGRectMake(source.size.width*(1-scale)/2,
source.size.height*(1-scale)/2,
source.size.width * scale,
source.size.height * scale)];
borderImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return borderImage;
}
and here is how to use it:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.heartImageView.image = [self borderedImageFromImage:[UIImage imageNamed:#"heart"] andColor:[UIColor blackColor]];
}
What this essentially does is draw the image you want twice, once in the colour of the border (slightly scaled) and once with the normal colour. Your mileage may vary depending on the image.

How to put 2 layers to follow same bezierpath without one hiding the other

I have created 2 CALayers of same size and am passing these to the method below. However, the two layers runs together. How can I seperate these so that both are visible?
- (void) myAnimation : (CALayer *) sublayer {
UIBezierPath* aPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(30, 100, 270, 270)];
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = aPath.CGPath;
anim.rotationMode = kCAAnimationRotateAuto;
anim.repeatCount = HUGE_VALF;
anim.duration =35.0;
[sublayer addAnimation:anim forKey:#"race"];
}
Your path begins and ends at the same point. Assuming both beginning times are the same and duration is the same, your layers will precisely overlap. You can either shift the beginning time or rotate your UIBezierPath* aPath Here's an example of rotating your UIBezierPath* aPath and altering the duration. It should give you an idea how you could alter duration, begin time, rotation, etc.
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor blackColor]];
CALayer * layer1 = [CALayer layer];
CALayer * layer2 = [CALayer layer];
[layer1 setFrame:CGRectMake(0, 0, 5, 50)];
[layer2 setFrame:CGRectMake(0, 0, 5, 100)];
layer1.backgroundColor = [[UIColor colorWithRed:.1 green:.5 blue:1 alpha:.35] CGColor];
layer2.backgroundColor = [[UIColor colorWithRed:.9 green:.2 blue:.5 alpha:.35] CGColor];
[self.view.layer addSublayer:layer1];
[self.view.layer addSublayer:layer2];
[self myAnimation:layer1 andRotation:0 andDuration:35.0];
[self myAnimation:layer2 andRotation:10 andDuration:10.0];
}
- (void) myAnimation:(CALayer *)sublayer andRotation:(CGFloat)rot andDuration:(CFTimeInterval)dur {
CGRect rect = CGRectMake(30, 100, 270, 270);
UIBezierPath* aPath = [UIBezierPath bezierPathWithOvalInRect:rect];
// Creating a center point around which we will transform the path
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, center.x, center.y);
transform = CGAffineTransformRotate(transform, rot);
// Recenter the transform
transform = CGAffineTransformTranslate(transform, -center.x, -center.y);
// This is the new path we will use.
CGPathRef rotated = CGPathCreateCopyByTransformingPath(aPath.CGPath, &transform);
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = rotated;
anim.rotationMode = kCAAnimationRotateAuto;
anim.repeatCount = HUGE_VALF;
anim.duration =dur;
[sublayer addAnimation:anim forKey:#"race"];
}

How to draw a rectangle with an animated stroke

I know how to draw a rectangle outline to the screen with something like this:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGPathRef path = CGPathCreateWithRect(rect, NULL);
[[UIColor greenColor] setStroke];
CGContextAddPath(context, path);
CGContextDrawPath(context, kCGPathFillStroke);
CGPathRelease(path);
}
But what I want is to have the "pen" start at the top center of the rectangle and draw around the edges at some variable speed, so that you can actually see the rectangle getting drawn as the "pen" moves. Is this possible? How?
You could easily use a CALayerShape with a Pen Image and do it like this (I added a button to the view which triggered the drawing):
#import "ViewController.h"
#interface ViewController () {
UIBezierPath *_drawPath;
CALayer *_pen;
UIBezierPath *_penPath;
CAShapeLayer *_rectLayer;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage *penImage = [UIImage imageNamed:#"pen"];
_drawPath = [UIBezierPath bezierPathWithRect:self.view.frame];
_penPath = [UIBezierPath bezierPath];
[_penPath moveToPoint:CGPointMake(penImage.size.width/2.f, penImage.size.height/2.f)];
[_penPath addLineToPoint:CGPointMake(self.view.frame.size.width - penImage.size.width/2.f, penImage.size.height/2.f)];
[_penPath addLineToPoint:CGPointMake(self.view.frame.size.width - penImage.size.width/2.f, self.view.frame.size.height - penImage.size.height/2.f)];
[_penPath addLineToPoint:CGPointMake(penImage.size.width/2.f, self.view.frame.size.height - penImage.size.height/2.f)];
[_penPath addLineToPoint:CGPointMake(penImage.size.width/2.f, penImage.size.height/2.f)];
_rectLayer = [[CAShapeLayer alloc] init];
_rectLayer.path = _drawPath.CGPath;
_rectLayer.strokeColor = [UIColor greenColor].CGColor;
_rectLayer.lineWidth = 5.f;
_rectLayer.fillColor = [UIColor clearColor].CGColor;
_rectLayer.strokeEnd = 0.f;
[self.view.layer addSublayer:_rectLayer];
_pen = [CALayer layer];
_pen.bounds = CGRectMake(0, 0, 25.f, 25.f);
_pen.position = CGPointMake(penImage.size.width/2.f, penImage.size.height/2.f);
_pen.contents = (id)(penImage.CGImage);
_pen.position = CGPointMake(penImage.size.width/2.f, penImage.size.height/2.f);
[self.view.layer addSublayer:_pen];
}
- (IBAction)drawRectangle:(id)sender
{
_rectLayer.strokeEnd = 1.f;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
anim.fromValue = (id)[NSNumber numberWithFloat:0];
anim.toValue = (id)[NSNumber numberWithFloat:1.f];
anim.duration = 5.f;
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[_rectLayer addAnimation:anim forKey:#"drawRectStroke"];
CAKeyframeAnimation *penMoveAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
penMoveAnimation.path = _penPath.CGPath;
penMoveAnimation.rotationMode = kCAAnimationRotateAuto;
penMoveAnimation.duration = 5.0;
penMoveAnimation.calculationMode = kCAAnimationPaced;
[_pen addAnimation:penMoveAnimation forKey:#"followStroke"];
}
EDIT: Added code for pen to follow stroke, heres the 2x image used: http://cl.ly/image/173J271Y003B
Note: The only problem is that when the pen gets closer to the rotating point or corner its still paced with the stroke therefore the pen looks like its behind the stroke a bit until it flips, a simple solution might be to arc the curve, but Im not sure what your overall goal is.

Drawing a shape in a UIImageView IOS

I am trying to draw some circles inside a UIImageView with a specific image. This is what I was trying to do:
UIGraphicsBeginImageContext(self.view.bounds.size);
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(contextRef, 2.0);
CGContextSetStrokeColorWithColor(contextRef, [color CGColor]);
CGRect circlePoint = (CGRectMake(coordsFinal.x, coordsFinal.y, 50.0, 50.0));
CGContextStrokeEllipseInRect(contextRef, circlePoint);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[photoView addSubview:image];
The circle is drawn fine, but I would like the PhotoView to act as a mask to it. So if for example I move the UIImageView out of the UIView using an animation, I would like the circle to move with it. Important is the fact that the coordinates are relative to the whole screen.
Use Core Animation's shape layer instead.
CAShapeLayer *circleLayer = [CAShapeLayer layer];
// Give the layer the same bounds as your image view
[circleLayer setBounds:CGRectMake(0.0f, 0.0f, [photoView bounds].size.width,
[photoView bounds].size.height)];
// Position the circle anywhere you like, but this will center it
// In the parent layer, which will be your image view's root layer
[circleLayer setPosition:CGPointMake([photoView bounds].size.width/2.0f,
[photoView bounds].size.height/2.0f)];
// Create a circle path.
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:
CGRectMake(0.0f, 0.0f, 50.0f, 50.0f)];
// Set the path on the layer
[circleLayer setPath:[path CGPath]];
// Set the stroke color
[circleLayer setStrokeColor:[[UIColor redColor] CGColor]];
// Set the stroke line width
[circleLayer setLineWidth:2.0f];
// Add the sublayer to the image view's layer tree
[[photoView layer] addSublayer:circleLayer];
Now, if you animate the UIImageView that contains this layer, the layer will move with it since it is a child layer. And there is now no need to override drawRect:.

Move a UIImageView

What is the best way to move an image along an array of dots?
My recommended approach would be to wrap the UIImage in a UIImageView and use a CAKeyframeAnimation to animate your UIImageView's layer along a path that passes through your three points:
UIImage *image = [UIImage imageNamed:#"image.png"];
imageView = [[UIImageView alloc] initWithImage:image];
[mainView addSubview:imageView];
// Remember to remove the image view and release it when done with it
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.duration = 1.0f;
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
CGMutablePathRef pointPath = CGPathCreateMutable();
CGPathMoveToPoint(pointPath, NULL, viewOrigin.x, viewOrigin.y);
CGPathAddLineToPoint(pointPath, NULL, point1.x, point1.y);
CGPathAddLineToPoint(pointPath, NULL, point2.x, point2.y);
CGPathAddLineToPoint(pointPath, NULL, point3.x, point3.y);
pathAnimation.path = pointPath;
CGPathRelease(pointPath);
[imageView.layer addAnimation:pathAnimation forKey:#"pathAnimation"];
Note that by default, the position of a layer is at the layer's center. If you'd like to move the layer relative to another reference point, you can set the layer's anchorPoint property to something like (0.0, 0.0) for its upper-left corner (on the iPhone) or (0.0, 1.0) for its lower left.
Also, this won't change the frame of the UIImageView when it's done, so if you refer to that frame later on, you may need to either take that into account or add a delegate method callback for the end of your animation to set it to the proper value.
You can also make your image move along curves, instead of straight lines, by replacing the calls to CGPathAddLineToPoint() with CGPathAddCurveToPoint().
EDIT (5/14/2009): I added the missing pathAnimation.path = pointPath line and changed a mistyped reference to curvedPath to pointPath.
The easiest way is to uses UIView animations
A quick example that assumes you are able to use UIImageView to hold your image and NSArray to hold your point.
UIImage *image = [UIImage imageNamed:#"image.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[someView addSubview:imageView]; // Assume someView exists
NSValue *firstPoint = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
NSValue *secondPoint = [NSValue valueWithCGPoint:CGPointMake(100, 0)];
NSValue *thirdPoint = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
// And so on....
NSArray *points = [NSArray arrayWithObjects:firstPoint, secondPoint, thirdPoint, nil];
for (NSValue *pointValue in points) {
[UIView beginAnimations:#"UIImage Move" context:NULL];
CGPoint point = [pointValue CGPointValue];
CGSize size = imageView.frame.size;
imageView.frame = CGRectMake(point.x, point.y, size.width, size.height);
[UIView commitAnimations];
}

Resources