summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2015-05-11 03:16:37 -0700
committerMinh Nguyễn <mxn@1ec5.org>2016-04-25 00:17:02 -0700
commitbabc43ea6289a5ec6eee9dbf80057ab2de742dfd (patch)
treebd6c448dacd5f5f02fa89fce891099d9f0299382
parent893a777a2f1c6188107952cc57b1d09e128a1bf2 (diff)
downloadqtlocation-mapboxgl-babc43ea6289a5ec6eee9dbf80057ab2de742dfd.tar.gz
[ios] Made annotations accessible
Lazily create and cache accessibility elements for visible annotations as UIAccessibility asks about them. Sort the annotations’ accessibility elements by the annotations’ distance from the logical center (the center after accounting for padding, or the user dot when user tracking mode is on). Updated the changelog. Fixes #1493.
-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);
}
}