[ios] Updated MGLScaleBar to use rendered UIImages instead of MGLScaleBarLabel (#11921)
This commit is contained in:
@@ -19,6 +19,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
|
||||
* Improved application launch performance.
|
||||
* Fixed an issue preventing nested key path expressions get parsed accordingly to the spec. ([#11959](https://github.com/mapbox/mapbox-gl-native/pull/11959))
|
||||
* Added custom `-hitTest:withEvent:` to `MGLSMCalloutView` to avoid registering taps in transparent areas of the standard annotation callout. ([#11939](https://github.com/mapbox/mapbox-gl-native/pull/11939))
|
||||
* Improved performance and memory impact of `MGLScaleBar`. ([#11921](https://github.com/mapbox/mapbox-gl-native/pull/11921))
|
||||
|
||||
## 4.0.1 - May 14, 2018
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) {
|
||||
typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
MBXSettingsMiscellaneousShowReuseQueueStats = 0,
|
||||
MBXSettingsMiscellaneousWorldTour,
|
||||
MBXSettingsMiscellaneousRandomTour,
|
||||
MBXSettingsMiscellaneousShowZoomLevel,
|
||||
MBXSettingsMiscellaneousScrollView,
|
||||
MBXSettingsMiscellaneousToggleTwoMaps,
|
||||
@@ -101,6 +102,65 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
MBXSettingsMiscellaneousDeleteLogFile,
|
||||
};
|
||||
|
||||
// Utility methods
|
||||
CLLocationCoordinate2D coordinateCentered(CLLocationCoordinate2D origin, CLLocationDegrees bearing, CLLocationDistance distance) {
|
||||
|
||||
// Convert to radians
|
||||
double multiplier = M_PI / 180.0;
|
||||
double sourceLatitude = multiplier * origin.latitude;
|
||||
double sourceLongitude = multiplier * origin.longitude;
|
||||
bearing *= multiplier;
|
||||
distance /= 6378137.0;
|
||||
|
||||
// Pulled from MGLRadianCoordinateAtDistanceFacingDirection:
|
||||
double latitude = asin((sin(sourceLatitude) * cos(distance)) +
|
||||
(cos(sourceLatitude) * sin(distance) * cos(bearing)));
|
||||
|
||||
double longitude = sourceLongitude + atan2((sin(bearing) * sin(distance) * cos(sourceLatitude)),
|
||||
cos(distance) - (sin(sourceLatitude) * sin(latitude)));
|
||||
|
||||
CLLocationCoordinate2D result;
|
||||
result.latitude = fmin(85.0, fmax(-85.0, (latitude / multiplier)));
|
||||
result.longitude = longitude / multiplier;
|
||||
return result;
|
||||
}
|
||||
|
||||
CLLocationCoordinate2D randomWorldCoordinate() {
|
||||
|
||||
static const struct {
|
||||
CLLocationCoordinate2D coordinate;
|
||||
CLLocationDistance radius;
|
||||
} landmasses[] = {
|
||||
// Rough land masses
|
||||
{{ 38.328531, 94.778736 }, 4100000 }, // Asia
|
||||
{{ 1.477244, 18.138111 }, 4100000 }, // Africa
|
||||
{{ 52.310059, 22.295425 }, 2000000 }, // Europe
|
||||
{{ 42.344216, -96.532700 }, 3000000 }, // N America
|
||||
{{ -11.537273, -57.035181 }, 2220000 }, // S America
|
||||
{{ -20.997030, 134.660541 }, 2220000 }, // Australia
|
||||
|
||||
// A few cities
|
||||
{{ 51.504787, -0.106977 }, 33000 }, // London
|
||||
{{ 37.740186, -122.437086 }, 8500 }, // SF
|
||||
{{ 52.509978, 13.406510 }, 12000 }, // Berlin
|
||||
{{ 12.966246, 77.586505 }, 19000 } // Bengaluru
|
||||
};
|
||||
|
||||
NSInteger index = drand48() * (sizeof(landmasses)/sizeof(landmasses[0]));
|
||||
CLLocationCoordinate2D coordinate = landmasses[index].coordinate;
|
||||
CLLocationDistance radius = landmasses[index].radius;
|
||||
|
||||
// Now create a world coord
|
||||
CLLocationDegrees heading = drand48()*360.0;
|
||||
CLLocationDistance distance = drand48()*radius;
|
||||
CLLocationCoordinate2D newLocation = coordinateCentered(coordinate, heading, distance);
|
||||
return newLocation;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@interface MBXDroppedPinAnnotation : MGLPointAnnotation
|
||||
@end
|
||||
|
||||
@@ -136,13 +196,11 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
@property (nonatomic) BOOL reuseQueueStatsEnabled;
|
||||
@property (nonatomic) BOOL showZoomLevelEnabled;
|
||||
@property (nonatomic) BOOL shouldLimitCameraChanges;
|
||||
|
||||
@property (nonatomic) BOOL randomWalk;
|
||||
@end
|
||||
|
||||
@interface MGLMapView (MBXViewController)
|
||||
|
||||
@property (nonatomic) NSDictionary *annotationViewReuseQueueByIdentifier;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MBXViewController
|
||||
@@ -277,6 +335,8 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
|
||||
- (IBAction)showSettings:(__unused id)sender
|
||||
{
|
||||
self.randomWalk = NO;
|
||||
|
||||
UITableViewController *settingsViewController = [[UITableViewController alloc] initWithStyle:UITableViewStyleGrouped];
|
||||
settingsViewController.tableView.delegate = self;
|
||||
settingsViewController.tableView.dataSource = self;
|
||||
@@ -377,6 +437,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
[settingsTitles addObjectsFromArray:@[
|
||||
[NSString stringWithFormat:@"%@ Reuse Queue Stats", (_reuseQueueStatsEnabled ? @"Hide" :@"Show")],
|
||||
@"Start World Tour",
|
||||
@"Random Tour",
|
||||
[NSString stringWithFormat:@"%@ Zoom/Pitch/Direction Label", (_showZoomLevelEnabled ? @"Hide" :@"Show")],
|
||||
@"Embedded Map View",
|
||||
[NSString stringWithFormat:@"%@ Second Map", ([self.view viewWithTag:2] == nil ? @"Show" : @"Hide")],
|
||||
@@ -580,6 +641,10 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
case MBXSettingsMiscellaneousWorldTour:
|
||||
[self startWorldTour];
|
||||
break;
|
||||
case MBXSettingsMiscellaneousRandomTour:
|
||||
[self randomWorldTour];
|
||||
break;
|
||||
|
||||
case MBXSettingsMiscellaneousPrintLogFile:
|
||||
[self printTelemetryLogFile];
|
||||
break;
|
||||
@@ -1681,6 +1746,75 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
#pragma mark - Random World Tour
|
||||
|
||||
- (void)addAnnotations:(NSInteger)numAnnotations aroundCoordinate:(CLLocationCoordinate2D)coordinate radius:(CLLocationDistance)radius {
|
||||
NSMutableArray *annotations = [[NSMutableArray alloc] initWithCapacity:numAnnotations];
|
||||
for (NSInteger i = 0; i<numAnnotations; i++) {
|
||||
|
||||
CLLocationDegrees heading = drand48()*360.0;
|
||||
CLLocationDistance distance = drand48()*radius;
|
||||
CLLocationCoordinate2D newLocation = coordinateCentered(coordinate, heading, distance);
|
||||
|
||||
MBXDroppedPinAnnotation *annotation = [[MBXDroppedPinAnnotation alloc] init];
|
||||
annotation.coordinate = newLocation;
|
||||
[annotations addObject:annotation];
|
||||
}
|
||||
[self.mapView addAnnotations:annotations];
|
||||
}
|
||||
|
||||
- (void)randomWorldTour {
|
||||
// Consistent initial conditions (consider setting these by test params)
|
||||
srand48(0);
|
||||
[self.mapView removeAnnotations:self.mapView.annotations];
|
||||
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(31, -100) zoomLevel:3 animated:NO];
|
||||
|
||||
[self randomWorldTourInternal];
|
||||
}
|
||||
|
||||
- (void)randomWorldTourInternal {
|
||||
|
||||
self.randomWalk = YES;
|
||||
|
||||
// Remove all annotations
|
||||
NSTimeInterval duration = 16.0;
|
||||
__weak MBXViewController *weakSelf = self;
|
||||
|
||||
// Remove old annotations, half-way through the flight.
|
||||
NSArray *annotationsToRemove = [self.mapView.annotations copy];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * 0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[weakSelf.mapView removeAnnotations:annotationsToRemove];
|
||||
});
|
||||
|
||||
MBXDroppedPinAnnotation *annotation = [[MBXDroppedPinAnnotation alloc] init];
|
||||
annotation.coordinate = randomWorldCoordinate();
|
||||
[self.mapView addAnnotation:annotation];
|
||||
|
||||
// Add annotations around that coord
|
||||
[self addAnnotations:50 aroundCoordinate:annotation.coordinate radius:100000.0]; // 100km
|
||||
|
||||
MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:annotation.coordinate
|
||||
fromDistance:10000.0
|
||||
pitch:drand48()*60.0
|
||||
heading:drand48()*360];
|
||||
[self.mapView flyToCamera:camera
|
||||
withDuration:duration
|
||||
peakAltitude:2000000.0
|
||||
completionHandler:^{
|
||||
// This completion handler is currently called BEFORE the
|
||||
// region did change delegate method, and we don't have a "reason"
|
||||
// so we can't tell if the motion was cancelled. We use the delegate
|
||||
// for that, and set self.randomWalk. But since we want a delay
|
||||
// anyway, we can just check later. Not ideal though..
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
MBXViewController *strongSelf = weakSelf;
|
||||
if (strongSelf.randomWalk) {
|
||||
[strongSelf randomWorldTourInternal];
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - User Actions
|
||||
|
||||
- (IBAction)handleLongPress:(UILongPressGestureRecognizer *)longPress
|
||||
@@ -2033,6 +2167,10 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
|
||||
- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated
|
||||
{
|
||||
if (reason != MGLCameraChangeReasonProgrammatic) {
|
||||
self.randomWalk = NO;
|
||||
}
|
||||
|
||||
[self updateHUD];
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ static const MGLRow MGLImperialTable[] ={
|
||||
@class MGLScaleBarLabel;
|
||||
|
||||
@interface MGLScaleBar()
|
||||
@property (nonatomic) NSArray<MGLScaleBarLabel *> *labels;
|
||||
@property (nonatomic) NSArray<UIView *> *labelViews;
|
||||
@property (nonatomic) NSArray<UIView *> *bars;
|
||||
@property (nonatomic) UIView *containerView;
|
||||
@property (nonatomic) MGLDistanceFormatter *formatter;
|
||||
@@ -84,6 +84,10 @@ static const MGLRow MGLImperialTable[] ={
|
||||
@property (nonatomic) UIColor *secondaryColor;
|
||||
@property (nonatomic) CALayer *borderLayer;
|
||||
@property (nonatomic, assign) CGFloat borderWidth;
|
||||
@property (nonatomic) NSCache* labelImageCache;
|
||||
@property (nonatomic) MGLScaleBarLabel* prototypeLabel;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
static const CGFloat MGLBarHeight = 4;
|
||||
@@ -152,6 +156,29 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
|
||||
[_containerView.layer addSublayer:_borderLayer];
|
||||
|
||||
_formatter = [[MGLDistanceFormatter alloc] init];
|
||||
|
||||
// Image labels are now images
|
||||
_labelImageCache = [[NSCache alloc] init];
|
||||
_prototypeLabel = [[MGLScaleBarLabel alloc] init];
|
||||
_prototypeLabel.font = [UIFont systemFontOfSize:8 weight:UIFontWeightMedium];
|
||||
_prototypeLabel.clipsToBounds = NO;
|
||||
|
||||
NSUInteger numberOfLabels = 4;
|
||||
NSMutableArray *labelViews = [NSMutableArray arrayWithCapacity:numberOfLabels];
|
||||
|
||||
for (NSUInteger i = 0; i < numberOfLabels; i++) {
|
||||
UIView *view = [[UIView alloc] init];
|
||||
view.bounds = CGRectZero;
|
||||
view.clipsToBounds = NO;
|
||||
view.contentMode = UIViewContentModeCenter;
|
||||
view.hidden = YES;
|
||||
[labelViews addObject:view];
|
||||
[self addSubview:view];
|
||||
}
|
||||
_labelViews = [labelViews copy];
|
||||
|
||||
// Zero is a special case (no formatting)
|
||||
[self addZeroLabel];
|
||||
}
|
||||
|
||||
#pragma mark - Dimensions
|
||||
@@ -190,21 +217,31 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
|
||||
CLLocationDistance maximumDistance = [self maximumWidth] * [self unitsPerPoint];
|
||||
|
||||
BOOL useMetric = [self usesMetricSystem];
|
||||
MGLRow row = useMetric ? MGLMetricTable[0] : MGLImperialTable[0];
|
||||
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;
|
||||
}
|
||||
|
||||
const MGLRow *row;
|
||||
const MGLRow *table;
|
||||
NSUInteger count;
|
||||
|
||||
if (useMetric) {
|
||||
row = table = MGLMetricTable;
|
||||
count = sizeof(MGLMetricTable) / sizeof(MGLMetricTable[0]);
|
||||
}
|
||||
|
||||
return row;
|
||||
else {
|
||||
row = table = MGLImperialTable;
|
||||
count = sizeof(MGLImperialTable) / sizeof(MGLImperialTable[0]);
|
||||
}
|
||||
|
||||
while (row < table + count) {
|
||||
if (row->distance > maximumDistance) {
|
||||
// use the previous row
|
||||
NSAssert(row != table, @"");
|
||||
return *(row - 1);
|
||||
}
|
||||
++row;
|
||||
}
|
||||
|
||||
// Didn't find it, just return the first.
|
||||
return *table;
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
@@ -255,9 +292,9 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
|
||||
|
||||
_row = row;
|
||||
[_bars makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
[_labels makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
_bars = nil;
|
||||
_labels = nil;
|
||||
|
||||
[self updateLabels];
|
||||
}
|
||||
|
||||
#pragma mark - Views
|
||||
@@ -275,61 +312,91 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
|
||||
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;
|
||||
#pragma mark - Labels
|
||||
|
||||
- (void)addZeroLabel {
|
||||
NSDecimalNumber *zeroNumber = [NSDecimalNumber decimalNumberWithString:@"0"];
|
||||
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
|
||||
NSString *text = [formatter stringFromNumber:zeroNumber];
|
||||
|
||||
UIImage* image = [self imageForLabelText:text];
|
||||
[self.labelImageCache setObject:image forKey:@(0)];
|
||||
}
|
||||
|
||||
- (UIImage*)imageForLabelText:(NSString*)text {
|
||||
self.prototypeLabel.text = text;
|
||||
[self.prototypeLabel setNeedsDisplay];
|
||||
[self.prototypeLabel sizeToFit];
|
||||
|
||||
// Now render
|
||||
UIGraphicsBeginImageContextWithOptions(self.prototypeLabel.bounds.size, NO, 0.0);
|
||||
[self.prototypeLabel.layer renderInContext: UIGraphicsGetCurrentContext()];
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return image;
|
||||
}
|
||||
|
||||
- (UIImage*)cachedLabelImageForDistance:(CLLocationDistance)barDistance {
|
||||
// Make a slightly nicer key, rather than something that's a double.
|
||||
NSUInteger floorDist = (NSUInteger)(barDistance*100);
|
||||
|
||||
NSNumber *key = @(floorDist);
|
||||
UIImage *cachedImage = [self.labelImageCache objectForKey:key];
|
||||
|
||||
if (cachedImage) {
|
||||
return cachedImage;
|
||||
}
|
||||
|
||||
// Calc it
|
||||
NSString *text = [self.formatter stringFromDistance:barDistance];
|
||||
UIImage *image = [self imageForLabelText:text];
|
||||
|
||||
[self.labelImageCache setObject:image forKey:key];
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
- (void)updateLabels {
|
||||
NSEnumerator<UIView*> *viewEnumerator = [self.labelViews objectEnumerator];
|
||||
NSInteger i = 0;
|
||||
CLLocationDistance multiplier = (self.row.distance / self.row.numberOfBars);
|
||||
|
||||
if (![self usesMetricSystem]) {
|
||||
multiplier /= MGLFeetPerMeter;
|
||||
}
|
||||
|
||||
for (; i <= self.row.numberOfBars; i++) {
|
||||
UIView *labelView = [viewEnumerator nextObject];
|
||||
labelView.hidden = NO;
|
||||
|
||||
CLLocationDistance barDistance = multiplier * i;
|
||||
UIImage *image = [self cachedLabelImageForDistance:barDistance];
|
||||
|
||||
labelView.layer.contents = (id)image.CGImage;
|
||||
labelView.layer.contentsScale = image.scale;
|
||||
}
|
||||
|
||||
// Hide the rest.
|
||||
for (; i < self.labelViews.count; i++) {
|
||||
UIView *labelView = [viewEnumerator nextObject];
|
||||
labelView.hidden = YES;
|
||||
}
|
||||
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 = round((CGRectGetWidth(self.bounds) - self.borderWidth * 2.0f) / self.bars.count);
|
||||
|
||||
@@ -357,11 +424,15 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
|
||||
CGFloat barWidth = round(self.bounds.size.width / self.bars.count);
|
||||
BOOL RTL = [self usesRightToLeftLayout];
|
||||
NSUInteger i = RTL ? self.bars.count : 0;
|
||||
for (MGLScaleBarLabel *label in self.labels) {
|
||||
for (UIView *label in self.labelViews) {
|
||||
CGFloat xPosition = round(barWidth * i - CGRectGetMidX(label.bounds) + self.borderWidth);
|
||||
label.frame = CGRectMake(xPosition, 0,
|
||||
CGRectGetWidth(label.bounds),
|
||||
CGRectGetHeight(label.bounds));
|
||||
CGFloat yPosition = round(0.5 * (CGRectGetMaxY(self.bounds) - MGLBarHeight));
|
||||
|
||||
CGRect frame = label.frame;
|
||||
frame.origin.x = xPosition;
|
||||
frame.origin.y = yPosition;
|
||||
label.frame = frame;
|
||||
|
||||
i = RTL ? i-1 : i+1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user