From 4e9cf49a69fc55cda0df346ecc9f9bbd700b453f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=93=D0=B0=D0=BB?= =?UTF-8?q?=D0=B5=D0=B7=D0=B4=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 18 Mar 2014 23:30:21 +0400 Subject: [PATCH 1/2] OSX MapKit support --- .../SVPulsingAnnotationView.h | 14 +- .../SVPulsingAnnotationView.m | 432 ++++++++++-------- 2 files changed, 250 insertions(+), 196 deletions(-) mode change 100644 => 100755 SVPulsingAnnotationView/SVPulsingAnnotationView.h mode change 100644 => 100755 SVPulsingAnnotationView/SVPulsingAnnotationView.m diff --git a/SVPulsingAnnotationView/SVPulsingAnnotationView.h b/SVPulsingAnnotationView/SVPulsingAnnotationView.h old mode 100644 new mode 100755 index a2bcbdc..f937b77 --- a/SVPulsingAnnotationView/SVPulsingAnnotationView.h +++ b/SVPulsingAnnotationView/SVPulsingAnnotationView.h @@ -7,11 +7,19 @@ #import +#if TARGET_OS_IPHONE +#define SVColor UIColor +#define SVImage UIImage +#else +#define SVColor NSColor +#define SVImage NSImage +#endif + @interface SVPulsingAnnotationView : MKAnnotationView -@property (nonatomic, strong) UIColor *annotationColor; // default is same as MKUserLocationView -@property (nonatomic, strong) UIColor *pulseColor; // default is same as annotationColor -@property (nonatomic, strong) UIImage *image; // default is nil +@property (nonatomic, strong) SVColor *annotationColor; // default is same as MKUserLocationView +@property (nonatomic, strong) SVColor *pulseColor; // default is same as annotationColor +@property (nonatomic, strong) SVImage *image; // default is nil @property (nonatomic, readwrite) float pulseScaleFactor; // default is 5.3 @property (nonatomic, readwrite) NSTimeInterval pulseAnimationDuration; // default is 1s diff --git a/SVPulsingAnnotationView/SVPulsingAnnotationView.m b/SVPulsingAnnotationView/SVPulsingAnnotationView.m old mode 100644 new mode 100755 index 078d26c..3471d28 --- a/SVPulsingAnnotationView/SVPulsingAnnotationView.m +++ b/SVPulsingAnnotationView/SVPulsingAnnotationView.m @@ -8,11 +8,19 @@ #import "SVPulsingAnnotationView.h" #import +#if TARGET_OS_IPHONE +#define SVImageView UIImageView +#define SVView UIView +#else +#define SVImageView NSImageView +#define SVView NSView +#endif + @interface SVPulsingAnnotationView () @property (nonatomic, strong) CALayer *shinyDotLayer; @property (nonatomic, strong) CALayer *glowingHaloLayer; -@property (nonatomic, strong) UIImageView *imageView; +@property (nonatomic, strong) SVImageView *imageView; @property (nonatomic, strong) CALayer *whiteDotLayer; @property (nonatomic, strong) CALayer *colorDotLayer; @@ -38,12 +46,16 @@ - (id)initWithAnnotation:(id)annotation reuseIdentifier:(NSString if(self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]) { self.layer.anchorPoint = CGPointMake(0.5, 0.5); self.calloutOffset = CGPointMake(0, 4); +#if TARGET_OS_IPHONE self.bounds = CGRectMake(0, 0, 22, 22); +#else + self.frame = NSMakeRect(0, 0, 22, 22); +#endif self.pulseScaleFactor = 5.3; self.pulseAnimationDuration = 1.5; self.outerPulseAnimationDuration = 3; self.delayBetweenPulseCycles = 0; - self.annotationColor = [UIColor colorWithRed:0.000 green:0.478 blue:1.000 alpha:1]; + self.annotationColor = [SVColor colorWithRed:0.000 green:0.478 blue:1.000 alpha:1]; } return self; } @@ -74,215 +86,249 @@ - (void)rebuildLayers { [self.layer addSublayer:self.colorDotLayer]; } -- (void)willMoveToSuperview:(UIView *)newSuperview { - if(newSuperview) { - [self rebuildLayers]; - [self popIn]; +#if TARGET_OS_IPHONE +- (void)willMoveToSuperview:(SVView *)newSuperview { +#else + -(void)viewWillMoveToSuperview:(SVView *)newSuperview { +#endif + if(newSuperview) { + [self rebuildLayers]; + [self popIn]; + } } -} - -- (void)popIn { - CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"]; - CAMediaTimingFunction *easeInOut = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - bounceAnimation.values = @[@0.05, @1.25, @0.8, @1.1, @0.9, @1.0]; - bounceAnimation.duration = 0.3; - bounceAnimation.timingFunctions = @[easeInOut, easeInOut, easeInOut, easeInOut, easeInOut, easeInOut]; - [self.layer addAnimation:bounceAnimation forKey:@"popIn"]; -} - -#pragma mark - Setters - -- (void)setAnnotationColor:(UIColor *)annotationColor { - if(CGColorGetNumberOfComponents(annotationColor.CGColor) == 2) { - float white = CGColorGetComponents(annotationColor.CGColor)[0]; - float alpha = CGColorGetComponents(annotationColor.CGColor)[1]; - annotationColor = [UIColor colorWithRed:white green:white blue:white alpha:alpha]; + - (void)popIn { + CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"]; + CAMediaTimingFunction *easeInOut = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + + bounceAnimation.values = @[@0.05, @1.25, @0.8, @1.1, @0.9, @1.0]; + bounceAnimation.duration = 0.3; + bounceAnimation.timingFunctions = @[easeInOut, easeInOut, easeInOut, easeInOut, easeInOut, easeInOut]; + [self.layer addAnimation:bounceAnimation forKey:@"popIn"]; } - _annotationColor = annotationColor; - _imageView.tintColor = annotationColor; - - if(self.superview) - [self rebuildLayers]; -} - -- (void)setDelayBetweenPulseCycles:(NSTimeInterval)delayBetweenPulseCycles { - _delayBetweenPulseCycles = delayBetweenPulseCycles; - - if(self.superview) - [self rebuildLayers]; -} - -- (void)setPulseAnimationDuration:(NSTimeInterval)pulseAnimationDuration { - _pulseAnimationDuration = pulseAnimationDuration; - - if(self.superview) - [self rebuildLayers]; -} - -- (void)setImage:(UIImage *)image { - _image = image; +#pragma mark - Setters - if(self.superview) - [self rebuildLayers]; + - (void)setAnnotationColor:(SVColor *)annotationColor { + if(CGColorGetNumberOfComponents(annotationColor.CGColor) == 2) { + float white = CGColorGetComponents(annotationColor.CGColor)[0]; + float alpha = CGColorGetComponents(annotationColor.CGColor)[1]; + annotationColor = [SVColor colorWithRed:white green:white blue:white alpha:alpha]; + } + + _annotationColor = annotationColor; +#if TARGET_OS_IPHONE + _imageView.tintColor = annotationColor; +#endif + if(self.superview) + [self rebuildLayers]; + } - self.imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - self.imageView.bounds = CGRectMake(0, 0, ceil(image.size.width), ceil(image.size.height)); - self.imageView.center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); - self.imageView.tintColor = self.annotationColor; -} - -#pragma mark - Getters - -- (UIColor *)pulseColor { - if(!_pulseColor) - return self.annotationColor; - return _pulseColor; -} - -- (CAAnimationGroup*)pulseAnimationGroup { - if(!_pulseAnimationGroup) { - CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + - (void)setDelayBetweenPulseCycles:(NSTimeInterval)delayBetweenPulseCycles { + _delayBetweenPulseCycles = delayBetweenPulseCycles; - _pulseAnimationGroup = [CAAnimationGroup animation]; - _pulseAnimationGroup.duration = self.outerPulseAnimationDuration + self.delayBetweenPulseCycles; - _pulseAnimationGroup.repeatCount = INFINITY; - _pulseAnimationGroup.removedOnCompletion = NO; - _pulseAnimationGroup.timingFunction = defaultCurve; + if(self.superview) + [self rebuildLayers]; + } + + - (void)setPulseAnimationDuration:(NSTimeInterval)pulseAnimationDuration { + _pulseAnimationDuration = pulseAnimationDuration; - NSMutableArray *animations = [NSMutableArray new]; + if(self.superview) + [self rebuildLayers]; + } + + - (void)setImage:(SVImage *)image { + _image = image; - CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; - pulseAnimation.fromValue = @0.0; - pulseAnimation.toValue = @1.0; - pulseAnimation.duration = self.outerPulseAnimationDuration; - [animations addObject:pulseAnimation]; + if(self.superview) + [self rebuildLayers]; - CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; - animation.duration = self.outerPulseAnimationDuration; - animation.values = @[@0.45, @0.45, @0]; - animation.keyTimes = @[@0, @0.2, @1]; - animation.removedOnCompletion = NO; - [animations addObject:animation]; +#if TARGET_OS_IPHONE + self.imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.imageView.bounds = CGRectMake(0, 0, ceil(image.size.width), ceil(image.size.height)); - _pulseAnimationGroup.animations = animations; + self.imageView.center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); + self.imageView.tintColor = self.annotationColor; +#else + self.imageView.image = _image; +#endif } - return _pulseAnimationGroup; -} - -#pragma mark - Graphics - -- (UIImageView *)imageView { - if(!_imageView) { - _imageView = [[UIImageView alloc] initWithFrame:self.bounds]; - _imageView.contentMode = UIViewContentModeTopLeft; + +#pragma mark - Getters + + - (SVColor *)pulseColor { + if(!_pulseColor) + return self.annotationColor; + return _pulseColor; } - return _imageView; -} - -- (CALayer*)whiteDotLayer { - if(!_whiteDotLayer) { - _whiteDotLayer = [CALayer layer]; - _whiteDotLayer.bounds = self.bounds; - _whiteDotLayer.contents = (id)[self circleImageWithColor:[UIColor whiteColor] height:self.bounds.size.height].CGImage; - _whiteDotLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); - _whiteDotLayer.contentsGravity = kCAGravityCenter; - _whiteDotLayer.contentsScale = [UIScreen mainScreen].scale; - _whiteDotLayer.shadowColor = [UIColor blackColor].CGColor; - _whiteDotLayer.shadowOffset = CGSizeMake(0, 2); - _whiteDotLayer.shadowRadius = 3; - _whiteDotLayer.shadowOpacity = 0.3; - _whiteDotLayer.shouldRasterize = YES; - _whiteDotLayer.rasterizationScale = [UIScreen mainScreen].scale; + + - (CAAnimationGroup*)pulseAnimationGroup { + if(!_pulseAnimationGroup) { + CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + + _pulseAnimationGroup = [CAAnimationGroup animation]; + _pulseAnimationGroup.duration = self.outerPulseAnimationDuration + self.delayBetweenPulseCycles; + _pulseAnimationGroup.repeatCount = INFINITY; + _pulseAnimationGroup.removedOnCompletion = NO; + _pulseAnimationGroup.timingFunction = defaultCurve; + + NSMutableArray *animations = [NSMutableArray new]; + + CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; + pulseAnimation.fromValue = @0.0; + pulseAnimation.toValue = @1.0; + pulseAnimation.duration = self.outerPulseAnimationDuration; + [animations addObject:pulseAnimation]; + + CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; + animation.duration = self.outerPulseAnimationDuration; + animation.values = @[@0.45, @0.45, @0]; + animation.keyTimes = @[@0, @0.2, @1]; + animation.removedOnCompletion = NO; + [animations addObject:animation]; + + _pulseAnimationGroup.animations = animations; + } + return _pulseAnimationGroup; } - return _whiteDotLayer; -} - -- (CALayer*)colorDotLayer { - if(!_colorDotLayer) { - _colorDotLayer = [CALayer layer]; - CGFloat width = self.bounds.size.width-6; - _colorDotLayer.bounds = CGRectMake(0, 0, width, width); - _colorDotLayer.allowsGroupOpacity = YES; - _colorDotLayer.backgroundColor = self.annotationColor.CGColor; - _colorDotLayer.cornerRadius = width/2; - _colorDotLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { + +#pragma mark - Graphics + + - (SVImageView *)imageView { + if(!_imageView) { + _imageView = [[SVImageView alloc] initWithFrame:self.bounds]; +#if TARGET_OS_IPHONE + _imageView.contentMode = UIViewContentModeTopLeft; +#endif + } + return _imageView; + } + + - (CALayer*)whiteDotLayer { + if(!_whiteDotLayer) { + _whiteDotLayer = [CALayer layer]; + _whiteDotLayer.bounds = self.bounds; +#if TARGET_OS_IPHONE + _whiteDotLayer.contents = (id)[self circleImageWithColor:[SVColor whiteColor] height:self.bounds.size.height].CGImage; + _whiteDotLayer.contentsScale = [UIScreen mainScreen].scale; + _whiteDotLayer.rasterizationScale = [UIScreen mainScreen].scale; +#else + _whiteDotLayer.contents = [self circleImageWithColor:[SVColor whiteColor] height:self.bounds.size.height]; +#endif + _whiteDotLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); + _whiteDotLayer.contentsGravity = kCAGravityCenter; - if(self.delayBetweenPulseCycles != INFINITY) { - CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; - - CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; - animationGroup.duration = self.pulseAnimationDuration; - animationGroup.repeatCount = INFINITY; - animationGroup.removedOnCompletion = NO; - animationGroup.autoreverses = YES; - animationGroup.timingFunction = defaultCurve; - animationGroup.speed = 1; - animationGroup.fillMode = kCAFillModeBoth; - - CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; - pulseAnimation.fromValue = @0.8; - pulseAnimation.toValue = @1; - pulseAnimation.duration = self.pulseAnimationDuration; - - CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacityAnimation.fromValue = @0.8; - opacityAnimation.toValue = @1; - opacityAnimation.duration = self.pulseAnimationDuration; - - animationGroup.animations = @[pulseAnimation, opacityAnimation]; - - dispatch_async(dispatch_get_main_queue(), ^(void) { - [_colorDotLayer addAnimation:animationGroup forKey:@"pulse"]; - }); - } - }); - + _whiteDotLayer.shadowColor = [SVColor blackColor].CGColor; + _whiteDotLayer.shadowOffset = CGSizeMake(0, 2); + _whiteDotLayer.shadowRadius = 3; + _whiteDotLayer.shadowOpacity = 0.3; + _whiteDotLayer.shouldRasterize = YES; + } + return _whiteDotLayer; } - return _colorDotLayer; -} - -- (CALayer *)colorHaloLayer { - if(!_colorHaloLayer) { - _colorHaloLayer = [CALayer layer]; - CGFloat width = self.bounds.size.width*self.pulseScaleFactor; - _colorHaloLayer.bounds = CGRectMake(0, 0, width, width); - _colorHaloLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); - _colorHaloLayer.contentsScale = [UIScreen mainScreen].scale; - _colorHaloLayer.backgroundColor = self.pulseColor.CGColor; - _colorHaloLayer.cornerRadius = width/2; - _colorHaloLayer.opacity = 0; - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { - if(self.delayBetweenPulseCycles != INFINITY) { - CAAnimationGroup *animationGroup = self.pulseAnimationGroup; + + - (CALayer*)colorDotLayer { + if(!_colorDotLayer) { + _colorDotLayer = [CALayer layer]; + CGFloat width = self.bounds.size.width-6; + _colorDotLayer.bounds = CGRectMake(0, 0, width, width); + +#if TARGET_OS_IPHONE + _colorDotLayer.allowsGroupOpacity = YES; +#endif + + _colorDotLayer.backgroundColor = self.annotationColor.CGColor; + _colorDotLayer.cornerRadius = width/2; + _colorDotLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { - dispatch_async(dispatch_get_main_queue(), ^(void) { - [_colorHaloLayer addAnimation:animationGroup forKey:@"pulse"]; - }); - } - }); + if(self.delayBetweenPulseCycles != INFINITY) { + CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + + CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; + animationGroup.duration = self.pulseAnimationDuration; + animationGroup.repeatCount = INFINITY; + animationGroup.removedOnCompletion = NO; + animationGroup.autoreverses = YES; + animationGroup.timingFunction = defaultCurve; + animationGroup.speed = 1; + animationGroup.fillMode = kCAFillModeBoth; + + CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; + pulseAnimation.fromValue = @0.8; + pulseAnimation.toValue = @1; + pulseAnimation.duration = self.pulseAnimationDuration; + + CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.fromValue = @0.8; + opacityAnimation.toValue = @1; + opacityAnimation.duration = self.pulseAnimationDuration; + + animationGroup.animations = @[pulseAnimation, opacityAnimation]; + + dispatch_async(dispatch_get_main_queue(), ^(void) { + [_colorDotLayer addAnimation:animationGroup forKey:@"pulse"]; + }); + } + }); + + } + return _colorDotLayer; } - return _colorHaloLayer; -} - -- (UIImage*)circleImageWithColor:(UIColor*)color height:(float)height { - UIGraphicsBeginImageContextWithOptions(CGSizeMake(height, height), NO, 0); - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - - UIBezierPath* fillPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, height, height)]; - [color setFill]; - [fillPath fill]; - UIImage *dotImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + - (CALayer *)colorHaloLayer { + if(!_colorHaloLayer) { + _colorHaloLayer = [CALayer layer]; + CGFloat width = self.bounds.size.width*self.pulseScaleFactor; + _colorHaloLayer.bounds = CGRectMake(0, 0, width, width); + _colorHaloLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); + +#if TARGET_OS_IPHONE + _colorHaloLayer.contentsScale = [UIScreen mainScreen].scale; +#endif + + _colorHaloLayer.backgroundColor = self.pulseColor.CGColor; + _colorHaloLayer.cornerRadius = width/2; + _colorHaloLayer.opacity = 0; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { + if(self.delayBetweenPulseCycles != INFINITY) { + CAAnimationGroup *animationGroup = self.pulseAnimationGroup; + + dispatch_async(dispatch_get_main_queue(), ^(void) { + [_colorHaloLayer addAnimation:animationGroup forKey:@"pulse"]; + }); + } + }); + } + return _colorHaloLayer; + } - CGColorSpaceRelease(colorSpace); + - (SVImage*)circleImageWithColor:(SVColor*)color height:(float)height { +#if TARGET_OS_IPHONE + UIGraphicsBeginImageContextWithOptions(CGSizeMake(height, height), NO, 0); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + UIBezierPath* fillPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, height, height)]; + [color setFill]; + [fillPath fill]; + + SVImage *dotImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + CGColorSpaceRelease(colorSpace); + + return dotImage; +#else + return [NSImage imageWithSize:NSMakeSize(height, height) flipped:NO drawingHandler:^BOOL(NSRect dstRect) { + NSBezierPath *fillPath = [NSBezierPath bezierPathWithOvalInRect:dstRect]; + [color setFill]; + [fillPath fill]; + return YES; + }]; +#endif + } - return dotImage; -} - -@end + @end From c890bff874b0ade1b979024eb6c37bfa2f28a868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=93=D0=B0=D0=BB?= =?UTF-8?q?=D0=B5=D0=B7=D0=B4=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 18 Mar 2014 23:33:45 +0400 Subject: [PATCH 2/2] removed extra indent --- .../SVPulsingAnnotationView.m | 416 +++++++++--------- 1 file changed, 208 insertions(+), 208 deletions(-) diff --git a/SVPulsingAnnotationView/SVPulsingAnnotationView.m b/SVPulsingAnnotationView/SVPulsingAnnotationView.m index 3471d28..20c6640 100755 --- a/SVPulsingAnnotationView/SVPulsingAnnotationView.m +++ b/SVPulsingAnnotationView/SVPulsingAnnotationView.m @@ -89,246 +89,246 @@ - (void)rebuildLayers { #if TARGET_OS_IPHONE - (void)willMoveToSuperview:(SVView *)newSuperview { #else - -(void)viewWillMoveToSuperview:(SVView *)newSuperview { +-(void)viewWillMoveToSuperview:(SVView *)newSuperview { #endif - if(newSuperview) { - [self rebuildLayers]; - [self popIn]; - } - } - - - (void)popIn { - CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"]; - CAMediaTimingFunction *easeInOut = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - - bounceAnimation.values = @[@0.05, @1.25, @0.8, @1.1, @0.9, @1.0]; - bounceAnimation.duration = 0.3; - bounceAnimation.timingFunctions = @[easeInOut, easeInOut, easeInOut, easeInOut, easeInOut, easeInOut]; - [self.layer addAnimation:bounceAnimation forKey:@"popIn"]; + if(newSuperview) { + [self rebuildLayers]; + [self popIn]; } +} + +- (void)popIn { + CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"]; + CAMediaTimingFunction *easeInOut = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + bounceAnimation.values = @[@0.05, @1.25, @0.8, @1.1, @0.9, @1.0]; + bounceAnimation.duration = 0.3; + bounceAnimation.timingFunctions = @[easeInOut, easeInOut, easeInOut, easeInOut, easeInOut, easeInOut]; + [self.layer addAnimation:bounceAnimation forKey:@"popIn"]; +} + #pragma mark - Setters + +- (void)setAnnotationColor:(SVColor *)annotationColor { + if(CGColorGetNumberOfComponents(annotationColor.CGColor) == 2) { + float white = CGColorGetComponents(annotationColor.CGColor)[0]; + float alpha = CGColorGetComponents(annotationColor.CGColor)[1]; + annotationColor = [SVColor colorWithRed:white green:white blue:white alpha:alpha]; + } - - (void)setAnnotationColor:(SVColor *)annotationColor { - if(CGColorGetNumberOfComponents(annotationColor.CGColor) == 2) { - float white = CGColorGetComponents(annotationColor.CGColor)[0]; - float alpha = CGColorGetComponents(annotationColor.CGColor)[1]; - annotationColor = [SVColor colorWithRed:white green:white blue:white alpha:alpha]; - } - - _annotationColor = annotationColor; + _annotationColor = annotationColor; #if TARGET_OS_IPHONE - _imageView.tintColor = annotationColor; + _imageView.tintColor = annotationColor; #endif - if(self.superview) - [self rebuildLayers]; - } + if(self.superview) + [self rebuildLayers]; +} + +- (void)setDelayBetweenPulseCycles:(NSTimeInterval)delayBetweenPulseCycles { + _delayBetweenPulseCycles = delayBetweenPulseCycles; - - (void)setDelayBetweenPulseCycles:(NSTimeInterval)delayBetweenPulseCycles { - _delayBetweenPulseCycles = delayBetweenPulseCycles; - - if(self.superview) - [self rebuildLayers]; - } + if(self.superview) + [self rebuildLayers]; +} + +- (void)setPulseAnimationDuration:(NSTimeInterval)pulseAnimationDuration { + _pulseAnimationDuration = pulseAnimationDuration; - - (void)setPulseAnimationDuration:(NSTimeInterval)pulseAnimationDuration { - _pulseAnimationDuration = pulseAnimationDuration; - - if(self.superview) - [self rebuildLayers]; - } + if(self.superview) + [self rebuildLayers]; +} + +- (void)setImage:(SVImage *)image { + _image = image; + + if(self.superview) + [self rebuildLayers]; - - (void)setImage:(SVImage *)image { - _image = image; - - if(self.superview) - [self rebuildLayers]; - #if TARGET_OS_IPHONE - self.imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - self.imageView.bounds = CGRectMake(0, 0, ceil(image.size.width), ceil(image.size.height)); - - self.imageView.center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); - self.imageView.tintColor = self.annotationColor; + self.imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.imageView.bounds = CGRectMake(0, 0, ceil(image.size.width), ceil(image.size.height)); + + self.imageView.center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); + self.imageView.tintColor = self.annotationColor; #else - self.imageView.image = _image; + self.imageView.image = _image; #endif - } - +} + #pragma mark - Getters - - - (SVColor *)pulseColor { - if(!_pulseColor) - return self.annotationColor; - return _pulseColor; - } - - - (CAAnimationGroup*)pulseAnimationGroup { - if(!_pulseAnimationGroup) { - CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; - - _pulseAnimationGroup = [CAAnimationGroup animation]; - _pulseAnimationGroup.duration = self.outerPulseAnimationDuration + self.delayBetweenPulseCycles; - _pulseAnimationGroup.repeatCount = INFINITY; - _pulseAnimationGroup.removedOnCompletion = NO; - _pulseAnimationGroup.timingFunction = defaultCurve; - - NSMutableArray *animations = [NSMutableArray new]; - - CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; - pulseAnimation.fromValue = @0.0; - pulseAnimation.toValue = @1.0; - pulseAnimation.duration = self.outerPulseAnimationDuration; - [animations addObject:pulseAnimation]; - - CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; - animation.duration = self.outerPulseAnimationDuration; - animation.values = @[@0.45, @0.45, @0]; - animation.keyTimes = @[@0, @0.2, @1]; - animation.removedOnCompletion = NO; - [animations addObject:animation]; - - _pulseAnimationGroup.animations = animations; - } - return _pulseAnimationGroup; + +- (SVColor *)pulseColor { + if(!_pulseColor) + return self.annotationColor; + return _pulseColor; +} + +- (CAAnimationGroup*)pulseAnimationGroup { + if(!_pulseAnimationGroup) { + CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + + _pulseAnimationGroup = [CAAnimationGroup animation]; + _pulseAnimationGroup.duration = self.outerPulseAnimationDuration + self.delayBetweenPulseCycles; + _pulseAnimationGroup.repeatCount = INFINITY; + _pulseAnimationGroup.removedOnCompletion = NO; + _pulseAnimationGroup.timingFunction = defaultCurve; + + NSMutableArray *animations = [NSMutableArray new]; + + CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; + pulseAnimation.fromValue = @0.0; + pulseAnimation.toValue = @1.0; + pulseAnimation.duration = self.outerPulseAnimationDuration; + [animations addObject:pulseAnimation]; + + CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; + animation.duration = self.outerPulseAnimationDuration; + animation.values = @[@0.45, @0.45, @0]; + animation.keyTimes = @[@0, @0.2, @1]; + animation.removedOnCompletion = NO; + [animations addObject:animation]; + + _pulseAnimationGroup.animations = animations; } - + return _pulseAnimationGroup; +} + #pragma mark - Graphics - - - (SVImageView *)imageView { - if(!_imageView) { - _imageView = [[SVImageView alloc] initWithFrame:self.bounds]; + +- (SVImageView *)imageView { + if(!_imageView) { + _imageView = [[SVImageView alloc] initWithFrame:self.bounds]; #if TARGET_OS_IPHONE - _imageView.contentMode = UIViewContentModeTopLeft; + _imageView.contentMode = UIViewContentModeTopLeft; #endif - } - return _imageView; } - - - (CALayer*)whiteDotLayer { - if(!_whiteDotLayer) { - _whiteDotLayer = [CALayer layer]; - _whiteDotLayer.bounds = self.bounds; + return _imageView; +} + +- (CALayer*)whiteDotLayer { + if(!_whiteDotLayer) { + _whiteDotLayer = [CALayer layer]; + _whiteDotLayer.bounds = self.bounds; #if TARGET_OS_IPHONE - _whiteDotLayer.contents = (id)[self circleImageWithColor:[SVColor whiteColor] height:self.bounds.size.height].CGImage; - _whiteDotLayer.contentsScale = [UIScreen mainScreen].scale; - _whiteDotLayer.rasterizationScale = [UIScreen mainScreen].scale; + _whiteDotLayer.contents = (id)[self circleImageWithColor:[SVColor whiteColor] height:self.bounds.size.height].CGImage; + _whiteDotLayer.contentsScale = [UIScreen mainScreen].scale; + _whiteDotLayer.rasterizationScale = [UIScreen mainScreen].scale; #else - _whiteDotLayer.contents = [self circleImageWithColor:[SVColor whiteColor] height:self.bounds.size.height]; + _whiteDotLayer.contents = [self circleImageWithColor:[SVColor whiteColor] height:self.bounds.size.height]; #endif - _whiteDotLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); - _whiteDotLayer.contentsGravity = kCAGravityCenter; - - _whiteDotLayer.shadowColor = [SVColor blackColor].CGColor; - _whiteDotLayer.shadowOffset = CGSizeMake(0, 2); - _whiteDotLayer.shadowRadius = 3; - _whiteDotLayer.shadowOpacity = 0.3; - _whiteDotLayer.shouldRasterize = YES; - } - return _whiteDotLayer; + _whiteDotLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); + _whiteDotLayer.contentsGravity = kCAGravityCenter; + + _whiteDotLayer.shadowColor = [SVColor blackColor].CGColor; + _whiteDotLayer.shadowOffset = CGSizeMake(0, 2); + _whiteDotLayer.shadowRadius = 3; + _whiteDotLayer.shadowOpacity = 0.3; + _whiteDotLayer.shouldRasterize = YES; } - - - (CALayer*)colorDotLayer { - if(!_colorDotLayer) { - _colorDotLayer = [CALayer layer]; - CGFloat width = self.bounds.size.width-6; - _colorDotLayer.bounds = CGRectMake(0, 0, width, width); - + return _whiteDotLayer; +} + +- (CALayer*)colorDotLayer { + if(!_colorDotLayer) { + _colorDotLayer = [CALayer layer]; + CGFloat width = self.bounds.size.width-6; + _colorDotLayer.bounds = CGRectMake(0, 0, width, width); + #if TARGET_OS_IPHONE - _colorDotLayer.allowsGroupOpacity = YES; + _colorDotLayer.allowsGroupOpacity = YES; #endif + + _colorDotLayer.backgroundColor = self.annotationColor.CGColor; + _colorDotLayer.cornerRadius = width/2; + _colorDotLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { - _colorDotLayer.backgroundColor = self.annotationColor.CGColor; - _colorDotLayer.cornerRadius = width/2; - _colorDotLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { + if(self.delayBetweenPulseCycles != INFINITY) { + CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; - if(self.delayBetweenPulseCycles != INFINITY) { - CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; - - CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; - animationGroup.duration = self.pulseAnimationDuration; - animationGroup.repeatCount = INFINITY; - animationGroup.removedOnCompletion = NO; - animationGroup.autoreverses = YES; - animationGroup.timingFunction = defaultCurve; - animationGroup.speed = 1; - animationGroup.fillMode = kCAFillModeBoth; - - CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; - pulseAnimation.fromValue = @0.8; - pulseAnimation.toValue = @1; - pulseAnimation.duration = self.pulseAnimationDuration; - - CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacityAnimation.fromValue = @0.8; - opacityAnimation.toValue = @1; - opacityAnimation.duration = self.pulseAnimationDuration; - - animationGroup.animations = @[pulseAnimation, opacityAnimation]; - - dispatch_async(dispatch_get_main_queue(), ^(void) { - [_colorDotLayer addAnimation:animationGroup forKey:@"pulse"]; - }); - } - }); - - } - return _colorDotLayer; + CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; + animationGroup.duration = self.pulseAnimationDuration; + animationGroup.repeatCount = INFINITY; + animationGroup.removedOnCompletion = NO; + animationGroup.autoreverses = YES; + animationGroup.timingFunction = defaultCurve; + animationGroup.speed = 1; + animationGroup.fillMode = kCAFillModeBoth; + + CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; + pulseAnimation.fromValue = @0.8; + pulseAnimation.toValue = @1; + pulseAnimation.duration = self.pulseAnimationDuration; + + CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.fromValue = @0.8; + opacityAnimation.toValue = @1; + opacityAnimation.duration = self.pulseAnimationDuration; + + animationGroup.animations = @[pulseAnimation, opacityAnimation]; + + dispatch_async(dispatch_get_main_queue(), ^(void) { + [_colorDotLayer addAnimation:animationGroup forKey:@"pulse"]; + }); + } + }); + } - - - (CALayer *)colorHaloLayer { - if(!_colorHaloLayer) { - _colorHaloLayer = [CALayer layer]; - CGFloat width = self.bounds.size.width*self.pulseScaleFactor; - _colorHaloLayer.bounds = CGRectMake(0, 0, width, width); - _colorHaloLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); - + return _colorDotLayer; +} + +- (CALayer *)colorHaloLayer { + if(!_colorHaloLayer) { + _colorHaloLayer = [CALayer layer]; + CGFloat width = self.bounds.size.width*self.pulseScaleFactor; + _colorHaloLayer.bounds = CGRectMake(0, 0, width, width); + _colorHaloLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); + #if TARGET_OS_IPHONE - _colorHaloLayer.contentsScale = [UIScreen mainScreen].scale; + _colorHaloLayer.contentsScale = [UIScreen mainScreen].scale; #endif - - _colorHaloLayer.backgroundColor = self.pulseColor.CGColor; - _colorHaloLayer.cornerRadius = width/2; - _colorHaloLayer.opacity = 0; - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { - if(self.delayBetweenPulseCycles != INFINITY) { - CAAnimationGroup *animationGroup = self.pulseAnimationGroup; - - dispatch_async(dispatch_get_main_queue(), ^(void) { - [_colorHaloLayer addAnimation:animationGroup forKey:@"pulse"]; - }); - } - }); - } - return _colorHaloLayer; + + _colorHaloLayer.backgroundColor = self.pulseColor.CGColor; + _colorHaloLayer.cornerRadius = width/2; + _colorHaloLayer.opacity = 0; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { + if(self.delayBetweenPulseCycles != INFINITY) { + CAAnimationGroup *animationGroup = self.pulseAnimationGroup; + + dispatch_async(dispatch_get_main_queue(), ^(void) { + [_colorHaloLayer addAnimation:animationGroup forKey:@"pulse"]; + }); + } + }); } - - - (SVImage*)circleImageWithColor:(SVColor*)color height:(float)height { + return _colorHaloLayer; +} + +- (SVImage*)circleImageWithColor:(SVColor*)color height:(float)height { #if TARGET_OS_IPHONE - UIGraphicsBeginImageContextWithOptions(CGSizeMake(height, height), NO, 0); - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - - UIBezierPath* fillPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, height, height)]; + UIGraphicsBeginImageContextWithOptions(CGSizeMake(height, height), NO, 0); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + UIBezierPath* fillPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, height, height)]; + [color setFill]; + [fillPath fill]; + + SVImage *dotImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + CGColorSpaceRelease(colorSpace); + + return dotImage; +#else + return [NSImage imageWithSize:NSMakeSize(height, height) flipped:NO drawingHandler:^BOOL(NSRect dstRect) { + NSBezierPath *fillPath = [NSBezierPath bezierPathWithOvalInRect:dstRect]; [color setFill]; [fillPath fill]; - - SVImage *dotImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - CGColorSpaceRelease(colorSpace); - - return dotImage; -#else - return [NSImage imageWithSize:NSMakeSize(height, height) flipped:NO drawingHandler:^BOOL(NSRect dstRect) { - NSBezierPath *fillPath = [NSBezierPath bezierPathWithOvalInRect:dstRect]; - [color setFill]; - [fillPath fill]; - return YES; - }]; + return YES; + }]; #endif - } - - @end +} + +@end