diff options
Diffstat (limited to 'platform/ios')
-rw-r--r-- | platform/ios/CHANGELOG.md | 1 | ||||
-rw-r--r-- | platform/ios/src/MGLMapView.mm | 112 |
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); } } |