diff options
Diffstat (limited to 'platform/ios/src/MGLMapAccessibilityElement.mm')
-rw-r--r-- | platform/ios/src/MGLMapAccessibilityElement.mm | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/platform/ios/src/MGLMapAccessibilityElement.mm b/platform/ios/src/MGLMapAccessibilityElement.mm new file mode 100644 index 0000000000..1a2953b0bb --- /dev/null +++ b/platform/ios/src/MGLMapAccessibilityElement.mm @@ -0,0 +1,200 @@ +#import "MGLMapAccessibilityElement.h" +#import "MGLDistanceFormatter.h" +#import "MGLCompassDirectionFormatter.h" +#import "MGLFeature.h" + +#import "MGLGeometry_Private.h" +#import "MGLVectorSource_Private.h" + +#import "NSBundle+MGLAdditions.h" + +@implementation MGLMapAccessibilityElement + +- (UIAccessibilityTraits)accessibilityTraits { + return super.accessibilityTraits | UIAccessibilityTraitAdjustable; +} + +- (void)accessibilityIncrement { + [self.accessibilityContainer accessibilityIncrement]; +} + +- (void)accessibilityDecrement { + [self.accessibilityContainer accessibilityDecrement]; +} + +@end + +@implementation MGLAnnotationAccessibilityElement + +- (instancetype)initWithAccessibilityContainer:(id)container tag:(MGLAnnotationTag)tag { + if (self = [super initWithAccessibilityContainer:container]) { + _tag = tag; + self.accessibilityHint = NSLocalizedStringWithDefaultValue(@"ANNOTATION_A11Y_HINT", nil, nil, @"Shows more info", @"Accessibility hint"); + } + return self; +} + +- (UIAccessibilityTraits)accessibilityTraits { + return super.accessibilityTraits | UIAccessibilityTraitButton; +} + +@end + +@implementation MGLFeatureAccessibilityElement + +- (instancetype)initWithAccessibilityContainer:(id)container feature:(id<MGLFeature>)feature { + if (self = [super initWithAccessibilityContainer:container]) { + _feature = feature; + + NSString *languageCode = [MGLVectorSource preferredMapboxStreetsLanguage]; + NSString *nameAttribute = [NSString stringWithFormat:@"name_%@", languageCode]; + NSString *name = [feature attributeForKey:nameAttribute]; + + // If a feature hasn’t been translated into the preferred language, it + // may be in the local language, which may be written in another script. + // Romanize it. + NSLocale *locale = [NSLocale localeWithLocaleIdentifier:languageCode]; + NSOrthography *orthography; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" + if ([NSOrthography respondsToSelector:@selector(defaultOrthographyForLanguage:)]) { + orthography = [NSOrthography defaultOrthographyForLanguage:locale.localeIdentifier]; + } +#pragma clang diagnostic pop +#endif + if ([orthography.dominantScript isEqualToString:@"Latn"]) { + name = [name stringByApplyingTransform:NSStringTransformToLatin reverse:NO]; + } + + self.accessibilityLabel = name; + } + return self; +} + +- (UIAccessibilityTraits)accessibilityTraits { + return super.accessibilityTraits | UIAccessibilityTraitStaticText; +} + +@end + +@implementation MGLPlaceFeatureAccessibilityElement + +- (instancetype)initWithAccessibilityContainer:(id)container feature:(id<MGLFeature>)feature { + if (self = [super initWithAccessibilityContainer:container feature:feature]) { + NSDictionary *attributes = feature.attributes; + NSMutableArray *facts = [NSMutableArray array]; + + // Announce the kind of place or POI. + if (attributes[@"type"]) { + // FIXME: Unfortunately, these types aren’t a closed set that can be + // localized, since they’re based on OpenStreetMap tags. + NSString *type = [attributes[@"type"] stringByReplacingOccurrencesOfString:@"_" + withString:@" "]; + [facts addObject:type]; + } + // Announce the kind of airport, rail station, or mountain based on its + // Maki image name. + else if (attributes[@"maki"]) { + // TODO: Localize Maki image names. + [facts addObject:attributes[@"maki"]]; + } + + // Announce the peak’s elevation in the preferred units. + if (attributes[@"elevation_m"] ?: attributes[@"elevation_ft"]) { + NSLengthFormatter *formatter = [[NSLengthFormatter alloc] init]; + formatter.unitStyle = NSFormattingUnitStyleLong; + + NSNumber *elevationValue; + NSLengthFormatterUnit unit; + BOOL usesMetricSystem = ![[formatter.numberFormatter.locale objectForKey:NSLocaleMeasurementSystem] + isEqualToString:@"U.S."]; + if (usesMetricSystem) { + elevationValue = attributes[@"elevation_m"]; + unit = NSLengthFormatterUnitMeter; + } else { + elevationValue = attributes[@"elevation_ft"]; + unit = NSLengthFormatterUnitFoot; + } + [facts addObject:[formatter stringFromValue:elevationValue.doubleValue unit:unit]]; + } + + if (facts.count) { + NSString *separator = NSLocalizedStringWithDefaultValue(@"LIST_SEPARATOR", nil, nil, @", ", @"List separator"); + self.accessibilityValue = [facts componentsJoinedByString:separator]; + } + } + return self; +} + +@end + +@implementation MGLRoadFeatureAccessibilityElement + +- (instancetype)initWithAccessibilityContainer:(id)container feature:(id<MGLFeature>)feature { + if (self = [super initWithAccessibilityContainer:container feature:feature]) { + NSDictionary *attributes = feature.attributes; + NSMutableArray *facts = [NSMutableArray array]; + + // Announce the route number. + if (attributes[@"ref"]) { + // TODO: Decorate the route number with the network name based on the shield attribute. + NSString *ref = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"ROAD_REF_A11Y_FMT", nil, nil, @"Route %@", @"String format for accessibility value for road feature; {route number}"), attributes[@"ref"]]; + [facts addObject:ref]; + } + + // Announce whether the road is a one-way road. + if ([attributes[@"oneway"] isEqualToString:@"true"]) { + [facts addObject:NSLocalizedStringWithDefaultValue(@"ROAD_ONEWAY_A11Y_VALUE", nil, nil, @"One way", @"Accessibility value indicating that a road is a one-way road")]; + } + + // Announce whether the road is a divided road. + MGLPolyline *polyline; + if ([feature isKindOfClass:[MGLMultiPolylineFeature class]]) { + [facts addObject:NSLocalizedStringWithDefaultValue(@"ROAD_DIVIDED_A11Y_VALUE", nil, nil, @"Divided road", @"Accessibility value indicating that a road is a divided road (dual carriageway)")]; + polyline = [(MGLMultiPolylineFeature *)feature polylines].firstObject; + } + + // Announce the road’s general direction. + if ([feature isKindOfClass:[MGLPolylineFeature class]]) { + polyline = (MGLPolylineFeature *)feature; + } + if (polyline) { + NSUInteger pointCount = polyline.pointCount; + if (pointCount) { + CLLocationCoordinate2D *coordinates = polyline.coordinates; + CLLocationDirection startDirection = MGLDirectionBetweenCoordinates(coordinates[pointCount - 1], coordinates[0]); + CLLocationDirection endDirection = MGLDirectionBetweenCoordinates(coordinates[0], coordinates[pointCount - 1]); + + MGLCompassDirectionFormatter *formatter = [[MGLCompassDirectionFormatter alloc] init]; + formatter.unitStyle = NSFormattingUnitStyleLong; + + NSString *startDirectionString = [formatter stringFromDirection:startDirection]; + NSString *endDirectionString = [formatter stringFromDirection:endDirection]; + NSString *directionString = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"ROAD_DIRECTION_A11Y_FMT", nil, nil, @"%@ to %@", @"String format for accessibility value for road feature; {starting compass direction}, {ending compass direction}"), startDirectionString, endDirectionString]; + [facts addObject:directionString]; + } + } + + if (facts.count) { + NSString *separator = NSLocalizedStringWithDefaultValue(@"LIST_SEPARATOR", nil, nil, @", ", @"List separator"); + self.accessibilityValue = [facts componentsJoinedByString:separator]; + } + } + return self; +} + +@end + +@implementation MGLMapViewProxyAccessibilityElement + +- (instancetype)initWithAccessibilityContainer:(id)container { + if (self = [super initWithAccessibilityContainer:container]) { + self.accessibilityTraits = UIAccessibilityTraitButton; + self.accessibilityLabel = [self.accessibilityContainer accessibilityLabel]; + self.accessibilityHint = NSLocalizedStringWithDefaultValue(@"CLOSE_CALLOUT_A11Y_HINT", nil, nil, @"Returns to the map", @"Accessibility hint for closing the selected annotation’s callout view and returning to the map"); + } + return self; +} + +@end |