Do you know how to create an animation like the Blue Marble drop User-Location in MKMapView?
Although I am not sure on the specifics of how Apple accomplished this effect, this feels to me like a great opportunity to use CoreAnimation and custom animatable properties. This post provides some nice background on the subject. I assume by the "Blue Marble drop" animation you're referring to the following sequence:
Although this may be simplifying the process slightly, I think it's a good place to start and more complex/detailed functionality can be added with relative ease (i.e. the small dark circle pulsing as larger circle converges on it.)
The first thing we need is a custom CALayer subclass with a custom property for our outer large light blue circles radius:
#import <QuartzCore/QuartzCore.h>
@interface CustomLayer : CALayer
@property (nonatomic, assign) CGFloat circleRadius;
@end
and the implementation:
#import "CustomLayer.h"
@implementation CustomLayer
@dynamic circleRadius; // Linked post tells us to let CA implement our accessors for us.
// Whether this is necessary or not is unclear to me and one
// commenter on the linked post claims success only when using
// @synthesize for the animatable property.
+ (BOOL)needsDisplayForKey:(NSString*)key {
// Let our layer know it has to redraw when circleRadius is changed
if ([key isEqualToString:@"circleRadius"]) {
return YES;
} else {
return [super needsDisplayForKey:key];
}
}
- (void)drawInContext:(CGContextRef)ctx {
// This call is probably unnecessary as super's implementation does nothing
[super drawInContext:ctx];
CGRect rect = CGContextGetClipBoundingBox(ctx);
// Fill the circle with a light blue
CGContextSetRGBFillColor(ctx, 0, 0, 255, 0.1);
// Stoke a dark blue border
CGContextSetRGBStrokeColor(ctx, 0, 0, 255, 0.5);
// Construct a CGMutablePath to draw the light blue circle
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, rect.size.width / 2,
rect.size.height / 2,
self.circleRadius, 0, 2 * M_PI, NO);
// Fill the circle
CGContextAddPath(ctx, path);
CGContextFillPath(ctx);
// Stroke the circle's border
CGContextAddPath(ctx, path);
CGContextStrokePath(ctx);
// Release the path
CGPathRelease(path);
// Set a dark blue color for the small inner circle
CGContextSetRGBFillColor(ctx, 0, 0, 255, 1.0f);
// Draw the center dot
CGContextBeginPath (ctx);
CGContextAddArc(ctx, rect.size.width / 2,
rect.size.height / 2,
5, 0, 2 * M_PI, NO);
CGContextFillPath(ctx);
CGContextStrokePath(ctx);
}
@end
With this infrastructure in place, we can now animate the radius of the outer circle with ease b/c CoreAnimation will take care of the value interpolations as well as redraw calls. All we have to do his add an animation to the layer. As a simple proof of concept, I chose a simple CAKeyframeAnimation
to go through the 3 stage animation:
// In some controller class...
- (void)addLayerAndAnimate {
CustomLayer *customLayer = [[CustomLayer alloc] init];
// Make layer big enough for the initial radius
// EDIT: You may want to shrink the layer when it reacehes it's final size
[customLayer setFrame:CGRectMake(0, 0, 205, 205)];
[self.view.layer addSublayer:customLayer];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"circleRadius"];
// Zoom in, oscillate a couple times, zoom in further
animation.values = [NSArray arrayWithObjects:[NSNumber numberWithFloat:100],
[NSNumber numberWithFloat:45],
[NSNumber numberWithFloat:50],
[NSNumber numberWithFloat:45],
[NSNumber numberWithFloat:50],
[NSNumber numberWithFloat:45],
[NSNumber numberWithFloat:20],
nil];
// We want the radii to be 20 in the end
customLayer.circleRadius = 20;
// Rather arbitrary values. I thought the cubic pacing w/ a 2.5 second pacing
// looked decent enough but you'd probably want to play with them to get a more
// accurate imitation of the Maps app. You could also define a keyTimes array for
// a more discrete control of the times per step.
animation.duration = 2.5;
animation.calculationMode = kCAAnimationCubicPaced;
[customLayer addAnimation:animation forKey:nil];
}
The above is a rather "hacky" proof of concept as I am not sure of the specific way in which you intend to use this effect. For example, if you wanted to oscillate the circle until data was ready, the above wouldn't make a lot of sense because it will always oscillate twice.
Some closing notes:
MKMapView
, the above may require
some tweaking to integrate with MapKit.EDIT: Also, for performance reasons, you're probably going to want to make sure the layer is only as big as it needs to be. That is, after your done animating from the larger size, you may want to scale the size down so you're only using/drawing as much room as necessary. A nice way to do this would be just to find a way animate the bounds
(as opposed to circleRadius
) and perform this animation based the size interpolation but I've had some trouble implementing that (perhaps someone could add some insight on that subject).
Hope this helps, Sam
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With