Skip to content
This repository was archived by the owner on Aug 8, 2023. It is now read-only.

[ios] Guard against looking up annotation contexts MGLAnnotationTagNotFound #8686

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions platform/ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Mapbox welcomes participation and contributions from everyone. Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) to get started.

## 3.5.2

* Fixed an issue that caused a crash when the user annotation was presenting a callout view and the map was moved. ([#8686](https://github.com/mapbox/mapbox-gl-native/pull/8686))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we tend to call it the “user location annotation”, to make clear that it’s not some annotation otherwise belonging to the user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made that change for the iOS changelog in 99eee0a. In that same commit, I also made the macOS changelog less specific since the possibility of a crash there is more theoretical since there is currently no user location annotation.


## 3.5.1 - April 5, 2017

* Fixed an issue that caused the return type of a map view delegate method to bridge incorrectly to applications written in Swift. ([#8541](https://github.com/mapbox/mapbox-gl-native/pull/8541))
Expand Down
78 changes: 45 additions & 33 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1452,7 +1452,9 @@ - (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap
}
else
{
nextElement = _annotationContextsByAnnotationTag.at(_selectedAnnotationTag).accessibilityElement;
if (_selectedAnnotationTag != MGLAnnotationTagNotFound) {
nextElement = _annotationContextsByAnnotationTag.at(_selectedAnnotationTag).accessibilityElement;
}
}
[self deselectAnnotation:self.selectedAnnotation animated:YES];
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nextElement);
Expand Down Expand Up @@ -2002,19 +2004,21 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
{
const mbgl::Point<double> point = MGLPointFromLocationCoordinate2D(annotation.coordinate);

MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
if (annotationContext.annotationView)
{
// Redundantly move the associated annotation view outside the scope of the animation-less transaction block in -updateAnnotationViews.
annotationContext.annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self];
}
if (annotationTag != MGLAnnotationTagNotFound) {
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
if (annotationContext.annotationView)
{
// Redundantly move the associated annotation view outside the scope of the animation-less transaction block in -updateAnnotationViews.
annotationContext.annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self];
}

MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
NSString *symbolName = annotationImage.styleIconIdentifier;
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
NSString *symbolName = annotationImage.styleIconIdentifier;

// Update the annotation’s backing geometry to match the annotation model object. Any associated annotation view is also moved by side effect. However, -updateAnnotationViews disables the view’s animation actions, because it can’t distinguish between moves due to the viewport changing and moves due to the annotation’s coordinate changing.
_mbglMap->updateAnnotation(annotationTag, mbgl::SymbolAnnotation { point, symbolName.UTF8String });
[self updateCalloutView];
// Update the annotation’s backing geometry to match the annotation model object. Any associated annotation view is also moved by side effect. However, -updateAnnotationViews disables the view’s animation actions, because it can’t distinguish between moves due to the viewport changing and moves due to the annotation’s coordinate changing.
_mbglMap->updateAnnotation(annotationTag, mbgl::SymbolAnnotation { point, symbolName.UTF8String });
[self updateCalloutView];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is correct with respect to the user location annotation, because we rely on Core Location, not KVO, to tell us when the model object has changed.

}
}
}
else if ([keyPath isEqualToString:@"coordinates"] && [object isKindOfClass:[MGLMultiPoint class]])
Expand Down Expand Up @@ -3055,17 +3059,18 @@ - (void)removeStyleClass:(NSString *)styleClass

for (auto const& annotationTag: annotationTags)
{
if (!_annotationContextsByAnnotationTag.count(annotationTag))
if (!_annotationContextsByAnnotationTag.count(annotationTag) ||
annotationTag == MGLAnnotationTagNotFound)
{
continue;
}

MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
NSAssert(annotationContext.annotation, @"Missing annotation for tag %u.", annotationTag);
if (annotationContext.annotation)
{
[annotations addObject:annotationContext.annotation];
}

}

return [annotations copy];
Expand All @@ -3077,8 +3082,8 @@ - (void)removeStyleClass:(NSString *)styleClass
/// Returns the annotation assigned the given tag. Cheap.
- (id <MGLAnnotation>)annotationWithTag:(MGLAnnotationTag)tag
{
if ( ! _annotationContextsByAnnotationTag.count(tag))
{
if ( ! _annotationContextsByAnnotationTag.count(tag) ||
tag == MGLAnnotationTagNotFound) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return userLocation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind. The code used to overload MGLAnnotationTagNotFound to mean the user location annotation tag. But in retrospect that's a pretty risky practice. This change moves us further away from that practice, forcing us to explicitly account for the user location annotation explicitly, which is a good thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return nil;
}

Expand Down Expand Up @@ -3550,6 +3555,10 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)
auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(),
[&](const MGLAnnotationTag annotationTag)
{
if (annotationTag == MGLAnnotationTagNotFound) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant to the check in -annotationWithTag:. Should we put an assertion in right above for this case? Is it legitimate for the user location annotation to be a nearby annotation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to answer my own question here: -annotationTagsInRect: wouldn’t know about the user location annotation, so it should never return MGLAnnotationTagNotFound. I still think we should either rely on the assertion below or assert here about MGLAnnotationTagNotFound, in case some sort of internal consistency leads it to show up in nearbyAnnotations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I'll remove this check to rely on the assertion below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this is the assertion we hit in debug builds if annotations are removed at almost the same time and just barely before the user tries to select them (part of the repro from #8163 (comment)).

The check below recently added in #8637 to make sure the annotation is not nil protects release builds from crashing at the call to std::map::at also just below.

return true;
}

