From 9e4a16d7eec72f3de77e6382e68b16c41e932dab Mon Sep 17 00:00:00 2001 From: Julian Rex Date: Thu, 24 May 2018 10:29:56 -0400 Subject: [ios] Updated MGLScaleBar to use rendered UIImages instead of MGLScaleBarLabel (#11921) --- platform/ios/CHANGELOG.md | 1 + platform/ios/app/MBXViewController.m | 144 +++++++++++++++++++++++++- platform/ios/src/MGLScaleBar.mm | 191 ++++++++++++++++++++++++----------- 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 *labels; +@property (nonatomic) NSArray *labelViews; @property (nonatomic) NSArray *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 *)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 *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; } } -- cgit v1.2.1