diff options
Diffstat (limited to 'platform/ios/src/MGLMapView.mm')
-rw-r--r-- | platform/ios/src/MGLMapView.mm | 272 |
1 files changed, 242 insertions, 30 deletions
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index a0c24eebe5..742e247fc5 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -37,6 +37,7 @@ #import "MGLUserLocationAnnotationView.h" #import "MGLUserLocation_Private.h" #import "MGLAnnotationImage_Private.h" +#import "MGLAnnotationView_Private.h" #import "MGLMapboxEvents.h" #import "MGLCompactCalloutView.h" @@ -167,6 +168,8 @@ public: /// The annotation’s image’s reuse identifier. NSString *imageReuseIdentifier; MGLAnnotationAccessibilityElement *accessibilityElement; + MGLAnnotationView *annotationView; + NSString *viewReuseIdentifier; }; /** An accessibility element representing the MGLMapView at large. */ @@ -240,10 +243,12 @@ public: BOOL _opaque; NS_MUTABLE_ARRAY_OF(NSURL *) *_bundledStyleURLs; - + MGLAnnotationContextMap _annotationContextsByAnnotationTag; /// Tag of the selected annotation. If the user location annotation is selected, this ivar is set to `MGLAnnotationTagNotFound`. MGLAnnotationTag _selectedAnnotationTag; + NS_MUTABLE_DICTIONARY_OF(NSString *, NS_MUTABLE_ARRAY_OF(MGLAnnotationView *) *) *_annotationViewReuseQueueByIdentifier; + BOOL _userLocationAnnotationIsSelected; /// Size of the rectangle formed by unioning the maximum slop area around every annotation image. CGSize _unionedAnnotationImageSize; @@ -272,6 +277,8 @@ public: BOOL _delegateHasLineWidthsForShapeAnnotations; MGLCompassDirectionFormatter *_accessibilityCompassFormatter; + + CGSize _largestAnnotationViewSize; } #pragma mark - Setup & Teardown - @@ -404,6 +411,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) // Set up annotation management and selection state. _annotationImagesByIdentifier = [NSMutableDictionary dictionary]; _annotationContextsByAnnotationTag = {}; + _annotationViewReuseQueueByIdentifier = [NSMutableDictionary dictionary]; _selectedAnnotationTag = MGLAnnotationTagNotFound; _annotationsNearbyLastTap = {}; @@ -1404,6 +1412,13 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) } return; } + + MGLAnnotationView *hitAnnotationView = [self annotationViewAtPoint:tapPoint]; + if (hitAnnotationView) + { + [self selectAnnotation:hitAnnotationView.annotation animated:YES]; + return; + } MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:tapPoint persistingResults:YES]; if (hitAnnotationTag != MGLAnnotationTagNotFound) @@ -2713,6 +2728,11 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) { return pair.second.annotation; }); + + annotations.erase(std::remove_if(annotations.begin(), annotations.end(), + [](const id <MGLAnnotation> annotation) { return annotation == nullptr; }), + annotations.end()); + return [NSArray arrayWithObjects:&annotations[0] count:annotations.size()]; } @@ -2763,8 +2783,11 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) std::vector<mbgl::PointAnnotation> points; std::vector<mbgl::ShapeAnnotation> shapes; - NSMutableArray *annotationImages = [NSMutableArray arrayWithCapacity:annotations.count]; + + NSMutableDictionary *annotationImagesForAnnotation = [NSMutableDictionary dictionary]; + NSMutableDictionary *annotationViewsForAnnotation = [NSMutableDictionary dictionary]; + BOOL delegateImplementsViewForAnnotation = [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)]; BOOL delegateImplementsImageForPoint = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)]; for (id <MGLAnnotation> annotation in annotations) @@ -2777,32 +2800,51 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) } else { - MGLAnnotationImage *annotationImage; - if (delegateImplementsImageForPoint) - { - annotationImage = [self.delegate mapView:self imageForAnnotation:annotation]; - } - if ( ! annotationImage) - { - annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName]; - } - if ( ! annotationImage) - { - annotationImage = self.defaultAnnotationImage; - } + MGLAnnotationView *annotationView; + NSString *symbolName; + NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation]; - NSString *symbolName = annotationImage.styleIconIdentifier; - if ( ! symbolName) + if (delegateImplementsViewForAnnotation) { - symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; - annotationImage.styleIconIdentifier = symbolName; + annotationView = [self annotationViewForAnnotation:annotation]; + if (annotationView) + { + annotationViewsForAnnotation[annotationValue] = annotationView; + annotationView.center = [self convertCoordinate:annotation.coordinate toPointToView:self]; + [self.glView addSubview:annotationView]; + } } - if ( ! self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) - { - [self installAnnotationImage:annotationImage]; + if ( ! annotationView) { + MGLAnnotationImage *annotationImage; + + if (delegateImplementsImageForPoint) + { + annotationImage = [self.delegate mapView:self imageForAnnotation:annotation]; + } + if ( ! annotationImage) + { + annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName]; + } + if ( ! annotationImage) + { + annotationImage = self.defaultAnnotationImage; + } + + symbolName = annotationImage.styleIconIdentifier; + + if ( ! symbolName) + { + symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; + annotationImage.styleIconIdentifier = symbolName; + } + if ( ! self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) + { + [self installAnnotationImage:annotationImage]; + } + + annotationImagesForAnnotation[annotationValue] = annotationImage; } - [annotationImages addObject:annotationImage]; points.emplace_back(MGLLatLngFromLocationCoordinate2D(annotation.coordinate), symbolName.UTF8String ?: ""); } @@ -2810,20 +2852,30 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) if (points.size()) { + // refactor this to build contexts above and just associate with tags here + std::vector<MGLAnnotationTag> annotationTags = _mbglMap->addPointAnnotations(points); for (size_t i = 0; i < annotationTags.size(); ++i) { - MGLAnnotationTag annotationTag = annotationTags[i]; - MGLAnnotationImage *annotationImage = annotationImages[i]; - annotationImage.styleIconIdentifier = @(points[i].icon.c_str()); - id <MGLAnnotation> annotation = annotations[i]; + id<MGLAnnotation> annotation = annotations[i]; + NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation]; MGLAnnotationContext context; context.annotation = annotation; - context.imageReuseIdentifier = annotationImage.reuseIdentifier; - _annotationContextsByAnnotationTag[annotationTag] = context; + MGLAnnotationImage *annotationImage = annotationImagesForAnnotation[annotationValue]; + + if (annotationImage) { + context.imageReuseIdentifier = annotationImage.reuseIdentifier; + } + MGLAnnotationView *annotationView = annotationViewsForAnnotation[annotationValue]; + if (annotationView) { + context.annotationView = annotationView; + context.viewReuseIdentifier = annotationView.reuseIdentifier; + } + MGLAnnotationTag annotationTag = annotationTags[i]; + _annotationContextsByAnnotationTag[annotationTag] = context; if ([annotation isKindOfClass:[NSObject class]]) { NSAssert(![annotation isKindOfClass:[MGLMultiPoint class]], @"Point annotation should not be MGLMultiPoint."); [(NSObject *)annotation addObserver:self forKeyPath:@"coordinate" options:0 context:(void *)(NSUInteger)annotationTag]; @@ -2864,6 +2916,20 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) return annotationImage; } +- (MGLAnnotationView *)annotationViewForAnnotation:(id<MGLAnnotation>)annotation +{ + MGLAnnotationView *annotationView = [self.delegate mapView:self viewForAnnotation:annotation]; + + if (annotationView) + { + annotationView.annotation = annotation; + CGRect bounds = UIEdgeInsetsInsetRect({ CGPointZero, annotationView.frame.size }, annotationView.alignmentRectInsets); + _largestAnnotationViewSize = CGSizeMake(bounds.size.width / 2.0, bounds.size.height / 2.0); + } + + return annotationView; +} + - (double)alphaForShapeAnnotation:(MGLShape *)annotation { if (_delegateHasAlphasForShapeAnnotations) @@ -2962,6 +3028,11 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) { continue; } + + MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); + MGLAnnotationView *annotationView = annotationContext.annotationView; + [annotationView removeFromSuperview]; + annotationTagsToRemove.push_back(annotationTag); if (annotationTag == _selectedAnnotationTag) @@ -3025,6 +3096,35 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) return self.annotationImagesByIdentifier[identifier]; } +- (nullable MGLAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier +{ + NSMutableArray *annotationViewReuseQueue = [self annotationViewReuseQueueForIdentifier:identifier]; + MGLAnnotationView *reusableView = annotationViewReuseQueue.firstObject; + [reusableView prepareForReuse]; + [annotationViewReuseQueue removeObject:reusableView]; + + return reusableView; +} + +- (MGLAnnotationView *)annotationViewAtPoint:(CGPoint)point +{ + std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:self.bounds]; + + for(auto const& annotationTag: annotationTags) + { + auto &annotationContext = _annotationContextsByAnnotationTag[annotationTag]; + MGLAnnotationView *annotationView = annotationContext.annotationView; + CGPoint convertedPoint = [self convertPoint:point toView:annotationView]; + + if ([annotationView pointInside:convertedPoint withEvent:nil]) + { + return annotationView; + } + } + + return nil; +} + /** Returns the tag of the annotation at the given point in the view. @@ -3067,6 +3167,8 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag]; NSAssert(annotation, @"Unknown annotation found nearby tap"); + MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag[annotationTag]; + MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; if ( ! annotationImage.enabled) { @@ -3076,8 +3178,10 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) // Filter out the annotation if the fattened finger didn’t land // within the image’s alignment rect. CGRect annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate]; + return !!!CGRectIntersectsRect(annotationRect, hitRect); }); + nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); } @@ -3224,10 +3328,26 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) if (annotationTag == MGLAnnotationTagNotFound && annotation != self.userLocation) { [self addAnnotation:annotation]; + annotationTag = [self annotationTagForAnnotation:annotation]; + if (annotationTag == MGLAnnotationTagNotFound) return; } - // The annotation can’t be selected if no part of it is hittable. + // By default attempt to use the GL annotation image frame as the positioning rect. CGRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag]; + + if (annotation != self.userLocation) + { + MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); + + if (annotationContext.annotationView) + { + // Annotations represented by views use the view frame as the positioning rect. + positioningRect = annotationContext.annotationView.frame; + } + } + + // The client can request that any annotation be selected (even ones that are offscreen). + // The annotation can’t be selected if no part of it is hittable. if ( ! CGRectIntersectsRect(positioningRect, self.bounds) && annotation != self.userLocation) { return; @@ -3320,6 +3440,8 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) /// and is appropriate for positioning a popover. - (CGRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annotationTag { + MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag[annotationTag]; + id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag]; if ( ! annotation) { @@ -3337,6 +3459,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) CGRect positioningRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate]; positioningRect.origin.x -= 0.5; + return CGRectInset(positioningRect, -MGLAnnotationImagePaddingForCallout, -MGLAnnotationImagePaddingForCallout); } @@ -4221,6 +4344,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) { [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:(change == mbgl::MapChangeDidFinishRenderingFrameFullyRendered)]; } + [self updateAnnotationViews]; break; } } @@ -4231,6 +4355,85 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) [self updateUserLocationAnnotationViewAnimatedWithDuration:0]; } +- (void)updateAnnotationViews +{ + BOOL delegateImplementsViewForAnnotation = [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)]; + + if (!delegateImplementsViewForAnnotation) + { + return; + } + + // Update all visible annotation views + std::set<MGLAnnotationTag> visibleTags; + std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:CGRectInset(self.bounds, -_largestAnnotationViewSize.width - MGLAnnotationUpdateViewportOutset.width, -_largestAnnotationViewSize.height - MGLAnnotationUpdateViewportOutset.width)]; + + for(auto const& annotationTag: annotationTags) + { + auto &annotationContext = _annotationContextsByAnnotationTag[annotationTag]; + id<MGLAnnotation> annotation = annotationContext.annotation; + + + // If there is no annotation view at this point, it means the context's view was reused by some + // other context so we need to reuse or make a new view. + if (!annotationContext.annotationView) + { + MGLAnnotationView *annotationView = [self annotationViewForAnnotation:annotation]; + + if (annotationView) + { + // If the annotation view has no superview it means it was never used before so add it + if (!annotationView.superview) + { + [self.glView addSubview:annotationView]; + } + + annotationContext.annotationView = annotationView; + } + } + + annotationContext.annotationView.center = [self convertCoordinate:annotation.coordinate toPointToView:self];; + visibleTags.insert(annotationTag); + } + + // Hide and add offscreen annotation views to reuse queue + for (auto &pair : _annotationContextsByAnnotationTag) + { + MGLAnnotationTag annotationTag = pair.first; + MGLAnnotationContext &annotationContext = pair.second; + MGLAnnotationView *annotationView = annotationContext.annotationView; + const bool tagIsNotVisible = visibleTags.find(annotationTag) == visibleTags.end(); + + // The call to `annotationTagsInRect:` (above) does not return the correct result when the + // map is tilted and the user is scrolling quickly. So, some annotation views get stuck in + // a limbo state where they are onscreen and put on the reuse queue. Hiding the views hides + // the bug until we fix the result of `annotationTagsInRect:`. + annotationView.hidden = tagIsNotVisible; + + if (annotationView && annotationView.reuseIdentifier && tagIsNotVisible) + { + [self enqueueAnnotationViewForAnnotationContext:annotationContext]; + } + } +} + +- (void)enqueueAnnotationViewForAnnotationContext:(MGLAnnotationContext &)annotationContext +{ + MGLAnnotationView *annotationView = annotationContext.annotationView; + + if (!annotationView) return; + + if (annotationContext.viewReuseIdentifier) + { + NSMutableArray *annotationViewReuseQueue = [self annotationViewReuseQueueForIdentifier:annotationContext.viewReuseIdentifier]; + if (![annotationViewReuseQueue containsObject:annotationView]) + { + [annotationViewReuseQueue addObject:annotationView]; + annotationContext.annotationView = nil; + } + } +} + - (void)updateUserLocationAnnotationViewAnimatedWithDuration:(NSTimeInterval)duration { MGLUserLocationAnnotationView *annotationView = self.userLocationAnnotationView; @@ -4492,6 +4695,15 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) views:views]]; } +- (NS_MUTABLE_ARRAY_OF(MGLAnnotationView *) *)annotationViewReuseQueueForIdentifier:(NSString *)identifier { + if (!_annotationViewReuseQueueByIdentifier[identifier]) + { + _annotationViewReuseQueueByIdentifier[identifier] = [NSMutableArray array]; + } + + return _annotationViewReuseQueueByIdentifier[identifier]; +} + class MBGLView : public mbgl::View { public: |