summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2016-02-05 08:04:07 -0800
committerMinh Nguyễn <mxn@1ec5.org>2016-04-19 14:24:59 -0700
commitbc9b52e0abe5d2616fe3f84c923e0083001e8732 (patch)
tree99537072a743711c73c55b82658af569fecf4b44 /platform
parent8703f1ef4bcf5cdbafb36a7ed5e7f2c1fc3333ce (diff)
downloadqtlocation-mapboxgl-bc9b52e0abe5d2616fe3f84c923e0083001e8732.tar.gz
[ios] Annotation image deletion
Added an API for deleting unused annotation images’ images. When you nil out the image of an MGLAnnotationImage, MGLMapView deletes the sprite from the style and recreates any annotation associated with the MGLAnnotationImage instance; the MGLAnnotationImage’s falls back to SDK’s default annotation image. In iosapp, deselecting an annotation resets its image to the default; deselecting it again restores the image. ref #3185
Diffstat (limited to 'platform')
-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);
}