diff options
Diffstat (limited to 'platform/ios/MGLUserLocationAnnotationView.m')
-rw-r--r-- | platform/ios/MGLUserLocationAnnotationView.m | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m new file mode 100644 index 0000000000..18c98bf1d0 --- /dev/null +++ b/platform/ios/MGLUserLocationAnnotationView.m @@ -0,0 +1,197 @@ +#import "MGLUserLocationAnnotationView.h" + +#import "MGLUserLocation_Private.h" +#import "MGLAnnotation.h" +#import "MGLMapView.h" + +const CGFloat MGLTrackingDotRingWidth = 24.0; + +@interface MGLUserLocationAnnotationView () + +@property (nonatomic, readwrite) CALayer *haloLayer; + +@end + +#pragma mark - + +@implementation MGLUserLocationAnnotationView +{ + CALayer *_accuracyRingLayer; + CALayer *_dotBorderLayer; + CALayer *_dotLayer; +} + +- (instancetype)initInMapView:(MGLMapView *)mapView +{ + if (self = [super init]) + { + self.annotation = [[MGLUserLocation alloc] init]; + _mapView = mapView; + [self setupLayers]; + } + return self; +} + +- (void)setTintColor:(UIColor *)tintColor +{ + UIImage *trackingDotHaloImage = [self trackingDotHaloImage]; + _haloLayer.bounds = CGRectMake(0, 0, trackingDotHaloImage.size.width, trackingDotHaloImage.size.height); + _haloLayer.contents = (__bridge id)[trackingDotHaloImage CGImage]; + + UIImage *dotImage = [self dotImage]; + _dotLayer.bounds = CGRectMake(0, 0, dotImage.size.width, dotImage.size.height); + _dotLayer.contents = (__bridge id)[dotImage CGImage]; +} + +- (void)setupLayers +{ + if (CLLocationCoordinate2DIsValid(self.annotation.coordinate)) + { + if ( ! _accuracyRingLayer && self.annotation.location.horizontalAccuracy) + { + UIImage *accuracyRingImage = [self accuracyRingImage]; + _accuracyRingLayer = [CALayer layer]; + _haloLayer.bounds = CGRectMake(0, 0, accuracyRingImage.size.width, accuracyRingImage.size.height); + _haloLayer.contents = (__bridge id)[accuracyRingImage CGImage]; + _haloLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); + + [self.layer addSublayer:_accuracyRingLayer]; + } + + if ( ! _haloLayer) + { + UIImage *haloImage = [self trackingDotHaloImage]; + _haloLayer = [CALayer layer]; + _haloLayer.bounds = CGRectMake(0, 0, haloImage.size.width, haloImage.size.height); + _haloLayer.contents = (__bridge id)[haloImage CGImage]; + _haloLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); + + [CATransaction begin]; + + [CATransaction setAnimationDuration:3.5]; + [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]]; + + // scale out radially + // + CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; + boundsAnimation.repeatCount = MAXFLOAT; + boundsAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)]; + boundsAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.0, 2.0, 1.0)]; + boundsAnimation.removedOnCompletion = NO; + + [_haloLayer addAnimation:boundsAnimation forKey:@"animateScale"]; + + // go transparent as scaled out + // + CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.repeatCount = MAXFLOAT; + opacityAnimation.fromValue = [NSNumber numberWithFloat:1.0]; + opacityAnimation.toValue = [NSNumber numberWithFloat:-1.0]; + opacityAnimation.removedOnCompletion = NO; + + [_haloLayer addAnimation:opacityAnimation forKey:@"animateOpacity"]; + + [CATransaction commit]; + + [self.layer addSublayer:_haloLayer]; + } + + // white dot background with shadow + // + if ( ! _dotBorderLayer) + { + CGRect rect = CGRectMake(0, 0, MGLTrackingDotRingWidth * 1.25, MGLTrackingDotRingWidth * 1.25); + + UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGContextSetShadow(context, CGSizeMake(0, 0), MGLTrackingDotRingWidth / 4.0); + + CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]); + CGContextFillEllipseInRect(context, CGRectMake((rect.size.width - MGLTrackingDotRingWidth) / 2.0, (rect.size.height - MGLTrackingDotRingWidth) / 2.0, MGLTrackingDotRingWidth, MGLTrackingDotRingWidth)); + + UIImage *whiteBackground = UIGraphicsGetImageFromCurrentImageContext(); + + UIGraphicsEndImageContext(); + + _dotBorderLayer = [CALayer layer]; + _dotBorderLayer.bounds = CGRectMake(0, 0, whiteBackground.size.width, whiteBackground.size.height); + _dotBorderLayer.contents = (__bridge id)[whiteBackground CGImage]; + _dotBorderLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); + [self.layer addSublayer:_dotBorderLayer]; + } + + // pulsing, tinted dot sublayer + // + if ( ! _dotLayer) + { + UIImage *dotImage = [self dotImage]; + _dotLayer = [CALayer layer]; + _dotLayer.bounds = CGRectMake(0, 0, dotImage.size.width, dotImage.size.height); + _dotLayer.contents = (__bridge id)[dotImage CGImage]; + _dotLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); + + CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; + animation.repeatCount = MAXFLOAT; + animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)]; + animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.8, 0.8, 1.0)]; + animation.removedOnCompletion = NO; + animation.autoreverses = YES; + animation.duration = 1.5; + animation.beginTime = CACurrentMediaTime() + 1.0; + animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; + + [_dotLayer addAnimation:animation forKey:@"animateTransform"]; + + [self.layer addSublayer:_dotLayer]; + } + } +} + +- (UIImage *)accuracyRingImage +{ + CGFloat latRadians = self.annotation.coordinate.latitude * M_PI / 180.0f; + CGFloat pixelRadius = self.annotation.location.horizontalAccuracy / cos(latRadians) / [self.mapView metersPerPixelAtLatitude:self.annotation.coordinate.latitude]; + UIGraphicsBeginImageContextWithOptions(CGSizeMake(pixelRadius * 2, pixelRadius * 2), NO, [[UIScreen mainScreen] scale]); + + CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.7] CGColor]); + CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.15] CGColor]); + CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 2.0); + CGContextStrokeEllipseInRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, pixelRadius * 2, pixelRadius * 2)); + + UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return finalImage; +} + +- (UIImage *)trackingDotHaloImage +{ + UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, [[UIScreen mainScreen] scale]); + CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[_mapView.tintColor colorWithAlphaComponent:0.75] CGColor]); + CGContextFillEllipseInRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 100, 100)); + UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return finalImage; +} + +- (UIImage *)dotImage +{ + CGFloat tintedWidth = MGLTrackingDotRingWidth * 0.7; + + CGRect rect = CGRectMake(0, 0, tintedWidth, tintedWidth); + + UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGContextSetFillColorWithColor(context, [_mapView.tintColor CGColor]); + CGContextFillEllipseInRect(context, CGRectMake((rect.size.width - tintedWidth) / 2.0, (rect.size.height - tintedWidth) / 2.0, tintedWidth, tintedWidth)); + + UIImage *tintedForeground = UIGraphicsGetImageFromCurrentImageContext(); + + UIGraphicsEndImageContext(); + + return tintedForeground; +} + +@end |