diff options
-rw-r--r-- | platform/ios/CHANGELOG.md | 1 | ||||
-rw-r--r-- | platform/ios/app/MBXViewController.m | 92 | ||||
-rw-r--r-- | platform/ios/include/MGLAnnotationImage.h | 2 | ||||
-rw-r--r-- | platform/ios/src/MGLAnnotationImage.m | 2 | ||||
-rw-r--r-- | platform/ios/src/MGLAnnotationImage_Private.h | 3 | ||||
-rw-r--r-- | platform/ios/src/MGLMapView.mm | 142 |
6 files changed, 172 insertions, 70 deletions
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index b6734a0fd9..c52e00be56 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -8,6 +8,7 @@ Mapbox welcomes participation and contributions from everyone. If you’d like - Removed the `armv7s` slice from the SDK to reduce its size. iPhone 5 and iPhone 5c automatically use the `armv7` slice instead. ([#4641](https://github.com/mapbox/mapbox-gl-native/pull/4641)) - The user dot now moves smoothly between user location updates while user location tracking is disabled. ([#1582](https://github.com/mapbox/mapbox-gl-native/pull/1582)) - User location heading updates now resume properly when an app becomes active again. ([#4674](https://github.com/mapbox/mapbox-gl-native/pull/4674)) +- Setting the `image` property of an MGLAnnotationImage to `nil` resets it to the default red pin image and reclaims resources that can be used to customize additional annotations. ([#3835](https://github.com/mapbox/mapbox-gl-native/pull/3835)) - Fixed an issue preventing KVO change notifications from being generated on MGLMapView’s `userTrackingMode` key path when `-setUserTrackingMode:animated:` is called. ([#4724](https://github.com/mapbox/mapbox-gl-native/pull/4724)) - Fixed a hang that could occur if the host application attempts to set user defaults on a background queue. ([#4745](https://github.com/mapbox/mapbox-gl-native/pull/4745)) - Added a `-reloadStyle:` action to MGLMapView to force a reload of the current style. ([#4728](https://github.com/mapbox/mapbox-gl-native/pull/4728)) diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index de9565091e..687bf64953 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -594,52 +594,70 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = { if (!title.length) return nil; NSString *lastTwoCharacters = [title substringFromIndex:title.length - 2]; - UIColor *color; + MGLAnnotationImage *annotationImage = [mapView dequeueReusableAnnotationImageWithIdentifier:lastTwoCharacters]; - // make every tenth annotation blue - if ([lastTwoCharacters hasSuffix:@"0"]) { - color = [UIColor blueColor]; - } else { - color = [UIColor redColor]; - } - - MGLAnnotationImage *image = [mapView dequeueReusableAnnotationImageWithIdentifier:lastTwoCharacters]; - - if ( ! image) + if ( ! annotationImage) { - CGRect rect = CGRectMake(0, 0, 20, 15); - - UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); - - CGContextRef ctx = UIGraphicsGetCurrentContext(); - - CGContextSetFillColorWithColor(ctx, [[color colorWithAlphaComponent:0.75] CGColor]); - CGContextFillRect(ctx, rect); - - CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]); - CGContextStrokeRectWithWidth(ctx, rect, 2); - - NSAttributedString *drawString = [[NSAttributedString alloc] initWithString:lastTwoCharacters attributes:@{ - NSFontAttributeName: [UIFont fontWithName:@"Arial-BoldMT" size:12], - NSForegroundColorAttributeName: [UIColor whiteColor] }]; - CGSize stringSize = drawString.size; - CGRect stringRect = CGRectMake((rect.size.width - stringSize.width) / 2, - (rect.size.height - stringSize.height) / 2, - stringSize.width, - stringSize.height); - [drawString drawInRect:stringRect]; - - image = [MGLAnnotationImage annotationImageWithImage:UIGraphicsGetImageFromCurrentImageContext() reuseIdentifier:lastTwoCharacters]; + UIColor *color; + + // make every tenth annotation blue + if ([lastTwoCharacters hasSuffix:@"0"]) { + color = [UIColor blueColor]; + } else { + color = [UIColor redColor]; + } + + UIImage *image = [self imageWithText:lastTwoCharacters backgroundColor:color]; + annotationImage = [MGLAnnotationImage annotationImageWithImage:image reuseIdentifier:lastTwoCharacters]; // don't allow touches on blue annotations - if ([color isEqual:[UIColor blueColor]]) image.enabled = NO; - - UIGraphicsEndImageContext(); + if ([color isEqual:[UIColor blueColor]]) annotationImage.enabled = NO; } + return annotationImage; +} + +- (UIImage *)imageWithText:(NSString *)text backgroundColor:(UIColor *)color +{ + CGRect rect = CGRectMake(0, 0, 20, 15); + + UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); + + CGContextRef ctx = UIGraphicsGetCurrentContext(); + + CGContextSetFillColorWithColor(ctx, [[color colorWithAlphaComponent:0.75] CGColor]); + CGContextFillRect(ctx, rect); + + CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]); + CGContextStrokeRectWithWidth(ctx, rect, 2); + + NSAttributedString *drawString = [[NSAttributedString alloc] initWithString:text attributes:@{ + NSFontAttributeName: [UIFont fontWithName:@"Arial-BoldMT" size:12], + NSForegroundColorAttributeName: [UIColor whiteColor], + }]; + CGSize stringSize = drawString.size; + CGRect stringRect = CGRectMake((rect.size.width - stringSize.width) / 2, + (rect.size.height - stringSize.height) / 2, + stringSize.width, + stringSize.height); + [drawString drawInRect:stringRect]; + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); return image; } +- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id<MGLAnnotation>)annotation { + NSString *title = [(MGLPointAnnotation *)annotation title]; + if ( ! title.length) + { + return; + } + NSString *lastTwoCharacters = [title substringFromIndex:title.length - 2]; + MGLAnnotationImage *annotationImage = [mapView dequeueReusableAnnotationImageWithIdentifier:lastTwoCharacters]; + annotationImage.image = annotationImage.image ? nil : [self imageWithText:lastTwoCharacters backgroundColor:[UIColor grayColor]]; +} + - (BOOL)mapView:(__unused MGLMapView *)mapView annotationCanShowCallout:(__unused id <MGLAnnotation>)annotation { return YES; diff --git a/platform/ios/include/MGLAnnotationImage.h b/platform/ios/include/MGLAnnotationImage.h index f9d9e70566..fa2adb3830 100644 --- a/platform/ios/include/MGLAnnotationImage.h +++ b/platform/ios/include/MGLAnnotationImage.h @@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark Getting and Setting Attributes /** The image to be displayed for the annotation. */ -@property (nonatomic, strong) UIImage *image; +@property (nonatomic, strong, nullable) UIImage *image; /** The string that identifies that this annotation image is reusable. (read-only) diff --git a/platform/ios/src/MGLAnnotationImage.m b/platform/ios/src/MGLAnnotationImage.m index 374ed162fb..e1085be98d 100644 --- a/platform/ios/src/MGLAnnotationImage.m +++ b/platform/ios/src/MGLAnnotationImage.m @@ -3,6 +3,8 @@ @interface MGLAnnotationImage () @property (nonatomic, strong) NSString *reuseIdentifier; +@property (nonatomic, strong, nullable) NSString *styleIconIdentifier; + @property (nonatomic, weak) id<MGLAnnotationImageDelegate> delegate; @end diff --git a/platform/ios/src/MGLAnnotationImage_Private.h b/platform/ios/src/MGLAnnotationImage_Private.h index f22a9ac4e2..dcd8a49bf9 100644 --- a/platform/ios/src/MGLAnnotationImage_Private.h +++ b/platform/ios/src/MGLAnnotationImage_Private.h @@ -11,6 +11,9 @@ NS_ASSUME_NONNULL_BEGIN @interface MGLAnnotationImage (Private) +/// Unique identifier of the sprite image used by the style to represent the receiver’s `image`. +@property (nonatomic, strong, nullable) NSString *styleIconIdentifier; + @property (nonatomic, weak) id<MGLAnnotationImageDelegate> delegate; @end diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 347ebb1cb6..526031f201 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -135,9 +135,8 @@ mbgl::Color MGLColorObjectFromUIColor(UIColor *color) class MGLAnnotationContext { public: id <MGLAnnotation> annotation; - /// mbgl-given identifier for the annotation image used by this annotation. - /// Based on the annotation image’s reusable identifier. - NSString *symbolIdentifier; + /// The annotation’s image’s reuse identifier. + NSString *imageReuseIdentifier; }; #pragma mark - Private - @@ -2371,11 +2370,17 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) - (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations { + [self addAnnotations:annotations withAnnotationImage:nil]; +} + +- (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations withAnnotationImage:(MGLAnnotationImage *)explicitAnnotationImage +{ if ( ! annotations) return; [self willChangeValueForKey:@"annotations"]; std::vector<mbgl::PointAnnotation> points; std::vector<mbgl::ShapeAnnotation> shapes; + NSMutableArray *annotationImages = [NSMutableArray arrayWithCapacity:annotations.count]; BOOL delegateImplementsImageForPoint = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)]; @@ -2389,31 +2394,32 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) } else { - MGLAnnotationImage *annotationImage = delegateImplementsImageForPoint ? [self.delegate mapView:self imageForAnnotation:annotation] : nil; + MGLAnnotationImage *annotationImage = explicitAnnotationImage; + if ( ! annotationImage && delegateImplementsImageForPoint) + { + annotationImage = [self.delegate mapView:self imageForAnnotation:annotation]; + } if ( ! annotationImage) { annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName]; } if ( ! annotationImage) { - // Create a default annotation image that depicts a round pin - // rising from the center, with a shadow slightly below center. - // The alignment rect therefore excludes the bottom half. - UIImage *defaultAnnotationImage = [MGLMapView resourceImageNamed:MGLDefaultStyleMarkerSymbolName]; - defaultAnnotationImage = [defaultAnnotationImage imageWithAlignmentRectInsets: - UIEdgeInsetsMake(0, 0, defaultAnnotationImage.size.height / 2, 0)]; - annotationImage = [MGLAnnotationImage annotationImageWithImage:defaultAnnotationImage - reuseIdentifier:MGLDefaultStyleMarkerSymbolName]; + annotationImage = self.defaultAnnotationImage; + } + + NSString *symbolName = annotationImage.styleIconIdentifier; + if ( ! symbolName) + { + symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; + annotationImage.styleIconIdentifier = symbolName; } if ( ! self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) { - self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; [self installAnnotationImage:annotationImage]; - annotationImage.delegate = self; } - - NSString *symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; + [annotationImages addObject:annotationImage]; points.emplace_back(MGLLatLngFromLocationCoordinate2D(annotation.coordinate), symbolName ? [symbolName UTF8String] : ""); } @@ -2425,9 +2431,12 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) for (size_t i = 0; i < pointAnnotationTags.size(); ++i) { + MGLAnnotationImage *annotationImage = annotationImages[i]; + annotationImage.styleIconIdentifier = @(points[i].icon.c_str()); + MGLAnnotationContext context; context.annotation = annotations[i]; - context.symbolIdentifier = @(points[i].icon.c_str()); + context.imageReuseIdentifier = annotationImage.reuseIdentifier; _annotationContextsByAnnotationTag[pointAnnotationTags[i]] = context; } } @@ -2447,6 +2456,20 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) [self didChangeValueForKey:@"annotations"]; } +/// Initialize and return a default annotation image that depicts a round pin +/// rising from the center, with a shadow slightly below center. The alignment +/// rect therefore excludes the bottom half. +- (MGLAnnotationImage *)defaultAnnotationImage +{ + UIImage *image = [MGLMapView resourceImageNamed:MGLDefaultStyleMarkerSymbolName]; + image = [image imageWithAlignmentRectInsets: + UIEdgeInsetsMake(0, 0, image.size.height / 2, 0)]; + MGLAnnotationImage *annotationImage = [MGLAnnotationImage annotationImageWithImage:image + reuseIdentifier:MGLDefaultStyleMarkerSymbolName]; + annotationImage.styleIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; + return annotationImage; +} + - (double)alphaForShapeAnnotation:(MGLShape *)annotation { if (_delegateHasAlphasForShapeAnnotations) @@ -2483,6 +2506,10 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) - (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage { + NSString *iconIdentifier = annotationImage.styleIconIdentifier; + self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; + annotationImage.delegate = self; + // retrieve pixels CGImageRef image = annotationImage.image.CGImage; size_t width = CGImageGetWidth(image); @@ -2503,8 +2530,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) float(annotationImage.image.scale)); // sprite upload - NSString *symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; - _mbglMap->addAnnotationIcon(symbolName.UTF8String, cSpriteImage); + _mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, cSpriteImage); // Create a slop area with a “radius” equal in size to the annotation // image’s alignment rect, allowing the eventual tap to be on any point @@ -2596,12 +2622,6 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) - (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier { - // This prefix is used to avoid collisions with style-defined sprites in - // mbgl, but reusable identifiers are never prefixed. - if ([identifier hasPrefix:MGLAnnotationSpritePrefix]) - { - identifier = [identifier substringFromIndex:MGLAnnotationSpritePrefix.length]; - } return self.annotationImagesByIdentifier[identifier]; } @@ -2636,6 +2656,9 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) -MGLAnnotationImagePaddingForHitTest, -MGLAnnotationImagePaddingForHitTest); + MGLAnnotationImage *fallbackAnnotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName]; + UIImage *fallbackImage = fallbackAnnotationImage.image; + // Filter out any annotation whose image is unselectable or for which // hit testing fails. auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), @@ -2650,10 +2673,11 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) return true; } + UIImage *image = annotationImage.image ? annotationImage.image : fallbackImage; + // Filter out the annotation if the fattened finger didn’t land // within the image’s alignment rect. - CGRect annotationRect = [self frameOfImage:annotationImage.image - centeredAtCoordinate:annotation.coordinate]; + CGRect annotationRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate]; return !!!CGRectIntersectsRect(annotationRect, hitRect); }); nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); @@ -2905,6 +2929,10 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) UIImage *image = [self imageOfAnnotationWithTag:annotationTag].image; if ( ! image) { + image = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName].image; + } + if ( ! image) + { return CGRectZero; } @@ -2932,7 +2960,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) return nil; } - NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).symbolIdentifier; + NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier; NSString *symbolName = customSymbol.length ? customSymbol : MGLDefaultStyleMarkerSymbolName; return [self dequeueReusableAnnotationImageWithIdentifier:symbolName]; @@ -3026,10 +3054,60 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) - (void)annotationImageNeedsRedisplay:(MGLAnnotationImage *)annotationImage { - // remove sprite - NSString *symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; - _mbglMap->removeAnnotationIcon(symbolName.UTF8String); - [self installAnnotationImage:annotationImage]; + NSString *reuseIdentifier = annotationImage.reuseIdentifier; + NSString *iconIdentifier = annotationImage.styleIconIdentifier; + NSString *fallbackReuseIdentifier = MGLDefaultStyleMarkerSymbolName; + NSString *fallbackIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:fallbackReuseIdentifier]; + + // Remove the old icon from the style. + if ( ! [iconIdentifier isEqualToString:fallbackIconIdentifier]) { + _mbglMap->removeAnnotationIcon(iconIdentifier.UTF8String); + } + + if (annotationImage.image) + { + // Add the new icon to the style. + annotationImage.styleIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; + [self installAnnotationImage:annotationImage]; + + if ([iconIdentifier isEqualToString:fallbackIconIdentifier]) + { + // Remove any annotations associated with the annotation image. + NSMutableArray *annotationsToRecreate = [NSMutableArray array]; + for (auto &pair : _annotationContextsByAnnotationTag) + { + if ([pair.second.imageReuseIdentifier isEqualToString:reuseIdentifier]) + { + [annotationsToRecreate addObject:pair.second.annotation]; + } + } + [self removeAnnotations:annotationsToRecreate]; + + // Recreate the annotations with the new icon. + [self addAnnotations:annotationsToRecreate withAnnotationImage:annotationImage]; + } + } + else + { + // Remove any annotations associated with the annotation image. + NSMutableArray *annotationsToRecreate = [NSMutableArray array]; + for (auto &pair : _annotationContextsByAnnotationTag) + { + if ([pair.second.imageReuseIdentifier isEqualToString:reuseIdentifier]) + { + [annotationsToRecreate addObject:pair.second.annotation]; + } + } + [self removeAnnotations:annotationsToRecreate]; + + // Recreate the annotations, falling back to the default icon. + annotationImage.styleIconIdentifier = fallbackIconIdentifier; + if ( ! [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName]) + { + [self installAnnotationImage:self.defaultAnnotationImage]; + } + [self addAnnotations:annotationsToRecreate withAnnotationImage:annotationImage]; + } _mbglMap->update(mbgl::Update::Annotations); } |