summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--platform/ios/CHANGELOG.md1
-rw-r--r--platform/ios/app/MBXViewController.m92
-rw-r--r--platform/ios/include/MGLAnnotationImage.h2
-rw-r--r--platform/ios/src/MGLAnnotationImage.m2
-rw-r--r--platform/ios/src/MGLAnnotationImage_Private.h3
-rw-r--r--platform/ios/src/MGLMapView.mm142
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);
}