Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Show scale control #7432

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions platform/ios/ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
35136D4D1D4277FC00C20EFD /* MGLSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 35136D4A1D4277FC00C20EFD /* MGLSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
35136D4E1D4277FC00C20EFD /* MGLSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35136D4B1D4277FC00C20EFD /* MGLSource.mm */; };
35136D4F1D4277FC00C20EFD /* MGLSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35136D4B1D4277FC00C20EFD /* MGLSource.mm */; };
35269F921E018FF8009BFDD4 /* MGLScaleControlView.h in Headers */ = {isa = PBXBuildFile; fileRef = 35269F901E018FF8009BFDD4 /* MGLScaleControlView.h */; };
35269F931E018FF8009BFDD4 /* MGLScaleControlView.h in Headers */ = {isa = PBXBuildFile; fileRef = 35269F901E018FF8009BFDD4 /* MGLScaleControlView.h */; };
35269F941E018FF8009BFDD4 /* MGLScaleControlView.m in Sources */ = {isa = PBXBuildFile; fileRef = 35269F911E018FF8009BFDD4 /* MGLScaleControlView.m */; };
35269F951E018FF8009BFDD4 /* MGLScaleControlView.m in Sources */ = {isa = PBXBuildFile; fileRef = 35269F911E018FF8009BFDD4 /* MGLScaleControlView.m */; };
35305D481D22AA680007D005 /* NSData+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35305D471D22AA450007D005 /* NSData+MGLAdditions.mm */; };
35305D491D22AA680007D005 /* NSData+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35305D471D22AA450007D005 /* NSData+MGLAdditions.mm */; };
35305D4A1D22AA6A0007D005 /* NSData+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 35305D461D22AA450007D005 /* NSData+MGLAdditions.h */; };
Expand Down Expand Up @@ -526,6 +530,8 @@
35136D441D42275100C20EFD /* MGLSymbolStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLSymbolStyleLayer.mm; sourceTree = "<group>"; };
35136D4A1D4277FC00C20EFD /* MGLSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLSource.h; sourceTree = "<group>"; };
35136D4B1D4277FC00C20EFD /* MGLSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLSource.mm; sourceTree = "<group>"; };
35269F901E018FF8009BFDD4 /* MGLScaleControlView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLScaleControlView.h; sourceTree = "<group>"; };
35269F911E018FF8009BFDD4 /* MGLScaleControlView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLScaleControlView.m; sourceTree = "<group>"; };
35305D461D22AA450007D005 /* NSData+MGLAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+MGLAdditions.h"; sourceTree = "<group>"; };
35305D471D22AA450007D005 /* NSData+MGLAdditions.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+MGLAdditions.mm"; sourceTree = "<group>"; };
3538AA1B1D542239008EC33D /* MGLForegroundStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLForegroundStyleLayer.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1161,6 +1167,8 @@
DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */,
DA88487F1CBB033F00AB86E3 /* Fabric */,
DA8848881CBB036000AB86E3 /* SMCalloutView */,
35269F901E018FF8009BFDD4 /* MGLScaleControlView.h */,
35269F911E018FF8009BFDD4 /* MGLScaleControlView.m */,
);
name = Kit;
path = src;
Expand Down Expand Up @@ -1484,6 +1492,7 @@
3510FFEA1D6D9C7A00F413B2 /* NSComparisonPredicate+MGLAdditions.h in Headers */,
DA6408DB1DA4E7D300908C90 /* MGLVectorStyleLayer.h in Headers */,
DD0902AB1DB192A800C5BDCE /* MGLNetworkConfiguration.h in Headers */,
35269F921E018FF8009BFDD4 /* MGLScaleControlView.h in Headers */,
DA8848571CBAFB9800AB86E3 /* MGLMapboxEvents.h in Headers */,
DA8848311CBAFA6200AB86E3 /* NSString+MGLAdditions.h in Headers */,
353933F81D3FB79F003F57D7 /* MGLLineStyleLayer.h in Headers */,
Expand Down Expand Up @@ -1568,6 +1577,7 @@
404C26E31D89B877000AA13D /* MGLTileSource.h in Headers */,
DABFB8611CBE99E500D62B32 /* MGLMultiPoint.h in Headers */,
3510FFF11D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */,
35269F931E018FF8009BFDD4 /* MGLScaleControlView.h in Headers */,
35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */,
DABFB86D1CBE9A0F00D62B32 /* MGLAnnotationImage.h in Headers */,
DABFB8721CBE9A0F00D62B32 /* MGLUserLocation.h in Headers */,
Expand Down Expand Up @@ -2007,6 +2017,7 @@
DA35A2A11CC9E95F00E826B2 /* MGLCoordinateFormatter.m in Sources */,
35305D481D22AA680007D005 /* NSData+MGLAdditions.mm in Sources */,
DA8848291CBAFA6200AB86E3 /* MGLStyle.mm in Sources */,
35269F941E018FF8009BFDD4 /* MGLScaleControlView.m in Sources */,
DA88481C1CBAFA6200AB86E3 /* MGLGeometry.mm in Sources */,
3510FFF21D6D9D8C00F413B2 /* NSExpression+MGLAdditions.mm in Sources */,
DA88481F1CBAFA6200AB86E3 /* MGLMultiPoint.mm in Sources */,
Expand Down Expand Up @@ -2082,6 +2093,7 @@
DAA4E4281CBB730400178DFB /* MGLTypes.m in Sources */,
DA35A2A21CC9E95F00E826B2 /* MGLCoordinateFormatter.m in Sources */,
35305D491D22AA680007D005 /* NSData+MGLAdditions.mm in Sources */,
35269F951E018FF8009BFDD4 /* MGLScaleControlView.m in Sources */,
DAA4E42D1CBB730400178DFB /* MGLAnnotationImage.m in Sources */,
3510FFF31D6D9D8C00F413B2 /* NSExpression+MGLAdditions.mm in Sources */,
DAA4E4301CBB730400178DFB /* MGLLocationManager.m in Sources */,
Expand Down
38 changes: 37 additions & 1 deletion platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#import "MGLAnnotationContainerView.h"
#import "MGLAnnotationContainerView_Private.h"
#import "MGLAttributionInfo.h"
#import "MGLScaleControlView.h"

