-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[ios, macos] Add selection support to MGLMultiPoint annotations. #9984
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3257,6 +3257,12 @@ - (void)removeStyleClass:(NSString *)styleClass | |
} | ||
|
||
std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:rect]; | ||
std::vector<MGLAnnotationTag> shapeAnnotationTags = [self shapeAnnotationTagsInRect:rect]; | ||
|
||
if (shapeAnnotationTags.size()) { | ||
annotationTags.insert(annotationTags.end(), shapeAnnotationTags.begin(), shapeAnnotationTags.end()); | ||
} | ||
|
||
if (annotationTags.size()) | ||
{ | ||
NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()]; | ||
|
@@ -3762,61 +3768,69 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL) | |
queryRect = CGRectInset(queryRect, -MGLAnnotationImagePaddingForHitTest, | ||
-MGLAnnotationImagePaddingForHitTest); | ||
std::vector<MGLAnnotationTag> nearbyAnnotations = [self annotationTagsInRect:queryRect]; | ||
BOOL queryingShapeAnnotations = NO; | ||
|
||
if (!nearbyAnnotations.size()) { | ||
nearbyAnnotations = [self shapeAnnotationTagsInRect:queryRect]; | ||
queryingShapeAnnotations = YES; | ||
} | ||
|
||
if (nearbyAnnotations.size()) | ||
{ | ||
// Assume that the user is fat-fingering an annotation. | ||
CGRect hitRect = CGRectInset({ point, CGSizeZero }, | ||
-MGLAnnotationImagePaddingForHitTest, | ||
-MGLAnnotationImagePaddingForHitTest); | ||
|
||
// Filter out any annotation whose image or view is unselectable or for which | ||
// hit testing fails. | ||
auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), | ||
[&](const MGLAnnotationTag annotationTag) | ||
{ | ||
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag]; | ||
NSAssert(annotation, @"Unknown annotation found nearby tap"); | ||
if ( ! annotation) | ||
{ | ||
return true; | ||
} | ||
|
||
MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); | ||
CGRect annotationRect; | ||
|
||
MGLAnnotationView *annotationView = annotationContext.annotationView; | ||
if (annotationView) | ||
{ | ||
if ( ! annotationView.enabled) | ||
|
||
if (!queryingShapeAnnotations) { | ||
// Filter out any annotation whose image or view is unselectable or for which | ||
// hit testing fails. | ||
auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag) { | ||
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag]; | ||
NSAssert(annotation, @"Unknown annotation found nearby tap"); | ||
if ( ! annotation) | ||
{ | ||
return true; | ||
} | ||
|
||
CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self]; | ||
CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2); | ||
annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets); | ||
} | ||
else | ||
{ | ||
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; | ||
if ( ! annotationImage.enabled) | ||
MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); | ||
CGRect annotationRect; | ||
|
||
MGLAnnotationView *annotationView = annotationContext.annotationView; | ||
|
||
if (annotationView) | ||
{ | ||
return true; | ||
if ( ! annotationView.enabled) | ||
{ | ||
return true; | ||
} | ||
|
||
CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self]; | ||
CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2); | ||
annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets); | ||
} | ||
else | ||
{ | ||
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; | ||
if ( ! annotationImage.enabled) | ||
{ | ||
return true; | ||
} | ||
|
||
MGLAnnotationImage *fallbackAnnotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName]; | ||
UIImage *fallbackImage = fallbackAnnotationImage.image; | ||
MGLAnnotationImage *fallbackAnnotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName]; | ||
UIImage *fallbackImage = fallbackAnnotationImage.image; | ||
|
||
annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate]; | ||
} | ||
annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate]; | ||
} | ||
|
||
// Filter out the annotation if the fattened finger didn’t land | ||
// within the image’s alignment rect. | ||
return !!!CGRectIntersectsRect(annotationRect, hitRect); | ||
}); | ||
// Filter out the annotation if the fattened finger didn’t land | ||
// within the image’s alignment rect. | ||
return !!!CGRectIntersectsRect(annotationRect, hitRect); | ||
}); | ||
|
||
nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); | ||
} | ||
|
||
nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); | ||
} | ||
|
||
MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound; | ||
|
@@ -3899,6 +3913,14 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL) | |
}); | ||
} | ||
|
||
- (std::vector<MGLAnnotationTag>)shapeAnnotationTagsInRect:(CGRect)rect | ||
{ | ||
return _rendererFrontend->getRenderer()->queryShapeAnnotations({ | ||
{ CGRectGetMinX(rect), CGRectGetMinY(rect) }, | ||
{ CGRectGetMaxX(rect), CGRectGetMaxY(rect) }, | ||
}); | ||
} | ||
|
||
- (id <MGLAnnotation>)selectedAnnotation | ||
{ | ||
if (_userLocationAnnotationIsSelected) | ||
|
@@ -3950,8 +3972,6 @@ - (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated | |
{ | ||
if ( ! annotation) return; | ||
|
||
if ([annotation isKindOfClass:[MGLMultiPoint class]]) return; | ||
|
||
if (annotation == self.selectedAnnotation) return; | ||
|
||
[self deselectAnnotation:self.selectedAnnotation animated:NO]; | ||
|
@@ -4095,6 +4115,13 @@ - (CGRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annota | |
{ | ||
return CGRectZero; | ||
} | ||
|
||
if ([annotation isKindOfClass:[MGLMultiPoint class]]) { | ||
CLLocationCoordinate2D origin = annotation.coordinate; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the coordinate lies outside the current viewport, we should move it somewhere within the current viewport. This might require us to clip the geometry to the viewport (in a temporary new shape object) before calculating its centroid. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have an API for clipping? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. turf-difference returns the difference between two polygons (in this case, the shape and the visible coordinate rect). But we don’t have a Swift port at the ready. mbgl may be using some clipping utilities that we can take advantage of. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm going to cut a ticket for this. I will add a new API for clipping. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason I brought this up is that the callout might end up being completely invisible, making the user think the map did nothing in response to the tap. But it does make sense to treat the clipping behavior as tail work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It just occurred to me that the best fix for this issue would be to automatically pan to the selected shape annotation’s centroid, to show as much of the shape as possible, before showing the callout. We could fix #3249 at the same time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also @frederoni pointed that it may be useful to set the callout anchor to where the user tapped in case the centroid is off screen. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anchoring the callout at the tap location would also be reasonable. If I’m not mistaken, that’s how polygon selection works in Leaflet, so there’s precedent for it. #3249 would still be desirable, but we can do that separately. |
||
CGPoint originPoint = [self convertCoordinate:origin toPointToView:self]; | ||
return CGRectMake(originPoint.x, originPoint.y, MGLAnnotationImagePaddingForHitTest, MGLAnnotationImagePaddingForHitTest); | ||
|
||
} | ||
UIImage *image = [self imageOfAnnotationWithTag:annotationTag].image; | ||
if ( ! image) | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is both a point annotation and a shape annotation beneath the passed-in point, this method should return the point annotation the first time this method is called and the shape annotation the second time, as long as
persist
is set toYES
. That way the shape annotation remains accessible even when there is an overlapping point annotation./ref #9984 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m OK with this detail being tail work, by the way. Overall, this PR is looking good, especially after the last few changes in core.