From c6708442c98665df36eabf3b0b7ca7ba38dfbdb4 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Wed, 9 Aug 2017 15:43:33 -0400 Subject: [ios] Refactor user location heading indicator into its own class --- platform/ios/ios.xcodeproj/project.pbxproj | 12 +- .../ios/src/MGLFaux3DUserLocationAnnotationView.h | 7 +- .../ios/src/MGLFaux3DUserLocationAnnotationView.m | 133 +++------------------ platform/ios/src/MGLUserLocationHeadingBeamLayer.h | 10 ++ platform/ios/src/MGLUserLocationHeadingBeamLayer.m | 104 ++++++++++++++++ 5 files changed, 147 insertions(+), 119 deletions(-) create mode 100644 platform/ios/src/MGLUserLocationHeadingBeamLayer.h create mode 100644 platform/ios/src/MGLUserLocationHeadingBeamLayer.m (limited to 'platform/ios') diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 76b35be2de..6408eb9087 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -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 = ""; }; 9660916E1E5BBFDC00A9A03B /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 9660916F1E5BBFDE00A9A03B /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = ""; }; + 966FCF4A1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingBeamLayer.h; sourceTree = ""; }; + 966FCF4B1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLUserLocationHeadingBeamLayer.m; sourceTree = ""; }; 968F36B41E4D0FC6003A5522 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; 96E027241E57C76E004B8E66 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 96E027271E57C77A004B8E66 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; @@ -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 = ""; @@ -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 */, diff --git a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.h b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.h index c48dd6b27b..d5dae3a919 100644 --- a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.h +++ b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.h @@ -1,7 +1,12 @@ #import #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 - diff --git a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m index 5f67f24f4e..36c5292127 100644 --- a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m +++ b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m @@ -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]; - } - - // heading indicator accuracy mask (fan-shaped) - // - if ( ! _headingIndicatorMaskLayer && self.userLocation.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.userLocation.heading.headingAccuracy; + _oldHeadingAccuracy = headingAccuracy; } - else if (_oldHeadingAccuracy != self.userLocation.heading.headingAccuracy) + else if (_oldHeadingAccuracy != 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 diff --git a/platform/ios/src/MGLUserLocationHeadingBeamLayer.h b/platform/ios/src/MGLUserLocationHeadingBeamLayer.h new file mode 100644 index 0000000000..9a5d4a8f14 --- /dev/null +++ b/platform/ios/src/MGLUserLocationHeadingBeamLayer.h @@ -0,0 +1,10 @@ +#import +#import "MGLUserLocationAnnotationView.h" + +@interface MGLUserLocationHeadingBeamLayer : CALayer + +- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView; +- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy; +- (void)updateTintColor:(CGColorRef)color; + +@end diff --git a/platform/ios/src/MGLUserLocationHeadingBeamLayer.m b/platform/ios/src/MGLUserLocationHeadingBeamLayer.m new file mode 100644 index 0000000000..efe7e4db93 --- /dev/null +++ b/platform/ios/src/MGLUserLocationHeadingBeamLayer.m @@ -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 -- cgit v1.2.1