id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
NSAssert(annotation, @"Unknown annotation found nearby tap");
if ( ! annotation)
Expand Down Expand Up @@ -3680,10 +3689,12 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)
{
return self.userLocation;
}
if ( ! _annotationContextsByAnnotationTag.count(_selectedAnnotationTag))
{

if ( ! _annotationContextsByAnnotationTag.count(_selectedAnnotationTag) ||
_selectedAnnotationTag == MGLAnnotationTagNotFound) {
return nil;
}

MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(_selectedAnnotationTag);
return annotationContext.annotation;
}
Expand Down Expand Up @@ -3749,19 +3760,16 @@ - (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated
MGLAnnotationView *annotationView = nil;

if (annotation != self.userLocation)
{
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);

annotationView = annotationContext.annotationView;

if (annotationView && annotationView.enabled)
{
// Annotations represented by views use the view frame as the positioning rect.
positioningRect = annotationView.frame;

[annotationView.superview bringSubviewToFront:annotationView];

[annotationView setSelected:YES animated:animated];
if (annotationTag != MGLAnnotationTagNotFound) {
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
annotationView = annotationContext.annotationView;
if (annotationView && annotationView.enabled) {
{
// Annotations represented by views use the view frame as the positioning rect.
positioningRect = annotationView.frame;
[annotationView.superview bringSubviewToFront:annotationView];
[annotationView setSelected:YES animated:animated];
}
}
}

Expand Down Expand Up @@ -5039,8 +5047,12 @@ - (void)updateCalloutView
if (isAnchoredToAnnotation)
{
MGLAnnotationTag tag = [self annotationTagForAnnotation:annotation];
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(tag);
MGLAnnotationView *annotationView = annotationContext.annotationView;
MGLAnnotationView *annotationView = nil;

if (tag != MGLAnnotationTagNotFound) {
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(tag);
annotationView = annotationContext.annotationView;
}

CGRect rect = [self positioningRectForCalloutForAnnotationWithTag:tag];

Expand Down
1 change: 1 addition & 0 deletions platform/macos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 0.4.1

* Fixed an issue causing code signing failures and bloating the framework. ([#8640](https://github.com/mapbox/mapbox-gl-native/pull/8640))
* Fixed an issue that caused a crash when the user annotation was presenting a callout view and the map was moved. ([#8686](https://github.com/mapbox/mapbox-gl-native/pull/8686))
* Renamed the "Data-Driven Styling" guide to "Using Style Functions at Runtime" and clarified the meaning of data-driven styling in the guide's discussion of runtime style functions. ([#8627](https://github.com/mapbox/mapbox-gl-native/pull/8627))

## 0.4.0
Expand Down
11 changes: 8 additions & 3 deletions platform/macos/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1796,10 +1796,12 @@ - (IBAction)giveFeedback:(id)sender {

for (auto const& annotationTag: annotationTags)
{
if (!_annotationContextsByAnnotationTag.count(annotationTag))
if (!_annotationContextsByAnnotationTag.count(annotationTag) ||
annotationTag == MGLAnnotationTagNotFound)
{
continue;
}

MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
NSAssert(annotationContext.annotation, @"Missing annotation for tag %u.", annotationTag);
if (annotationContext.annotation)
Expand All @@ -1816,7 +1818,8 @@ - (IBAction)giveFeedback:(id)sender {

/// Returns the annotation assigned the given tag. Cheap.
- (id <MGLAnnotation>)annotationWithTag:(MGLAnnotationTag)tag {
if (!_annotationContextsByAnnotationTag.count(tag)) {
if ( ! _annotationContextsByAnnotationTag.count(tag) ||
tag == MGLAnnotationTagNotFound) {
return nil;
}

Expand Down Expand Up @@ -2147,9 +2150,11 @@ - (MGLAnnotationTag)annotationTagAtPoint:(NSPoint)point persistingResults:(BOOL)
}

- (id <MGLAnnotation>)selectedAnnotation {
if (!_annotationContextsByAnnotationTag.count(_selectedAnnotationTag)) {
if ( ! _annotationContextsByAnnotationTag.count(_selectedAnnotationTag) ||
_selectedAnnotationTag == MGLAnnotationTagNotFound) {
return nil;
}

MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(_selectedAnnotationTag);
return annotationContext.annotation;
}
Expand Down