#include <algorithm>
#include <cstdlib>
Expand Down Expand Up @@ -230,6 +231,8 @@ @interface MGLMapView () <UIGestureRecognizerDelegate,
@property (nonatomic) EAGLContext *context;
@property (nonatomic) GLKView *glView;
@property (nonatomic) UIImageView *glSnapshotView;
@property (nonatomic, readwrite) MGLScaleControlView *scaleControlView;
@property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *scaleControlViewConstraints;
@property (nonatomic, readwrite) UIImageView *compassView;
@property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *compassViewConstraints;
@property (nonatomic, readwrite) UIImageView *logoView;
Expand Down Expand Up @@ -486,6 +489,13 @@ - (void)commonInit
_compassView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_compassView];
_compassViewConstraints = [NSMutableArray array];

// setup scale control
//
_scaleControlView = [[MGLScaleControlView alloc] init];
_scaleControlView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_scaleControlView];
_scaleControlViewConstraints = [NSMutableArray array];

// setup interaction
//
Expand Down Expand Up @@ -770,6 +780,31 @@ - (UIViewController *)viewControllerForLayoutGuides

- (void)updateConstraints
{
// scale control
//
[self removeConstraints:self.scaleControlViewConstraints];
[self.scaleControlViewConstraints removeAllObjects];

[self.scaleControlViewConstraints addObject:
[NSLayoutConstraint constraintWithItem:self.scaleControlView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeTop
multiplier:1
constant:5+self.contentInset.top]];

[self.scaleControlViewConstraints addObject:
[NSLayoutConstraint constraintWithItem:self.scaleControlView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeLeading
multiplier:1
constant:8 + self.contentInset.left]];

[self addConstraints:self.scaleControlViewConstraints];

// compass
//
[self removeConstraints:self.compassViewConstraints];
Expand Down Expand Up @@ -4587,7 +4622,8 @@ - (void)notifyMapChange:(mbgl::MapChange)change
case mbgl::MapChangeRegionIsChanging:
{
[self updateCompass];

[self.scaleControlView updateWithZoomLevel:self.zoomLevel metersPerPoint:[self metersPerPointAtLatitude:self.latitude]];

if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)])
{
[self.delegate mapViewRegionIsChanging:self];
Expand Down
8 changes: 8 additions & 0 deletions platform/ios/src/MGLScaleControlView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>

@interface MGLScaleControlView : UIView

- (void)updateWithZoomLevel:(double)zoomLevel metersPerPoint:(CLLocationDistance)metersPerPoint;

@end
154 changes: 154 additions & 0 deletions platform/ios/src/MGLScaleControlView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#import "MGLScaleControlView.h"

@interface MGLScaleControlView()
@property (nonatomic, assign) double zoomLevel;
@property (nonatomic, assign) CLLocationDistance metersPerPoint;
@property (nonatomic) NSArray<UILabel *> *labels;
@property (nonatomic) NSArray<UIView *> *bars;
@property (nonatomic) UILabel *debugLabel;
@property (nonatomic) NSLengthFormatter *formatter;
@end

@implementation MGLScaleControlView

static const NSUInteger TEMP_NUMBER_OF_BARS = 2;
static const CGFloat BASE_WIDTH = 100;

- (instancetype)init {
if (self == [super init]) {
_formatter = [[NSLengthFormatter alloc] init];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use MGLDistanceFormatter once #3331 is implemented. NSLengthFormatter is suboptimal because, when the system is set to U.S. units, it tends to use yards instead of feet or fractional miles, whereas Americans find yards difficult to relate to (except in football).

_formatter.unitStyle = NSDateComponentsFormatterUnitsStyleAbbreviated;
}
return self;
}

- (void)updateWithZoomLevel:(double)zoomLevel metersPerPoint:(CLLocationDistance)metersPerPoint {
self.zoomLevel = zoomLevel;
self.metersPerPoint = metersPerPoint;
}

- (void)setZoomLevel:(double)zoomLevel {
_zoomLevel = zoomLevel;
[self.debugLabel setText:[NSString stringWithFormat:@"%0.2f", zoomLevel]];
[self.debugLabel sizeToFit];
[self invalidateIntrinsicContentSize];
[self setNeedsLayout];
}

- (NSUInteger)stepsForDistance:(CLLocationDistance)distance {
if (distance <= 15) {
return 5;
} else if (distance <= 20) {
return 12;
} else if (distance < 100) {
return 25;
} else if (distance < 250) {
return 50;
}

return 100;
}

- (NSUInteger)nearestStepForDistance:(CLLocationDistance)distance {
NSUInteger step = [self stepsForDistance:distance];
return step * floor((distance/step)+0.5);
}

- (CGSize)intrinsicContentSize {
return CGSizeMake(self.preferredWidth, 10);
}

- (CGFloat)preferredWidth {
CGFloat widthInMeters = BASE_WIDTH * self.metersPerPoint;
NSUInteger step = [self nearestStepForDistance:widthInMeters];
return step / self.metersPerPoint;
}

- (NSArray<UIView *> *)bars {
if (!_bars) {
NSMutableArray *bars = [NSMutableArray array];
for (NSUInteger i = 0; i < TEMP_NUMBER_OF_BARS; i++) {
UIView *bar = [[UIView alloc] init];
[bars addObject:bar];
[self addSubview:bar];
}
_bars = bars;
}
return _bars;
}

- (NSArray<UILabel *> *)labels {
if (!_labels) {
NSMutableArray *labels = [NSMutableArray array];
for (NSUInteger i = 0; i < TEMP_NUMBER_OF_BARS+1; i++) {
UILabel *label = [[UILabel alloc] init];
label.text = @"0";
label.font = [UIFont systemFontOfSize:8];
[label sizeToFit];
[labels addObject:label];
[self addSubview:label];
}
_labels = labels;
}
return _labels;
}

- (UILabel *)debugLabel {
if (!_debugLabel) {
_debugLabel = [[UILabel alloc] init];
_debugLabel.font = [UIFont systemFontOfSize:8];
_debugLabel.textColor = [UIColor redColor];
[self addSubview:_debugLabel];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A label means you'd have to manage constraints internal to this view. That may be a good idea, but consider whether it might be easier to override -drawInRect: and use Core Graphics and attributed string drawing methods to accomplish the same effect, since you don't need to worry about line wrapping.

Copy link
Contributor Author

@frederoni frederoni Dec 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to use -drawInRect: when I want instant rendering instead of default animation between layouts otherwise I prefer -layoutSubviews. Working directly with the views also improves readability but I haven't really decided yet. The logic should be the same so we can switch to Core Graphics if we don't get the expected behavior without much fuss.

}
return _debugLabel;
}

- (void)layoutSubviews {
[super layoutSubviews];

self.clipsToBounds = NO;
self.backgroundColor = [UIColor yellowColor];

self.debugLabel.frame = CGRectMake(CGRectGetMidX(self.bounds)-CGRectGetMidX(_debugLabel.bounds),
CGRectGetMaxY(self.bounds),
CGRectGetWidth(_debugLabel.frame),
CGRectGetHeight(_debugLabel.frame));

[self updateLabels];
[self layoutBars];
[self layoutLabels];
}

- (void)layoutBars {
CGFloat barWidth = floorf(self.bounds.size.width / self.bars.count);
[self.bars enumerateObjectsUsingBlock:^(UIView * _Nonnull bar, NSUInteger idx, BOOL * _Nonnull stop) {
CGFloat xPos = barWidth * idx;
bar.backgroundColor = (idx % 2 == 0) ? [UIColor blackColor] : [UIColor whiteColor];
bar.frame = CGRectMake(xPos, CGRectGetMidY(self.bounds), barWidth, CGRectGetMidY(self.bounds));
}];
}

- (void)layoutLabels {
CGFloat barWidth = floorf(self.bounds.size.width / self.bars.count);
[self.labels enumerateObjectsUsingBlock:^(UILabel * _Nonnull label, NSUInteger idx, BOOL * _Nonnull stop) {
CGFloat xPosition = barWidth * idx - CGRectGetMidX(label.bounds);
label.frame = CGRectMake(xPosition,
CGRectGetMidY(self.bounds)-CGRectGetHeight(label.bounds),
CGRectGetWidth(label.bounds),
CGRectGetHeight(label.bounds));
}];
}

- (void)updateLabels {
NSArray *labels = [self.labels subarrayWithRange:NSMakeRange(1, self.labels.count-1)];
[labels enumerateObjectsUsingBlock:^(UILabel * _Nonnull label, NSUInteger idx, BOOL * _Nonnull stop) {
UIView *bar = self.bars[idx];
CGFloat scaleWidth = CGRectGetMaxX(bar.frame);
CGFloat widthInMeters = scaleWidth * self.metersPerPoint;
NSUInteger distance = [self nearestStepForDistance:widthInMeters];
label.text = [self.formatter stringFromMeters:distance];
[label sizeToFit];
}];
}

@end