diff options
author | Fredrik Karlsson <bjorn.fredrik.karlsson@gmail.com> | 2017-04-16 17:24:17 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-04-16 17:24:17 +0200 |
commit | 8eb23cbac8a6c5821dc5935e27689700216c3f1a (patch) | |
tree | d02fd3667c8e19daf3724384f4956f16ec0bf01c | |
parent | fcf5d1b8af5ca01f8634107f00fbc843c892dd48 (diff) | |
download | qtlocation-mapboxgl-8eb23cbac8a6c5821dc5935e27689700216c3f1a.tar.gz |
[ios] Scale bar (#7631)
* [ios] added a scale bar
* [ios] update design
* [ios] show/hide scale bar
* [ios] Remove the need to localize 0
* [ios] Support for imperial units
* [ios] Round to nearest foot
* [ios] Make scale bar private
* [ios] Update design and clean up
* [ios] Rename and various optimizations
* [ios] RTL support
* [ios] added max scale and removed animations
* [ios] animate scale bar
-rw-r--r-- | platform/darwin/src/MGLDistanceFormatter.m | 2 | ||||
-rw-r--r-- | platform/ios/CHANGELOG.md | 2 | ||||
-rw-r--r-- | platform/ios/app/MBXViewController.m | 2 | ||||
-rw-r--r-- | platform/ios/ios.xcodeproj/project.pbxproj | 20 | ||||
-rw-r--r-- | platform/ios/src/MGLMapView.h | 6 | ||||
-rw-r--r-- | platform/ios/src/MGLMapView.mm | 45 | ||||
-rw-r--r-- | platform/ios/src/MGLScaleBar.h | 9 | ||||
-rw-r--r-- | platform/ios/src/MGLScaleBar.mm | 365 |
8 files changed, 447 insertions, 4 deletions
diff --git a/platform/darwin/src/MGLDistanceFormatter.m b/platform/darwin/src/MGLDistanceFormatter.m index e77e48b512..a7a2f9c9e1 100644 --- a/platform/darwin/src/MGLDistanceFormatter.m +++ b/platform/darwin/src/MGLDistanceFormatter.m @@ -24,7 +24,7 @@ static const double FEET_PER_MILE = YARDS_PER_MILE * 3.0; return [self stringFromValue:miles unit:unit]; } else { unit = NSLengthFormatterUnitFoot; - self.numberFormatter.roundingIncrement = @50; + self.numberFormatter.roundingIncrement = @1; return [self stringFromValue:feet unit:unit]; } } else { diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index ee5b2a29c0..062462c5d9 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -4,6 +4,8 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ## master +* Added a scale bar to MGLMapView that indicates the scale of the map. ([#7631](https://github.com/mapbox/mapbox-gl-native/pull/7631)) + ### Styles * MGLSymbolStyleLayer’s `iconImageName`, `iconScale`, `textFontSize`, `textOffset`, and `textRotation` properties can now be set to a source or composite function. ([#8544](https://github.com/mapbox/mapbox-gl-native/pull/8544), [#8590](https://github.com/mapbox/mapbox-gl-native/pull/8590), [#8592](https://github.com/mapbox/mapbox-gl-native/pull/8592), [#8593](https://github.com/mapbox/mapbox-gl-native/pull/8593)) diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index a7bab2108a..70271c02b9 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -159,7 +159,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { [self restoreState:nil]; self.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"MGLMapboxMetricsDebugLoggingEnabled"]; - + self.mapView.scaleBar.hidden = NO; self.hudLabel.hidden = YES; if ([MGLAccountManager accessToken].length) diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 9936c7244f..1d25a9679e 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -81,6 +81,8 @@ 3557F7B21E1D27D300CCA5E6 /* MGLDistanceFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 3557F7AF1E1D27D300CCA5E6 /* MGLDistanceFormatter.m */; }; 35599DED1D46F14E0048254D /* MGLStyleValue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35599DEA1D46F14E0048254D /* MGLStyleValue.mm */; }; 35599DEE1D46F14E0048254D /* MGLStyleValue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35599DEA1D46F14E0048254D /* MGLStyleValue.mm */; }; + 355AE0011E9281DA00F3939D /* MGLScaleBar.mm in Sources */ = {isa = PBXBuildFile; fileRef = 355ADFFC1E9281DA00F3939D /* MGLScaleBar.mm */; }; + 355AE0021E9281DA00F3939D /* MGLScaleBar.mm in Sources */ = {isa = PBXBuildFile; fileRef = 355ADFFC1E9281DA00F3939D /* MGLScaleBar.mm */; }; 3566C7661D4A77BA008152BC /* MGLShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 3566C7641D4A77BA008152BC /* MGLShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3566C7671D4A77BA008152BC /* MGLShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 3566C7641D4A77BA008152BC /* MGLShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3566C7681D4A77BA008152BC /* MGLShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3566C7651D4A77BA008152BC /* MGLShapeSource.mm */; }; @@ -121,6 +123,8 @@ 35D13AC41D3D19DD00AFB4E0 /* MGLFillStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 35D13AC11D3D19DD00AFB4E0 /* MGLFillStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 35D13AC51D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35D13AC21D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm */; }; 35D13AC61D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35D13AC21D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm */; }; + 35D3A1E61E9BE7EB002B38EE /* MGLScaleBar.h in Headers */ = {isa = PBXBuildFile; fileRef = 355ADFFB1E9281DA00F3939D /* MGLScaleBar.h */; }; + 35D3A1E71E9BE7EC002B38EE /* MGLScaleBar.h in Headers */ = {isa = PBXBuildFile; fileRef = 355ADFFB1E9281DA00F3939D /* MGLScaleBar.h */; }; 35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */; }; 35E0CFE61D3E501500188327 /* MGLStyle_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 35E0CFE51D3E501500188327 /* MGLStyle_Private.h */; }; 35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 35E0CFE51D3E501500188327 /* MGLStyle_Private.h */; }; @@ -575,6 +579,8 @@ 3557F7AE1E1D27D300CCA5E6 /* MGLDistanceFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLDistanceFormatter.h; sourceTree = "<group>"; }; 3557F7AF1E1D27D300CCA5E6 /* MGLDistanceFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLDistanceFormatter.m; sourceTree = "<group>"; }; 35599DEA1D46F14E0048254D /* MGLStyleValue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLStyleValue.mm; sourceTree = "<group>"; }; + 355ADFFB1E9281DA00F3939D /* MGLScaleBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLScaleBar.h; sourceTree = "<group>"; }; + 355ADFFC1E9281DA00F3939D /* MGLScaleBar.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLScaleBar.mm; sourceTree = "<group>"; }; 3566C7641D4A77BA008152BC /* MGLShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLShapeSource.h; sourceTree = "<group>"; }; 3566C7651D4A77BA008152BC /* MGLShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLShapeSource.mm; sourceTree = "<group>"; }; 3566C76A1D4A8DFA008152BC /* MGLRasterSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLRasterSource.h; sourceTree = "<group>"; }; @@ -1028,6 +1034,15 @@ name = Categories; sourceTree = "<group>"; }; + 355ADFF91E9281C300F3939D /* Views */ = { + isa = PBXGroup; + children = ( + 355ADFFB1E9281DA00F3939D /* MGLScaleBar.h */, + 355ADFFC1E9281DA00F3939D /* MGLScaleBar.mm */, + ); + name = Views; + sourceTree = "<group>"; + }; 357579811D502AD4000B822E /* Styling */ = { isa = PBXGroup; children = ( @@ -1290,6 +1305,7 @@ DA8848331CBAFB2A00AB86E3 /* Kit */ = { isa = PBXGroup; children = ( + 355ADFF91E9281C300F3939D /* Views */, 35CE617F1D4165C2004F2359 /* Categories */, DAD165841CF4D06B001FF4B9 /* Annotations */, DAD165851CF4D08B001FF4B9 /* Telemetry */, @@ -1628,6 +1644,7 @@ DA6408DB1DA4E7D300908C90 /* MGLVectorStyleLayer.h in Headers */, DD0902AB1DB192A800C5BDCE /* MGLNetworkConfiguration.h in Headers */, DA8848571CBAFB9800AB86E3 /* MGLMapboxEvents.h in Headers */, + 35D3A1E61E9BE7EB002B38EE /* MGLScaleBar.h in Headers */, DA8848311CBAFA6200AB86E3 /* NSString+MGLAdditions.h in Headers */, 353933F81D3FB79F003F57D7 /* MGLLineStyleLayer.h in Headers */, DAAF722D1DA903C700312FA4 /* MGLStyleValue_Private.h in Headers */, @@ -1714,6 +1731,7 @@ 404C26E31D89B877000AA13D /* MGLTileSource.h in Headers */, DABFB8611CBE99E500D62B32 /* MGLMultiPoint.h in Headers */, 3510FFF11D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */, + 35D3A1E71E9BE7EC002B38EE /* MGLScaleBar.h in Headers */, 35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */, DABFB86D1CBE9A0F00D62B32 /* MGLAnnotationImage.h in Headers */, DABFB8721CBE9A0F00D62B32 /* MGLUserLocation.h in Headers */, @@ -2199,6 +2217,7 @@ DA88482B1CBAFA6200AB86E3 /* MGLTypes.m in Sources */, 4018B1C71CDC287F00F666AF /* MGLAnnotationView.mm in Sources */, 404C26E41D89B877000AA13D /* MGLTileSource.mm in Sources */, + 355AE0011E9281DA00F3939D /* MGLScaleBar.mm in Sources */, DA88481D1CBAFA6200AB86E3 /* MGLMapCamera.mm in Sources */, DA8848261CBAFA6200AB86E3 /* MGLPolygon.mm in Sources */, 35B82BFA1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */, @@ -2277,6 +2296,7 @@ DAA4E4321CBB730400178DFB /* MGLMapView.mm in Sources */, DAA4E41E1CBB730400178DFB /* MGLMapCamera.mm in Sources */, 404C26E51D89B877000AA13D /* MGLTileSource.mm in Sources */, + 355AE0021E9281DA00F3939D /* MGLScaleBar.mm in Sources */, 4018B1C81CDC287F00F666AF /* MGLAnnotationView.mm in Sources */, DAA4E4341CBB730400178DFB /* MGLFaux3DUserLocationAnnotationView.m in Sources */, 35B82BFB1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */, diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h index 4872ff2448..31320ac977 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -226,6 +226,12 @@ IB_DESIGNABLE - (IBAction)reloadStyle:(id)sender; /** + A control indicating the scale of the map. The scale bar is positioned in the + upper-left corner. The scale bar is hidden by default. + */ +@property (nonatomic, readonly) UIView *scaleBar; + +/** A control indicating the map’s direction and allowing the user to manipulate the direction, positioned in the upper-right corner. */ diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 6badb1d857..fc4d39a143 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -54,6 +54,7 @@ #import "MGLUserLocation_Private.h" #import "MGLAnnotationImage_Private.h" #import "MGLAnnotationView_Private.h" +#import "MGLScaleBar.h" #import "MGLStyle_Private.h" #import "MGLStyleLayer_Private.h" #import "MGLMapboxEvents.h" @@ -229,6 +230,8 @@ public: @property (nonatomic) EAGLContext *context; @property (nonatomic) GLKView *glView; @property (nonatomic) UIImageView *glSnapshotView; +@property (nonatomic, readwrite) MGLScaleBar *scaleBar; +@property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *scaleBarConstraints; @property (nonatomic, readwrite) UIImageView *compassView; @property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *compassViewConstraints; @property (nonatomic, readwrite) UIImageView *logoView; @@ -493,7 +496,14 @@ public: _compassView.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:_compassView]; _compassViewConstraints = [NSMutableArray array]; - + + // setup scale control + // + _scaleBar = [[MGLScaleBar alloc] init]; + _scaleBar.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_scaleBar]; + _scaleBarConstraints = [NSMutableArray array]; + // setup interaction // _pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; @@ -774,6 +784,31 @@ public: - (void)updateConstraints { + // scale control + // + [self removeConstraints:self.scaleBarConstraints]; + [self.scaleBarConstraints removeAllObjects]; + + [self.scaleBarConstraints addObject: + [NSLayoutConstraint constraintWithItem:self.scaleBar + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeTop + multiplier:1 + constant:5+self.contentInset.top]]; + + [self.scaleBarConstraints addObject: + [NSLayoutConstraint constraintWithItem:self.scaleBar + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeLeading + multiplier:1 + constant:8 + self.contentInset.left]]; + + [self addConstraints:self.scaleBarConstraints]; + // compass // [self removeConstraints:self.compassViewConstraints]; @@ -1564,6 +1599,8 @@ public: { [weakSelf unrotateIfNeededForGesture]; }]; + } else { + [self unrotateIfNeededForGesture]; } } } @@ -4827,7 +4864,11 @@ public: } [self updateCompass]; - + + if (!self.scaleBar.hidden) { + [(MGLScaleBar *)self.scaleBar setMetersPerPoint:[self metersPerPointAtLatitude:self.centerCoordinate.latitude]]; + } + if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) { [self.delegate mapViewRegionIsChanging:self]; diff --git a/platform/ios/src/MGLScaleBar.h b/platform/ios/src/MGLScaleBar.h new file mode 100644 index 0000000000..77fd6736b5 --- /dev/null +++ b/platform/ios/src/MGLScaleBar.h @@ -0,0 +1,9 @@ +#import <UIKit/UIKit.h> +#import <CoreLocation/CoreLocation.h> + +@interface MGLScaleBar : UIView + +// Sets the scale and redraws the scale bar +@property (nonatomic, assign) CLLocationDistance metersPerPoint; + +@end diff --git a/platform/ios/src/MGLScaleBar.mm b/platform/ios/src/MGLScaleBar.mm new file mode 100644 index 0000000000..383710ea37 --- /dev/null +++ b/platform/ios/src/MGLScaleBar.mm @@ -0,0 +1,365 @@ +#import <Mapbox/Mapbox.h> +#import "MGLScaleBar.h" + +static const CGFloat MGLFeetPerMile = 5280; + +struct MGLRow { + CLLocationDistance distance; + NSUInteger numberOfBars; +}; + +static const MGLRow MGLMetricTable[] = { + {.distance = 1, .numberOfBars = 2}, + {.distance = 2, .numberOfBars = 2}, + {.distance = 4, .numberOfBars = 2}, + {.distance = 10, .numberOfBars = 2}, + {.distance = 20, .numberOfBars = 2}, + {.distance = 50, .numberOfBars = 2}, + {.distance = 75, .numberOfBars = 3}, + {.distance = 100, .numberOfBars = 2}, + {.distance = 150, .numberOfBars = 2}, + {.distance = 200, .numberOfBars = 2}, + {.distance = 300, .numberOfBars = 3}, + {.distance = 500, .numberOfBars = 2}, + {.distance = 1000, .numberOfBars = 2}, + {.distance = 1500, .numberOfBars = 2}, + {.distance = 3000, .numberOfBars = 3}, + {.distance = 5000, .numberOfBars = 2}, + {.distance = 10000, .numberOfBars = 2}, + {.distance = 20000, .numberOfBars = 2}, + {.distance = 30000, .numberOfBars = 3}, + {.distance = 50000, .numberOfBars = 2}, + {.distance = 100000, .numberOfBars = 2}, + {.distance = 200000, .numberOfBars = 2}, + {.distance = 300000, .numberOfBars = 3}, + {.distance = 400000, .numberOfBars = 2}, + {.distance = 500000, .numberOfBars = 2}, + {.distance = 600000, .numberOfBars = 3}, + {.distance = 800000, .numberOfBars = 2}, +}; + +static const MGLRow MGLImperialTable[] ={ + {.distance = 4, .numberOfBars = 2}, + {.distance = 6, .numberOfBars = 2}, + {.distance = 10, .numberOfBars = 2}, + {.distance = 20, .numberOfBars = 2}, + {.distance = 30, .numberOfBars = 2}, + {.distance = 50, .numberOfBars = 2}, + {.distance = 75, .numberOfBars = 3}, + {.distance = 100, .numberOfBars = 2}, + {.distance = 200, .numberOfBars = 2}, + {.distance = 300, .numberOfBars = 3}, + {.distance = 400, .numberOfBars = 2}, + {.distance = 600, .numberOfBars = 3}, + {.distance = 800, .numberOfBars = 2}, + {.distance = 1000, .numberOfBars = 2}, + {.distance = 0.25f*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 0.5f*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 1*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 2*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 3*MGLFeetPerMile, .numberOfBars = 3}, + {.distance = 4*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 8*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 12*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 15*MGLFeetPerMile, .numberOfBars = 3}, + {.distance = 20*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 30*MGLFeetPerMile, .numberOfBars = 3}, + {.distance = 40*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 80*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 120*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 200*MGLFeetPerMile, .numberOfBars = 2}, + {.distance = 300*MGLFeetPerMile, .numberOfBars = 3}, + {.distance = 400*MGLFeetPerMile, .numberOfBars = 2}, +}; + +@class MGLScaleBarLabel; + +@interface MGLScaleBar() +@property (nonatomic) NSArray<MGLScaleBarLabel *> *labels; +@property (nonatomic) NSArray<UIView *> *bars; +@property (nonatomic) UIView *containerView; +@property (nonatomic) MGLDistanceFormatter *formatter; +@property (nonatomic, assign) MGLRow row; +@property (nonatomic) UIColor *primaryColor; +@property (nonatomic) UIColor *secondaryColor; +@property (nonatomic) CALayer *borderLayer; +@property (nonatomic, assign) CGFloat borderWidth; +@end + +static const CGFloat MGLBarHeight = 4; +static const CGFloat MGLFeetPerMeter = 3.28084; + +@interface MGLScaleBarLabel : UILabel +@end + +@implementation MGLScaleBarLabel + +- (void)drawTextInRect:(CGRect)rect { + CGSize shadowOffset = self.shadowOffset; + UIColor *textColor = self.textColor; + + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSetLineWidth(context, 2); + CGContextSetLineJoin(context, kCGLineJoinRound); + + CGContextSetTextDrawingMode(context, kCGTextStroke); + self.textColor = [UIColor whiteColor]; + [super drawTextInRect:rect]; + + CGContextSetTextDrawingMode(context, kCGTextFill); + self.textColor = textColor; + self.shadowOffset = CGSizeMake(0, 0); + [super drawTextInRect:rect]; + + self.shadowOffset = shadowOffset; +} + +@end + +@implementation MGLScaleBar + +- (instancetype)initWithCoder:(NSCoder *)decoder { + if (self = [super initWithCoder:decoder]) { + [self commonInit]; + } + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + [self commonInit]; + } + return self; +} + +- (void)commonInit { + _primaryColor = [UIColor colorWithRed:18.0/255.0 green:45.0/255.0 blue:17.0/255.0 alpha:1]; + _secondaryColor = [UIColor colorWithRed:247.0/255.0 green:247.0/255.0 blue:247.0/255.0 alpha:1]; + _borderWidth = 1.0f; + + self.clipsToBounds = NO; + self.hidden = YES; + + _containerView = [[UIView alloc] init]; + _containerView.clipsToBounds = YES; + _containerView.backgroundColor = self.secondaryColor; + [self addSubview:_containerView]; + + _borderLayer = [CAShapeLayer layer]; + _borderLayer.borderColor = [self.primaryColor CGColor]; + _borderLayer.borderWidth = 1.0f / [[UIScreen mainScreen] scale]; + + [_containerView.layer addSublayer:_borderLayer]; + + _formatter = [[MGLDistanceFormatter alloc] init]; +} + +#pragma mark - Dimensions + +- (CGSize)intrinsicContentSize { + return CGSizeMake(self.actualWidth, 16); +} + +- (CGFloat)actualWidth { + return self.row.distance / [self unitsPerPoint]; +} + +- (CGFloat)maximumWidth { + CGFloat fullWidth = CGRectGetWidth(self.superview.bounds); + CGFloat padding = [self usesRightToLeftLayout] ? fullWidth - CGRectGetMaxX(self.frame) : CGRectGetMinX(self.frame); + return floorf(fullWidth / 2 - padding); +} + +- (CGFloat)unitsPerPoint { + return [self usesMetricSystem] ? self.metersPerPoint : self.metersPerPoint * MGLFeetPerMeter; +} + +#pragma mark - Convenient methods + +- (BOOL)usesRightToLeftLayout { + return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.superview.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft; +} + +- (BOOL)usesMetricSystem { + NSLocale *locale = [NSLocale currentLocale]; + return [[locale objectForKey:NSLocaleUsesMetricSystem] boolValue]; +} + +- (MGLRow)preferredRow { + CLLocationDistance maximumDistance = [self maximumWidth] * [self unitsPerPoint]; + MGLRow row; + + BOOL useMetric = [self usesMetricSystem]; + NSUInteger count = useMetric + ? sizeof(MGLMetricTable) / sizeof(MGLMetricTable[0]) + : sizeof(MGLImperialTable) / sizeof(MGLImperialTable[0]); + + for (NSUInteger i = 0; i < count; i++) { + CLLocationDistance distance = useMetric ? MGLMetricTable[i].distance : MGLImperialTable[i].distance; + if (distance <= maximumDistance) { + row = useMetric ? MGLMetricTable[i] : MGLImperialTable[i]; + } else { + break; + } + } + + return row; +} + +#pragma mark - Setters + +- (void)setMetersPerPoint:(CLLocationDistance)metersPerPoint { + if (_metersPerPoint == metersPerPoint) { + return; + } + + _metersPerPoint = metersPerPoint; + + [self updateVisibility]; + + self.row = [self preferredRow]; + + [self invalidateIntrinsicContentSize]; + [self setNeedsLayout]; +} + +- (void)updateVisibility { + BOOL metric = [self usesMetricSystem]; + + NSUInteger count = metric + ? sizeof(MGLMetricTable) / sizeof(MGLMetricTable[0]) + : sizeof(MGLImperialTable) / sizeof(MGLImperialTable[0]); + + CLLocationDistance maximumDistance = [self maximumWidth] * [self unitsPerPoint]; + CLLocationDistance allowedDistance = metric + ? MGLMetricTable[count-1].distance + : MGLImperialTable[count-1].distance; + + CGFloat alpha = maximumDistance > allowedDistance ? .0f : 1.0f; + + if(self.alpha != alpha) { + [UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ + self.alpha = alpha; + } completion:nil]; + } +} + +- (void)setRow:(MGLRow)row { + if (_row.distance == row.distance) { + return; + } + + _row = row; + [_bars makeObjectsPerformSelector:@selector(removeFromSuperview)]; + [_labels makeObjectsPerformSelector:@selector(removeFromSuperview)]; + _bars = nil; + _labels = nil; +} + +#pragma mark - Views + +- (NSArray<UIView *> *)bars { + if (!_bars) { + NSMutableArray *bars = [NSMutableArray array]; + for (NSUInteger i = 0; i < self.row.numberOfBars; i++) { + UIView *bar = [[UIView alloc] init]; + [bars addObject:bar]; + [self.containerView addSubview:bar]; + } + _bars = bars; + } + return _bars; +} + +- (NSArray<UILabel *> *)labels { + if (!_labels) { + NSDecimalNumber *zeroNumber = [NSDecimalNumber decimalNumberWithString:@"0"]; + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + NSMutableArray *labels = [NSMutableArray array]; + + for (NSUInteger i = 0; i <= self.row.numberOfBars; i++) { + UILabel *label = [[MGLScaleBarLabel alloc] init]; + label.font = [UIFont systemFontOfSize:8 weight:UIFontWeightMedium]; + label.text = [formatter stringFromNumber:zeroNumber]; + label.clipsToBounds = NO; + [label setNeedsDisplay]; + [label sizeToFit]; + [labels addObject:label]; + [self addSubview:label]; + } + _labels = labels; + } + return _labels; +} + +#pragma mark - Layout + +- (void)layoutSubviews { + [super layoutSubviews]; + + if (!self.row.numberOfBars) { + // Current distance is not within allowed range + return; + } + + [self updateLabels]; + [self layoutBars]; + [self layoutLabels]; +} + +- (void)updateLabels { + NSArray *labels = [self.labels subarrayWithRange:NSMakeRange(1, self.labels.count-1)]; + BOOL useMetric = [self usesMetricSystem]; + NSUInteger i = 0; + + for (MGLScaleBarLabel *label in labels) { + CLLocationDistance barDistance = (self.row.distance / self.row.numberOfBars) * (i + 1); + + if (!useMetric) { + barDistance /= MGLFeetPerMeter; + } + + label.text = [self.formatter stringFromDistance:barDistance]; + [label setNeedsDisplay]; + [label sizeToFit]; + i++; + } +} + +- (void)layoutBars { + CGFloat barWidth = (CGRectGetWidth(self.bounds) - self.borderWidth * 2.0f) / self.bars.count; + + NSUInteger i = 0; + for (UIView *bar in self.bars) { + CGFloat xPosition = barWidth * i + self.borderWidth; + bar.backgroundColor = (i % 2 == 0) ? self.primaryColor : self.secondaryColor; + bar.frame = CGRectMake(xPosition, self.borderWidth, barWidth, MGLBarHeight); + i++; + } + + self.containerView.frame = CGRectMake(CGRectGetMinX(self.bars.firstObject.frame), + CGRectGetMaxY(self.bounds)-MGLBarHeight, + self.actualWidth, + MGLBarHeight+self.borderWidth*2); + + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + self.borderLayer.frame = CGRectInset(self.containerView.bounds, self.borderWidth, self.borderWidth); + self.borderLayer.zPosition = FLT_MAX; + [CATransaction commit]; +} + +- (void)layoutLabels { + CGFloat barWidth = self.bounds.size.width / self.bars.count; + BOOL RTL = [self usesRightToLeftLayout]; + NSUInteger i = RTL ? self.bars.count : 0; + for (MGLScaleBarLabel *label in self.labels) { + CGFloat xPosition = barWidth * i - CGRectGetMidX(label.bounds) + self.borderWidth; + label.frame = CGRectMake(xPosition, 0, + CGRectGetWidth(label.bounds), + CGRectGetHeight(label.bounds)); + i = RTL ? i-1 : i+1; + } +} + +@end |