diff --git a/platform/darwin/src/MGLDistanceFormatter.m b/platform/darwin/src/MGLDistanceFormatter.m index e77e48b5129..a7a2f9c9e17 100644 --- a/platform/darwin/src/MGLDistanceFormatter.m +++ b/platform/darwin/src/MGLDistanceFormatter.m @@ -24,7 +24,7 @@ - (NSString *)stringFromDistance:(CLLocationDistance)distance { 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 ee5b2a29c06..062462c5d90 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 a7bab2108ae..70271c02b9a 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -159,7 +159,7 @@ - (void)viewDidLoad [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 9936c7244f5..1d25a9679ea 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 = ""; }; 3557F7AF1E1D27D300CCA5E6 /* MGLDistanceFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLDistanceFormatter.m; sourceTree = ""; }; 35599DEA1D46F14E0048254D /* MGLStyleValue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLStyleValue.mm; sourceTree = ""; }; + 355ADFFB1E9281DA00F3939D /* MGLScaleBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLScaleBar.h; sourceTree = ""; }; + 355ADFFC1E9281DA00F3939D /* MGLScaleBar.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLScaleBar.mm; sourceTree = ""; }; 3566C7641D4A77BA008152BC /* MGLShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLShapeSource.h; sourceTree = ""; }; 3566C7651D4A77BA008152BC /* MGLShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLShapeSource.mm; sourceTree = ""; }; 3566C76A1D4A8DFA008152BC /* MGLRasterSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLRasterSource.h; sourceTree = ""; }; @@ -1028,6 +1034,15 @@ name = Categories; sourceTree = ""; }; + 355ADFF91E9281C300F3939D /* Views */ = { + isa = PBXGroup; + children = ( + 355ADFFB1E9281DA00F3939D /* MGLScaleBar.h */, + 355ADFFC1E9281DA00F3939D /* MGLScaleBar.mm */, + ); + name = Views; + sourceTree = ""; + }; 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 4872ff24485..31320ac977e 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -225,6 +225,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 6badb1d857d..fc4d39a143e 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 @@ @interface MGLMapView () +#import + +@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 00000000000..383710ea372 --- /dev/null +++ b/platform/ios/src/MGLScaleBar.mm @@ -0,0 +1,365 @@ +#import +#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 *labels; +@property (nonatomic) NSArray *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 *)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 *)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