summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
authorJason Wray <jason@kulturny.com>2015-09-01 23:30:54 -0400
committerJustin R. Miller <incanus@codesorcery.net>2015-09-11 11:36:32 -0700
commitc8e80595d854c79f4c16ef4237f7a4ca9d18ddd9 (patch)
treea4b95e5781c7bae1de1bbaca5e147c90a94d1feb /platform
parent169f580519367105ca626f158964910295ee73c1 (diff)
downloadqtlocation-mapboxgl-c8e80595d854c79f4c16ef4237f7a4ca9d18ddd9.tar.gz
[iOS] Initial user location puck
Diffstat (limited to 'platform')
-rw-r--r--platform/ios/MGLUserLocationAnnotationView.m478
1 files changed, 295 insertions, 183 deletions
diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m
index 7733674e63..301e3c4b20 100644
--- a/platform/ios/MGLUserLocationAnnotationView.m
+++ b/platform/ios/MGLUserLocationAnnotationView.m
@@ -8,6 +8,9 @@
const CGFloat MGLUserLocationAnnotationDotSize = 22.0;
const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
+const CGFloat MGLUserLocationAnnotationPuckSize = 35.0;
+const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.6;
+
@interface MGLUserLocationAnnotationView ()
@property (nonatomic, readwrite) CALayer *haloLayer;
@@ -18,12 +21,17 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
@implementation MGLUserLocationAnnotationView
{
+ BOOL _puckModeActivated;
+
+ CALayer *_puckDot;
+ CAShapeLayer *_puckArrow;
+
CALayer *_headingIndicatorLayer;
CAShapeLayer *_headingIndicatorMaskLayer;
CALayer *_accuracyRingLayer;
CALayer *_dotBorderLayer;
CALayer *_dotLayer;
-
+
double _oldHeadingAccuracy;
CLLocationAccuracy _oldHorizontalAccuracy;
double _oldZoom;
@@ -37,7 +45,9 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
- (instancetype)initInMapView:(MGLMapView *)mapView
{
- if (self = [super initWithFrame:CGRectMake(0, 0, MGLUserLocationAnnotationDotSize, MGLUserLocationAnnotationDotSize)])
+ CGFloat frameSize = (mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse) ? MGLUserLocationAnnotationPuckSize : MGLUserLocationAnnotationDotSize;
+
+ if (self = [super initWithFrame:CGRectMake(0, 0, frameSize, frameSize)])
{
self.annotation = [[MGLUserLocation alloc] initWithMapView:mapView];
_mapView = mapView;
@@ -55,189 +65,291 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
- (void)setTintColor:(UIColor *)tintColor
{
- if (_accuracyRingLayer)
+ if (_puckModeActivated)
{
- _accuracyRingLayer.backgroundColor = [tintColor CGColor];
+ _puckArrow.fillColor = [tintColor CGColor];
+ }
+ else
+ {
+ if (_accuracyRingLayer)
+ {
+ _accuracyRingLayer.backgroundColor = [tintColor CGColor];
+ }
+
+ _haloLayer.backgroundColor = [tintColor CGColor];
+ _dotLayer.backgroundColor = [tintColor CGColor];
+
+ _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage];
}
-
- _haloLayer.backgroundColor = [tintColor CGColor];
- _dotLayer.backgroundColor = [tintColor CGColor];
-
- _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage];
}
- (void)setupLayers
{
if (CLLocationCoordinate2DIsValid(self.annotation.coordinate))
{
- // update heading indicator
- //
- if (_headingIndicatorLayer)
- {
- _headingIndicatorLayer.hidden = !(_mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading ||
- _mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse);
-
- if (_oldHeadingAccuracy != self.annotation.heading.headingAccuracy)
- {
- // recalculate the clipping mask based on updated accuracy
- _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath];
-
- _oldHeadingAccuracy = self.annotation.heading.headingAccuracy;
- }
- }
-
- // heading indicator (tinted, semi-circle)
- //
- if ( ! _headingIndicatorLayer && self.annotation.heading.headingAccuracy)
- {
- CGFloat headingIndicatorSize = MGLUserLocationAnnotationHaloSize;
-
- _headingIndicatorLayer = [CALayer layer];
- _headingIndicatorLayer.bounds = CGRectMake(0, 0, headingIndicatorSize, headingIndicatorSize);
- _headingIndicatorLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0);
- _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage];
- _headingIndicatorLayer.contentsGravity = kCAGravityBottom;
- _headingIndicatorLayer.contentsScale = [UIScreen mainScreen].scale;
- _headingIndicatorLayer.opacity = 0.4;
- _headingIndicatorLayer.shouldRasterize = YES;
- _headingIndicatorLayer.rasterizationScale = [UIScreen mainScreen].scale;
- _headingIndicatorLayer.drawsAsynchronously = YES;
-
- [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer];
- }
-
- // heading indicator accuracy mask (fan-shaped)
- //
- if ( ! _headingIndicatorMaskLayer && self.annotation.heading.headingAccuracy)
+ (_mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse) ? [self drawPuck] : [self drawDot];
+ }
+}
+
+- (void)drawPuck
+{
+ if ( ! _puckModeActivated)
+ {
+ self.layer.sublayers = nil;
+
+ _headingIndicatorLayer = nil;
+ _headingIndicatorMaskLayer = nil;
+ _accuracyRingLayer = nil;
+ _haloLayer = nil;
+ _dotBorderLayer = nil;
+ _dotLayer = nil;
+
+ _puckModeActivated = YES;
+ }
+
+ // background dot (white with black shadow)
+ //
+ if ( ! _puckDot)
+ {
+ _puckDot = [self circleLayerWithSize:MGLUserLocationAnnotationPuckSize];
+ _puckDot.backgroundColor = [[UIColor whiteColor] CGColor];
+ _puckDot.shadowColor = [[UIColor blackColor] CGColor];
+ _puckDot.shadowOffset = CGSizeMake(0, 1);
+ _puckDot.shadowRadius = 1;
+ _puckDot.shadowOpacity = 0.1;
+
+ [self.layer addSublayer:_puckDot];
+ }
+
+ // arrow
+ //
+ if ( ! _puckArrow)
+ {
+ _puckArrow = [CAShapeLayer layer];
+ _puckArrow.path = [[self puckArrow] CGPath];
+ _puckArrow.fillColor = [_mapView.tintColor CGColor];
+ _puckArrow.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationArrowSize, MGLUserLocationAnnotationArrowSize);
+ _puckArrow.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0);
+ _puckArrow.shouldRasterize = YES;
+ _puckArrow.rasterizationScale = [UIScreen mainScreen].scale;
+ _puckArrow.drawsAsynchronously = YES;
+
+ [self.layer addSublayer:_puckArrow];
+ }
+
+ if (self.mapView.pitch)
+ {
+ CATransform3D t = CATransform3DRotate(CATransform3DIdentity, MGLRadiansFromDegrees(self.mapView.pitch), 1.0, 0, 0);
+ self.layer.sublayerTransform = t;
+ }
+}
+
+CGFloat MGLRadiansFromDegrees(CLLocationDegrees degrees)
+{
+ return degrees * M_PI / 180;
+}
+
+CLLocationDegrees MGLDegreesFromRadians(CGFloat radians)
+{
+ return radians * 180 / M_PI;
+}
+
+- (UIBezierPath *)puckArrow
+{
+ CGFloat max = MGLUserLocationAnnotationArrowSize;
+
+ UIBezierPath *bezierPath = UIBezierPath.bezierPath;
+ [bezierPath moveToPoint: CGPointMake(max * 0.5, 0)];
+ [bezierPath addLineToPoint: CGPointMake(max * 0.1, max)];
+ [bezierPath addLineToPoint: CGPointMake(max * 0.5, max * 0.65)];
+ [bezierPath addLineToPoint: CGPointMake(max * 0.9, max)];
+ [bezierPath addLineToPoint: CGPointMake(max * 0.5, 0)];
+ [bezierPath closePath];
+
+ return bezierPath;
+}
+
+- (void)drawDot
+{
+ if (_puckModeActivated)
+ {
+ self.layer.sublayers = nil;
+
+ _puckDot = nil;
+ _puckArrow = nil;
+
+ _puckModeActivated = NO;
+
+ self.layer.sublayerTransform = CATransform3DIdentity;
+ }
+
+ // update heading indicator
+ //
+ if (_headingIndicatorLayer)
+ {
+ _headingIndicatorLayer.hidden = !(_mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading ||
+ _mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse);
+
+ if (_oldHeadingAccuracy != self.annotation.heading.headingAccuracy)
{
- _headingIndicatorMaskLayer = [CAShapeLayer layer];
- _headingIndicatorMaskLayer.frame = _headingIndicatorLayer.bounds;
+ // recalculate the clipping mask based on updated accuracy
_headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath];
- // apply the mask to the halo-radius-sized gradient layer
- _headingIndicatorLayer.mask = _headingIndicatorMaskLayer;
-
_oldHeadingAccuracy = self.annotation.heading.headingAccuracy;
}
+ }
+
+ // heading indicator (tinted, semi-circle)
+ //
+ if ( ! _headingIndicatorLayer && self.annotation.heading.headingAccuracy)
+ {
+ CGFloat headingIndicatorSize = MGLUserLocationAnnotationHaloSize;
+
+ _headingIndicatorLayer = [CALayer layer];
+ _headingIndicatorLayer.bounds = CGRectMake(0, 0, headingIndicatorSize, headingIndicatorSize);
+ _headingIndicatorLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0);
+ _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage];
+ _headingIndicatorLayer.contentsGravity = kCAGravityBottom;
+ _headingIndicatorLayer.contentsScale = [UIScreen mainScreen].scale;
+ _headingIndicatorLayer.opacity = 0.4;
+ _headingIndicatorLayer.shouldRasterize = YES;
+ _headingIndicatorLayer.rasterizationScale = [UIScreen mainScreen].scale;
+ _headingIndicatorLayer.drawsAsynchronously = YES;
+
+ [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer];
+ }
+
+ // heading indicator accuracy mask (fan-shaped)
+ //
+ if ( ! _headingIndicatorMaskLayer && self.annotation.heading.headingAccuracy)
+ {
+ _headingIndicatorMaskLayer = [CAShapeLayer layer];
+ _headingIndicatorMaskLayer.frame = _headingIndicatorLayer.bounds;
+ _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath];
+
+ // apply the mask to the halo-radius-sized gradient layer
+ _headingIndicatorLayer.mask = _headingIndicatorMaskLayer;
+
+ _oldHeadingAccuracy = self.annotation.heading.headingAccuracy;
+ }
+
+ // update accuracy ring (if zoom or horizontal accuracy have changed)
+ //
+ if (_accuracyRingLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.annotation.location.horizontalAccuracy))
+ {
+ CGFloat accuracyRingSize = [self calculateAccuracyRingSize];
- // update accuracy ring (if zoom or horizontal accuracy have changed)
- //
- if (_accuracyRingLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.annotation.location.horizontalAccuracy))
- {
- CGFloat accuracyRingSize = [self calculateAccuracyRingSize];
-
- // only show the accuracy ring if it won't be obscured by the location dot
- if (accuracyRingSize > MGLUserLocationAnnotationDotSize + 15)
- {
- _accuracyRingLayer.hidden = NO;
- _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize);
- _accuracyRingLayer.cornerRadius = accuracyRingSize / 2;
-
- // match the halo to the accuracy ring
- _haloLayer.bounds = _accuracyRingLayer.bounds;
- _haloLayer.cornerRadius = _accuracyRingLayer.cornerRadius;
- _haloLayer.shouldRasterize = NO;
- }
- else
- {
- _accuracyRingLayer.hidden = YES;
-
- _haloLayer.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize);
- _haloLayer.cornerRadius = MGLUserLocationAnnotationHaloSize / 2.0;
- _haloLayer.shouldRasterize = YES;
- _haloLayer.rasterizationScale = [UIScreen mainScreen].scale;
- }
-
- // store accuracy and zoom so we're not redrawing unchanged location updates
- _oldHorizontalAccuracy = self.annotation.location.horizontalAccuracy;
- _oldZoom = self.mapView.zoomLevel;
- }
-
- // accuracy ring (circular, tinted, mostly-transparent)
- //
- if ( ! _accuracyRingLayer && self.annotation.location.horizontalAccuracy)
+ // only show the accuracy ring if it won't be obscured by the location dot
+ if (accuracyRingSize > MGLUserLocationAnnotationDotSize + 15)
{
- CGFloat accuracyRingSize = [self calculateAccuracyRingSize];
- _accuracyRingLayer = [self circleLayerWithSize:accuracyRingSize];
- _accuracyRingLayer.backgroundColor = [_mapView.tintColor CGColor];
- _accuracyRingLayer.opacity = 0.1;
- _accuracyRingLayer.shouldRasterize = NO;
- _accuracyRingLayer.allowsGroupOpacity = NO;
-
- [self.layer addSublayer:_accuracyRingLayer];
+ _accuracyRingLayer.hidden = NO;
+ _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize);
+ _accuracyRingLayer.cornerRadius = accuracyRingSize / 2;
+
+ // match the halo to the accuracy ring
+ _haloLayer.bounds = _accuracyRingLayer.bounds;
+ _haloLayer.cornerRadius = _accuracyRingLayer.cornerRadius;
+ _haloLayer.shouldRasterize = NO;
}
-
- // expanding sonar-like pulse (circular, tinted, fades out)
- //
- if ( ! _haloLayer)
+ else
{
- _haloLayer = [self circleLayerWithSize:MGLUserLocationAnnotationHaloSize];
- _haloLayer.backgroundColor = [_mapView.tintColor CGColor];
- _haloLayer.allowsGroupOpacity = NO;
-
- // set defaults for the animations
- CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0];
-
- // scale out radially with initial acceleration
- CAKeyframeAnimation *boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"];
- boundsAnimation.values = @[@0, @0.35, @1];
- boundsAnimation.keyTimes = @[@0, @0.2, @1];
-
- // go transparent as scaled out, start semi-opaque
- CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
- opacityAnimation.values = @[@0.4, @0.4, @0];
- opacityAnimation.keyTimes = @[@0, @0.2, @1];
-
- animationGroup.animations = @[boundsAnimation, opacityAnimation];
-
- [_haloLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"];
-
- [self.layer addSublayer:_haloLayer];
+ _accuracyRingLayer.hidden = YES;
+
+ _haloLayer.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize);
+ _haloLayer.cornerRadius = MGLUserLocationAnnotationHaloSize / 2.0;
+ _haloLayer.shouldRasterize = YES;
+ _haloLayer.rasterizationScale = [UIScreen mainScreen].scale;
}
+
+ // store accuracy and zoom so we're not redrawing unchanged location updates
+ _oldHorizontalAccuracy = self.annotation.location.horizontalAccuracy;
+ _oldZoom = self.mapView.zoomLevel;
+ }
+
+ // accuracy ring (circular, tinted, mostly-transparent)
+ //
+ if ( ! _accuracyRingLayer && self.annotation.location.horizontalAccuracy)
+ {
+ CGFloat accuracyRingSize = [self calculateAccuracyRingSize];
+ _accuracyRingLayer = [self circleLayerWithSize:accuracyRingSize];
+ _accuracyRingLayer.backgroundColor = [_mapView.tintColor CGColor];
+ _accuracyRingLayer.opacity = 0.1;
+ _accuracyRingLayer.shouldRasterize = NO;
+ _accuracyRingLayer.allowsGroupOpacity = NO;
- // background dot (white with black shadow)
- //
- if ( ! _dotBorderLayer)
- {
- _dotBorderLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize];
- _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor];
- _dotBorderLayer.shadowColor = [[UIColor blackColor] CGColor];
- _dotBorderLayer.shadowOffset = CGSizeMake(0, 0);
- _dotBorderLayer.shadowRadius = 3;
- _dotBorderLayer.shadowOpacity = 0.25;
-
- [self.layer addSublayer:_dotBorderLayer];
- }
+ [self.layer addSublayer:_accuracyRingLayer];
+ }
+
+ // expanding sonar-like pulse (circular, tinted, fades out)
+ //
+ if ( ! _haloLayer)
+ {
+ _haloLayer = [self circleLayerWithSize:MGLUserLocationAnnotationHaloSize];
+ _haloLayer.backgroundColor = [_mapView.tintColor CGColor];
+ _haloLayer.allowsGroupOpacity = NO;
+
+ // set defaults for the animations
+ CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0];
+
+ // scale out radially with initial acceleration
+ CAKeyframeAnimation *boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"];
+ boundsAnimation.values = @[@0, @0.35, @1];
+ boundsAnimation.keyTimes = @[@0, @0.2, @1];
+
+ // go transparent as scaled out, start semi-opaque
+ CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
+ opacityAnimation.values = @[@0.4, @0.4, @0];
+ opacityAnimation.keyTimes = @[@0, @0.2, @1];
+
+ animationGroup.animations = @[boundsAnimation, opacityAnimation];
+
+ [_haloLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"];
+
+ [self.layer addSublayer:_haloLayer];
+ }
+
+ // background dot (white with black shadow)
+ //
+ if ( ! _dotBorderLayer)
+ {
+ _dotBorderLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize];
+ _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor];
+ _dotBorderLayer.shadowColor = [[UIColor blackColor] CGColor];
+ _dotBorderLayer.shadowOffset = CGSizeMake(0, 0);
+ _dotBorderLayer.shadowRadius = 3;
+ _dotBorderLayer.shadowOpacity = 0.25;
- // inner dot (pulsing, tinted)
- //
- if ( ! _dotLayer)
- {
- _dotLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize * 0.75];
- _dotLayer.backgroundColor = [_mapView.tintColor CGColor];
- _dotLayer.shouldRasterize = NO;
-
- // set defaults for the animations
- CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5];
- animationGroup.autoreverses = YES;
- animationGroup.fillMode = kCAFillModeBoth;
-
- // scale the dot up and down
- CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"];
- pulseAnimation.fromValue = @0.8;
- pulseAnimation.toValue = @1;
-
- // fade opacity in and out, subtly
- CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
- opacityAnimation.fromValue = @0.8;
- opacityAnimation.toValue = @1;
-
- animationGroup.animations = @[pulseAnimation, opacityAnimation];
-
- [_dotLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"];
-
- [self.layer addSublayer:_dotLayer];
- }
+ [self.layer addSublayer:_dotBorderLayer];
+ }
+
+ // inner dot (pulsing, tinted)
+ //
+ if ( ! _dotLayer)
+ {
+ _dotLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize * 0.75];
+ _dotLayer.backgroundColor = [_mapView.tintColor CGColor];
+ _dotLayer.shouldRasterize = NO;
+
+ // set defaults for the animations
+ CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5];
+ animationGroup.autoreverses = YES;
+ animationGroup.fillMode = kCAFillModeBoth;
+
+ // scale the dot up and down
+ CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"];
+ pulseAnimation.fromValue = @0.8;
+ pulseAnimation.toValue = @1;
+
+ // fade opacity in and out, subtly
+ CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
+ opacityAnimation.fromValue = @0.8;
+ opacityAnimation.toValue = @1;
+
+ animationGroup.animations = @[pulseAnimation, opacityAnimation];
+
+ [_dotLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"];
+
+ [self.layer addSublayer:_dotLayer];
}
}
@@ -250,7 +362,7 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
circleLayer.shouldRasterize = YES;
circleLayer.rasterizationScale = [UIScreen mainScreen].scale;
circleLayer.drawsAsynchronously = YES;
-
+
return circleLayer;
}
@@ -261,7 +373,7 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
animationGroup.repeatCount = INFINITY;
animationGroup.removedOnCompletion = NO;
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
-
+
return animationGroup;
}
@@ -269,65 +381,65 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
{
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];
-
+
return pixelRadius * 2;
}
- (UIImage *)headingIndicatorTintedGradientImage
{
UIImage *image;
-
+
CGFloat haloRadius = MGLUserLocationAnnotationHaloSize / 2.0;
-
+
UIGraphicsBeginImageContextWithOptions(CGSizeMake(MGLUserLocationAnnotationHaloSize, haloRadius), NO, 0);
-
+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
-
+
// gradient from the tint color to no-alpha tint color
CGFloat gradientLocations[] = {0.0, 1.0};
CGGradientRef gradient = CGGradientCreateWithColors(
- colorSpace, (__bridge CFArrayRef)@[(id)[_mapView.tintColor CGColor],
- (id)[[_mapView.tintColor colorWithAlphaComponent:0] CGColor]], gradientLocations);
-
+ colorSpace, (__bridge CFArrayRef)@[(id)[_mapView.tintColor CGColor],
+ (id)[[_mapView.tintColor colorWithAlphaComponent:0] CGColor]], gradientLocations);
+
// draw the gradient from the center point to the edge (full halo radius)
CGPoint centerPoint = CGPointMake(haloRadius, haloRadius);
CGContextDrawRadialGradient(context, gradient,
centerPoint, 0.0,
centerPoint, haloRadius,
nil);
-
+
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
-
+
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
-
+
return image;
}
- (UIBezierPath *)headingIndicatorClippingMask
{
CGFloat accuracy = self.annotation.heading.headingAccuracy;
-
+
// size the mask using exagerated accuracy, but keep within a good display range
CGFloat clippingDegrees = 90 - (accuracy * 1.5);
clippingDegrees = fmin(clippingDegrees, 55);
clippingDegrees = fmax(clippingDegrees, 10);
-
+
CGRect ovalRect = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize);
UIBezierPath *ovalPath = UIBezierPath.bezierPath;
-
+
// clip the oval to ± incoming accuracy degrees (converted to radians), from the top
[ovalPath addArcWithCenter:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))
radius:CGRectGetWidth(ovalRect) / 2.0
startAngle:(-180 + clippingDegrees) * M_PI / 180
endAngle:-clippingDegrees * M_PI / 180
clockwise:YES];
-
+
[ovalPath addLineToPoint:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))];
[ovalPath closePath];
-
+
return ovalPath;
}