[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
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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];
|
||||
|
||||
9
platform/ios/src/MGLScaleBar.h
Normal file
9
platform/ios/src/MGLScaleBar.h
Normal file
@@ -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
|
||||
365
platform/ios/src/MGLScaleBar.mm
Normal file
365
platform/ios/src/MGLScaleBar.mm
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user