summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Rex <julian.rex@gmail.com>2018-05-24 10:29:56 -0400
committerGitHub <noreply@github.com>2018-05-24 10:29:56 -0400
commit9e4a16d7eec72f3de77e6382e68b16c41e932dab (patch)
treec1a8b02245f43b04e07be5e9d488eb336e9649b6
parent4067a61c80b90e656f129f34f4479fa0c7262b91 (diff)
downloadqtlocation-mapboxgl-9e4a16d7eec72f3de77e6382e68b16c41e932dab.tar.gz
[ios] Updated MGLScaleBar to use rendered UIImages instead of MGLScaleBarLabel (#11921)
-rw-r--r--platform/ios/CHANGELOG.md1
-rw-r--r--platform/ios/app/MBXViewController.m144
-rw-r--r--platform/ios/src/MGLScaleBar.mm191
3 files changed, 273 insertions, 63 deletions
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index 7c8181a65a..1cf5bf1e66 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -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
diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m
index 0cb6c73d31..a1479b4538 100644
--- a/platform/ios/app/MBXViewController.m
+++ b/platform/ios/app/MBXViewController.m
@@ -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];
}
diff --git a/platform/ios/src/MGLScaleBar.mm b/platform/ios/src/MGLScaleBar.mm
index a2fc24c75c..d69fb3e852 100644
--- a/platform/ios/src/MGLScaleBar.mm
+++ b/platform/ios/src/MGLScaleBar.mm
@@ -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]);
+ }
+ 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;
}
-
- return 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;
}
}