1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
#import "MGLUserLocationHeadingBeamLayer.h"
#import "MGLFaux3DUserLocationAnnotationView.h"
#import "MGLGeometry.h"
@implementation MGLUserLocationHeadingBeamLayer
{
CAShapeLayer *_maskLayer;
}
- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView
{
CGFloat size = MGLUserLocationAnnotationHaloSize;
self = [super init];
self.bounds = CGRectMake(0, 0, size, size);
self.position = CGPointMake(CGRectGetMidX(userLocationView.bounds), CGRectGetMidY(userLocationView.bounds));
self.contents = (__bridge id)[self gradientImageWithTintColor:userLocationView.tintColor.CGColor];
self.contentsGravity = kCAGravityBottom;
self.contentsScale = UIScreen.mainScreen.scale;
self.opacity = 0.4;
self.shouldRasterize = YES;
self.rasterizationScale = UIScreen.mainScreen.scale;
self.drawsAsynchronously = YES;
_maskLayer = [CAShapeLayer layer];
_maskLayer.frame = self.bounds;
_maskLayer.path = [self clippingMaskForAccuracy:0];
self.mask = _maskLayer;
return self;
}
- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy
{
// recalculate the clipping mask based on updated accuracy
_maskLayer.path = [self clippingMaskForAccuracy:accuracy];
}
- (void)updateTintColor:(CGColorRef)color
{
// redraw the raw tinted gradient
self.contents = (__bridge id)[self gradientImageWithTintColor:color];
}
- (CGImageRef)gradientImageWithTintColor:(CGColorRef)tintColor
{
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)@[(__bridge id)tintColor,
(id)CFBridgingRelease(CGColorCreateCopyWithAlpha(tintColor, 0))],
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,
kNilOptions);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
return image.CGImage;
}
- (CGPathRef)clippingMaskForAccuracy:(CGFloat)accuracy
{
// size the mask using accuracy, but keep within a good display range
CGFloat clippingDegrees = 90 - accuracy;
clippingDegrees = fmin(clippingDegrees, 70); // most accurate
clippingDegrees = fmax(clippingDegrees, 10); // least accurate
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:MGLRadiansFromDegrees(-180 + clippingDegrees)
endAngle:MGLRadiansFromDegrees(-clippingDegrees)
clockwise:YES];
[ovalPath addLineToPoint:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))];
[ovalPath closePath];
return ovalPath.CGPath;
}
@end
|