[ios] Refactor user location heading indicator into its own class

This commit is contained in:
Jason Wray
2017-08-09 15:43:33 -04:00
parent cb7eb5cafb
commit c6708442c9
5 changed files with 148 additions and 120 deletions

View File

@@ -216,6 +216,9 @@
9620BB391E69FE1700705A1D /* MGLSDKUpdateChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = 9620BB361E69FE1700705A1D /* MGLSDKUpdateChecker.h */; };
9620BB3A1E69FE1700705A1D /* MGLSDKUpdateChecker.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9620BB371E69FE1700705A1D /* MGLSDKUpdateChecker.mm */; };
9620BB3B1E69FE1700705A1D /* MGLSDKUpdateChecker.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9620BB371E69FE1700705A1D /* MGLSDKUpdateChecker.mm */; };
966FCF4C1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 966FCF4A1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h */; };
966FCF4E1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 966FCF4B1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m */; };
966FCF4F1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 966FCF4B1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m */; };
968F36B51E4D128D003A5522 /* MGLDistanceFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3557F7AE1E1D27D300CCA5E6 /* MGLDistanceFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; };
96E027231E57C76E004B8E66 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E027251E57C76E004B8E66 /* Localizable.strings */; };
DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -686,6 +689,8 @@
9660916D1E5BBFDB00A9A03B /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
9660916E1E5BBFDC00A9A03B /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
9660916F1E5BBFDE00A9A03B /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = "<group>"; };
966FCF4A1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingBeamLayer.h; sourceTree = "<group>"; };
966FCF4B1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLUserLocationHeadingBeamLayer.m; sourceTree = "<group>"; };
968F36B41E4D0FC6003A5522 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
96E027241E57C76E004B8E66 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
96E027271E57C77A004B8E66 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -1588,8 +1593,8 @@
DAD165841CF4D06B001FF4B9 /* Annotations */ = {
isa = PBXGroup;
children = (
40EDA1BD1CFE0D4A00D9EA68 /* MGLAnnotationContainerView.h */,
404326881D5B9B1A007111BD /* MGLAnnotationContainerView_Private.h */,
40EDA1BD1CFE0D4A00D9EA68 /* MGLAnnotationContainerView.h */,
40EDA1BE1CFE0D4A00D9EA68 /* MGLAnnotationContainerView.m */,
DA8848401CBAFB9800AB86E3 /* MGLAnnotationImage_Private.h */,
DA8848341CBAFB8500AB86E3 /* MGLAnnotationImage.h */,
@@ -1608,6 +1613,8 @@
359F57451D2FDBD5005217F1 /* MGLUserLocationAnnotationView_Private.h */,
354B83941D2E873E005D9406 /* MGLUserLocationAnnotationView.h */,
354B83951D2E873E005D9406 /* MGLUserLocationAnnotationView.m */,
966FCF4A1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h */,
966FCF4B1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m */,
);
name = Annotations;
sourceTree = "<group>";
@@ -1696,6 +1703,7 @@
408AA8571DAEDA1700022900 /* NSDictionary+MGLAdditions.h in Headers */,
DA88483F1CBAFB8500AB86E3 /* MGLUserLocation.h in Headers */,
558DE7A01E5615E400C7916D /* MGLFoundation_Private.h in Headers */,
966FCF4C1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h in Headers */,
DA88483D1CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h in Headers */,
DA17BE301CC4BAC300402C41 /* MGLMapView_Private.h in Headers */,
DAD165781CF4CDFF001FF4B9 /* MGLShapeCollection.h in Headers */,
@@ -2266,6 +2274,7 @@
35B82BFA1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */,
7E016D861D9E890300A29A21 /* MGLPolygon+MGLAdditions.m in Sources */,
DA8848521CBAFB9800AB86E3 /* MGLAPIClient.m in Sources */,
966FCF4E1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */,
DA8848301CBAFA6200AB86E3 /* NSProcessInfo+MGLAdditions.m in Sources */,
353AFA161D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */,
35D13AC51D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */,
@@ -2349,6 +2358,7 @@
35B82BFB1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */,
7E016D871D9E890300A29A21 /* MGLPolygon+MGLAdditions.m in Sources */,
DAA4E4311CBB730400178DFB /* MGLMapboxEvents.m in Sources */,
966FCF4F1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */,
DAA4E4231CBB730400178DFB /* MGLPolygon.mm in Sources */,
353AFA171D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */,
35D13AC61D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */,

View File

@@ -1,7 +1,12 @@
#import <UIKit/UIKit.h>
#import "MGLUserLocationAnnotationView.h"
const CGFloat MGLUserLocationAnnotationDotSize = 22.0;
const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
const CGFloat MGLUserLocationAnnotationPuckSize = 45.0;
const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.6;
@interface MGLFaux3DUserLocationAnnotationView : MGLUserLocationAnnotationView
@end

View File

@@ -2,14 +2,7 @@
#import "MGLMapView.h"
#import "MGLUserLocation.h"
const CGFloat MGLUserLocationAnnotationDotSize = 22.0;
const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
const CGFloat MGLUserLocationAnnotationPuckSize = 45.0;
const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.6;
#pragma mark -
#import "MGLUserLocationHeadingBeamLayer.h"
@implementation MGLFaux3DUserLocationAnnotationView
{
@@ -18,8 +11,7 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
CALayer *_puckDot;
CAShapeLayer *_puckArrow;
CALayer *_headingIndicatorLayer;
CAShapeLayer *_headingIndicatorMaskLayer;
MGLUserLocationHeadingBeamLayer *_headingIndicatorLayer;
CALayer *_accuracyRingLayer;
CALayer *_dotBorderLayer;
CALayer *_dotLayer;
@@ -56,21 +48,18 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
- (void)setTintColor:(UIColor *)tintColor
{
CGColorRef newTintColor = [tintColor CGColor];
if (_puckModeActivated)
{
_puckArrow.fillColor = [tintColor CGColor];
_puckArrow.fillColor = newTintColor;
}
else
{
if (_accuracyRingLayer)
{
_accuracyRingLayer.backgroundColor = [tintColor CGColor];
}
_haloLayer.backgroundColor = [tintColor CGColor];
_dotLayer.backgroundColor = [tintColor CGColor];
_headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage];
_accuracyRingLayer.backgroundColor = newTintColor;
_haloLayer.backgroundColor = newTintColor;
_dotLayer.backgroundColor = newTintColor;
[_headingIndicatorLayer updateTintColor:newTintColor];
}
}
@@ -138,7 +127,6 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
self.layer.sublayers = nil;
_headingIndicatorLayer = nil;
_headingIndicatorMaskLayer = nil;
_accuracyRingLayer = nil;
_haloLayer = nil;
_dotBorderLayer = nil;
@@ -232,47 +220,21 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
if (showHeadingIndicator)
{
_headingIndicatorLayer.hidden = NO;
CLLocationDirection headingAccuracy = self.userLocation.heading.headingAccuracy;
// heading indicator (tinted, semi-circle)
//
if ( ! _headingIndicatorLayer && self.userLocation.heading.headingAccuracy)
if ( ! _headingIndicatorLayer && 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;
_headingIndicatorLayer = [[MGLUserLocationHeadingBeamLayer alloc] initWithUserLocationAnnotationView:self];
[self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer];
_oldHeadingAccuracy = headingAccuracy;
}
// heading indicator accuracy mask (fan-shaped)
//
if ( ! _headingIndicatorMaskLayer && self.userLocation.heading.headingAccuracy)
else if (_oldHeadingAccuracy != 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.userLocation.heading.headingAccuracy;
}
else if (_oldHeadingAccuracy != self.userLocation.heading.headingAccuracy)
{
// recalculate the clipping mask based on updated accuracy
_headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath];
_oldHeadingAccuracy = self.userLocation.heading.headingAccuracy;
[_headingIndicatorLayer updateHeadingAccuracy:headingAccuracy];
_oldHeadingAccuracy = headingAccuracy;
}
if (self.userLocation.heading.trueHeading >= 0)
@@ -283,9 +245,7 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
else
{
[_headingIndicatorLayer removeFromSuperlayer];
[_headingIndicatorMaskLayer removeFromSuperlayer];
_headingIndicatorLayer = nil;
_headingIndicatorMaskLayer = nil;
}
@@ -464,65 +424,4 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
return self.userLocation.location.horizontalAccuracy / [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude] * 2.0;
}
- (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)[self.mapView.tintColor CGColor],
(id)[[self.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,
kNilOptions);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
return image;
}
- (UIBezierPath *)headingIndicatorClippingMask
{
CGFloat accuracy = self.userLocation.heading.headingAccuracy;
// 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;
}
@end

View File

@@ -0,0 +1,10 @@
#import <QuartzCore/QuartzCore.h>
#import "MGLUserLocationAnnotationView.h"
@interface MGLUserLocationHeadingBeamLayer : CALayer
- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView;
- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy;
- (void)updateTintColor:(CGColorRef)color;
@end

View File

@@ -0,0 +1,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