summaryrefslogtreecommitdiff
path: root/platform/ios
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios')
-rw-r--r--platform/ios/CHANGELOG.md1
-rw-r--r--platform/ios/src/MGLMapView.mm112
2 files changed, 113 insertions, 0 deletions
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index 2377da7cf1..e36fd46290 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -9,6 +9,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CON
- 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))
- An MGLAnnotation can be relocated by changing its `coordinate` property in a KVO-compliant way. An MGLMultiPoint cannot be relocated. ([#3835](https://github.com/mapbox/mapbox-gl-native/pull/3835))
- 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))
+- The compass, user dot, and visible annotations are now accessible to VoiceOver users. ([#1496](https://github.com/mapbox/mapbox-gl-native/pull/1496))
- The SDK is now localizable. No localizations are currently provided, other than English, but if you need a particular localization, you can install the SDK manually and drop a .lproj folder into the framework. ([#4783](https://github.com/mapbox/mapbox-gl-native/pull/4783))
- 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))
- Rendering now occurs on the main thread, fixing a hang when calling `-[MGLMapView styleURL]` before the map view has fully loaded or while the application is in the background. ([#2909](https://github.com/mapbox/mapbox-gl-native/pull/2909))
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 5b429d9d48..222bd8f22b 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -129,12 +129,23 @@ mbgl::Color MGLColorObjectFromUIColor(UIColor *color)
return {{ (float)r, (float)g, (float)b, (float)a }};
}
+@interface MGLAnnotationAccessibilityElement : UIAccessibilityElement
+
+@property (nonatomic) MGLAnnotationTag tag;
+
+@end
+
+@implementation MGLAnnotationAccessibilityElement
+
+@end
+
/// Lightweight container for metadata about an annotation, including the annotation itself.
class MGLAnnotationContext {
public:
id <MGLAnnotation> annotation;
/// The annotation’s image’s reuse identifier.
NSString *imageReuseIdentifier;
+ MGLAnnotationAccessibilityElement *accessibilityElement;
};
#pragma mark - Private -
@@ -1789,6 +1800,105 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
return frame;
}
+- (NSInteger)accessibilityElementCount
+{
+ std::vector<MGLAnnotationTag> visibleAnnotations = [self annotationTagsInRect:self.bounds];
+ return visibleAnnotations.size() + 2 /* compass, attributionButton */;
+}
+
+- (id)accessibilityElementAtIndex:(NSInteger)index
+{
+ std::vector<MGLAnnotationTag> visibleAnnotations = [self annotationTagsInRect:self.bounds];
+
+ // Ornaments
+ if (index == 0)
+ {
+ return self.compassView;
+ }
+ if (index > 0 && (NSUInteger)index == visibleAnnotations.size() + 1 /* compass */)
+ {
+ return self.attributionButton;
+ }
+
+ std::sort(visibleAnnotations.begin(), visibleAnnotations.end());
+ CGPoint centerPoint = self.contentCenter;
+ if (self.userTrackingMode != MGLUserTrackingModeNone)
+ {
+ centerPoint = self.userLocationAnnotationViewCenter;
+ }
+ CLLocationCoordinate2D currentCoordinate = [self convertPoint:centerPoint toCoordinateFromView:self];
+ std::sort(visibleAnnotations.begin(), visibleAnnotations.end(), [&](const MGLAnnotationTag tagA, const MGLAnnotationTag tagB) {
+ CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate];
+ CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate];
+ CLLocationDegrees deltaA = hypot(coordinateA.latitude - currentCoordinate.latitude,
+ coordinateA.longitude - currentCoordinate.longitude);
+ CLLocationDegrees deltaB = hypot(coordinateB.latitude - currentCoordinate.latitude,
+ coordinateB.longitude - currentCoordinate.longitude);
+ return deltaA < deltaB;
+ });
+
+ NSUInteger annotationIndex = MGLAnnotationTagNotFound;
+ if (index >= 0 && (NSUInteger)index < visibleAnnotations.size())
+ {
+ annotationIndex = index - 1 /* compass */;
+ }
+ MGLAnnotationTag annotationTag = visibleAnnotations[annotationIndex];
+ NSAssert(annotationTag != MGLAnnotationTagNotFound, @"Can’t get accessibility element for nonexistent or invisible annotation at index %li.", index);
+ NSAssert(_annotationContextsByAnnotationTag.count(annotationTag), @"Missing annotation for tag %u.", annotationTag);
+ MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
+ id <MGLAnnotation> annotation = annotationContext.annotation;
+ MGLAnnotationAccessibilityElement *element = annotationContext.accessibilityElement;
+
+ // Lazily create an accessibility element for the found annotation.
+ if ( ! element)
+ {
+ element = [[MGLAnnotationAccessibilityElement alloc] initWithAccessibilityContainer:self];
+ element.tag = annotationTag;
+ element.accessibilityTraits = UIAccessibilityTraitButton;
+ if ([annotation respondsToSelector:@selector(title)])
+ {
+ element.accessibilityLabel = annotation.title;
+ }
+ if ([annotation respondsToSelector:@selector(subtitle)])
+ {
+ element.accessibilityValue = annotation.subtitle;
+ }
+ annotationContext.accessibilityElement = element;
+ }
+
+ // Update the accessibility element’s frame.
+ MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
+ CGRect annotationFrame = [self frameOfImage:annotationImage.image centeredAtCoordinate:annotation.coordinate];
+ CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self);
+ element.accessibilityFrame = screenRect;
+
+ return element;
+}
+
+- (NSInteger)indexOfAccessibilityElement:(id)element
+{
+ if (element == self.compassView)
+ {
+ return 0;
+ }
+ if ( ! [element isKindOfClass:[MGLAnnotationAccessibilityElement class]] &&
+ element != self.attributionButton)
+ {
+ return NSNotFound;
+ }
+
+ std::vector<MGLAnnotationTag> visibleAnnotations = [self annotationTagsInRect:self.bounds];
+ if (element == self.attributionButton)
+ {
+ return visibleAnnotations.size();
+ }
+ std::sort(visibleAnnotations.begin(), visibleAnnotations.end());
+ auto foundElement = std::find(visibleAnnotations.begin(), visibleAnnotations.end(),
+ ((MGLAnnotationAccessibilityElement *)element).tag);
+ if (foundElement == visibleAnnotations.end()) return NSNotFound;
+ else return std::distance(visibleAnnotations.begin(), foundElement) + 1;
+}
+
#pragma mark - Geography -
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCenterCoordinate
@@ -2551,6 +2661,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
}
[self didChangeValueForKey:@"annotations"];
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
}
/// Initialize and return a default annotation image that depicts a round pin
@@ -2685,6 +2796,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
[self willChangeValueForKey:@"annotations"];
_mbglMap->removeAnnotations(annotationTagsToRemove);
[self didChangeValueForKey:@"annotations"];
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
}
}