summaryrefslogtreecommitdiff
path: root/platform/ios/src
diff options
context:
space:
mode:
authorTobrun <tobrun.van.nuland@gmail.com>2017-11-09 16:28:31 +0100
committerTobrun <tobrun.van.nuland@gmail.com>2017-11-09 16:28:31 +0100
commitf82c628f3070076256513eff3431ccaff8ea1f51 (patch)
tree284f87329993073f62e091dbff194a4a9758e0d4 /platform/ios/src
parenta339dcc6cc16c991fbc083cdca99c7fc440221df (diff)
parenta936fd6f8a625e8a95c3d1ab524b8dddbbbb5ccd (diff)
downloadqtlocation-mapboxgl-f82c628f3070076256513eff3431ccaff8ea1f51.tar.gz
Merge branch 'release-agua' into tvn-merge-releaseupstream/tvn-merge-release
Diffstat (limited to 'platform/ios/src')
-rw-r--r--platform/ios/src/MGLCompactCalloutView.h2
-rw-r--r--platform/ios/src/MGLFaux3DUserLocationAnnotationView.m18
-rw-r--r--platform/ios/src/MGLMapAccessibilityElement.h54
-rw-r--r--platform/ios/src/MGLMapAccessibilityElement.mm199
-rw-r--r--platform/ios/src/MGLMapView.mm625
-rw-r--r--platform/ios/src/MGLScaleBar.mm17
-rw-r--r--platform/ios/src/MGLUserLocation.h3
7 files changed, 743 insertions, 175 deletions
diff --git a/platform/ios/src/MGLCompactCalloutView.h b/platform/ios/src/MGLCompactCalloutView.h
index 56c48a99e5..5cecf37ff6 100644
--- a/platform/ios/src/MGLCompactCalloutView.h
+++ b/platform/ios/src/MGLCompactCalloutView.h
@@ -7,7 +7,7 @@
callout view displays the represented annotation’s title, subtitle, and
accessory views in a compact, two-line layout.
*/
-@interface MGLCompactCalloutView : SMCalloutView <MGLCalloutView>
+@interface MGLCompactCalloutView : MGLSMCalloutView <MGLCalloutView>
+ (instancetype)platformCalloutView;
diff --git a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m
index 21f6aaa540..1ed3d86ad1 100644
--- a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m
+++ b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m
@@ -10,7 +10,7 @@ const CGFloat MGLUserLocationAnnotationDotSize = 22.0;
const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
const CGFloat MGLUserLocationAnnotationPuckSize = 45.0;
-const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.6;
+const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.5;
const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01;
@@ -175,12 +175,16 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01;
_puckArrow = [CAShapeLayer layer];
_puckArrow.path = [[self puckArrow] CGPath];
_puckArrow.fillColor = [self.mapView.tintColor CGColor];
- _puckArrow.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationArrowSize, MGLUserLocationAnnotationArrowSize);
- _puckArrow.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0);
+ _puckArrow.bounds = CGRectMake(0, 0, round(MGLUserLocationAnnotationArrowSize), round(MGLUserLocationAnnotationArrowSize));
+ _puckArrow.position = CGPointMake(CGRectGetMidX(super.bounds), CGRectGetMidY(super.bounds));
_puckArrow.shouldRasterize = YES;
_puckArrow.rasterizationScale = [UIScreen mainScreen].scale;
_puckArrow.drawsAsynchronously = YES;
+ _puckArrow.lineJoin = @"round";
+ _puckArrow.lineWidth = 1.f;
+ _puckArrow.strokeColor = _puckArrow.fillColor;
+
[self.layer addSublayer:_puckArrow];
}
if (self.userLocation.location.course >= 0)
@@ -302,7 +306,7 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01;
[CATransaction setDisableActions:shouldDisableActions];
_accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize);
- _accuracyRingLayer.cornerRadius = accuracyRingSize / 2;
+ _accuracyRingLayer.cornerRadius = accuracyRingSize / 2.0;
// match the halo to the accuracy ring
_haloLayer.bounds = _accuracyRingLayer.bounds;
@@ -431,9 +435,11 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01;
- (CALayer *)circleLayerWithSize:(CGFloat)layerSize
{
+ layerSize = round(layerSize);
+
CALayer *circleLayer = [CALayer layer];
circleLayer.bounds = CGRectMake(0, 0, layerSize, layerSize);
- circleLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0);
+ circleLayer.position = CGPointMake(CGRectGetMidX(super.bounds), CGRectGetMidY(super.bounds));
circleLayer.cornerRadius = layerSize / 2.0;
circleLayer.shouldRasterize = YES;
circleLayer.rasterizationScale = [UIScreen mainScreen].scale;
@@ -456,7 +462,7 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01;
- (CGFloat)calculateAccuracyRingSize
{
// diameter in screen points
- return self.userLocation.location.horizontalAccuracy / [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude] * 2.0;
+ return round(self.userLocation.location.horizontalAccuracy / [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude] * 2.0);
}
@end
diff --git a/platform/ios/src/MGLMapAccessibilityElement.h b/platform/ios/src/MGLMapAccessibilityElement.h
new file mode 100644
index 0000000000..952f6cbf2f
--- /dev/null
+++ b/platform/ios/src/MGLMapAccessibilityElement.h
@@ -0,0 +1,54 @@
+#import <UIKit/UIKit.h>
+
+#import "MGLFoundation.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol MGLFeature;
+
+/// Unique identifier representing a single annotation in mbgl.
+typedef uint32_t MGLAnnotationTag;
+
+/** An accessibility element representing something that appears on the map. */
+MGL_EXPORT
+@interface MGLMapAccessibilityElement : UIAccessibilityElement
+
+@end
+
+/** An accessibility element representing a map annotation. */
+@interface MGLAnnotationAccessibilityElement : MGLMapAccessibilityElement
+
+/** The tag of the annotation represented by this element. */
+@property (nonatomic) MGLAnnotationTag tag;
+
+- (instancetype)initWithAccessibilityContainer:(id)container tag:(MGLAnnotationTag)identifier NS_DESIGNATED_INITIALIZER;
+
+@end
+
+/** An accessibility element representing a map feature. */
+MGL_EXPORT
+@interface MGLFeatureAccessibilityElement : MGLMapAccessibilityElement
+
+/** The feature represented by this element. */
+@property (nonatomic, strong) id <MGLFeature> feature;
+
+- (instancetype)initWithAccessibilityContainer:(id)container feature:(id <MGLFeature>)feature NS_DESIGNATED_INITIALIZER;
+
+@end
+
+/** An accessibility element representing a place feature. */
+MGL_EXPORT
+@interface MGLPlaceFeatureAccessibilityElement : MGLFeatureAccessibilityElement
+@end
+
+/** An accessibility element representing a road feature. */
+MGL_EXPORT
+@interface MGLRoadFeatureAccessibilityElement : MGLFeatureAccessibilityElement
+@end
+
+/** An accessibility element representing the MGLMapView at large. */
+MGL_EXPORT
+@interface MGLMapViewProxyAccessibilityElement : UIAccessibilityElement
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLMapAccessibilityElement.mm b/platform/ios/src/MGLMapAccessibilityElement.mm
new file mode 100644
index 0000000000..4e5f165fbf
--- /dev/null
+++ b/platform/ios/src/MGLMapAccessibilityElement.mm
@@ -0,0 +1,199 @@
+#import "MGLMapAccessibilityElement.h"
+#import "MGLDistanceFormatter.h"
+#import "MGLCompassDirectionFormatter.h"
+#import "MGLFeature.h"
+#import "MGLVectorSource+MGLAdditions.h"
+
+#import "NSBundle+MGLAdditions.h"
+#import "MGLGeometry_Private.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
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index d5bfdbbc9f..4e9ee9a4c7 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -44,6 +44,7 @@
#import "MGLFoundation_Private.h"
#import "MGLRendererFrontend.h"
+#import "MGLVectorSource+MGLAdditions.h"
#import "NSBundle+MGLAdditions.h"
#import "NSDate+MGLAdditions.h"
#import "NSException+MGLAdditions.h"
@@ -69,6 +70,7 @@
#import "MGLAnnotationContainerView.h"
#import "MGLAnnotationContainerView_Private.h"
#import "MGLAttributionInfo_Private.h"
+#import "MGLMapAccessibilityElement.h"
#include <algorithm>
#include <cstdlib>
@@ -140,9 +142,6 @@ const CGFloat MGLAnnotationImagePaddingForCallout = 1;
const CGSize MGLAnnotationAccessibilityElementMinimumSize = CGSizeMake(10, 10);
-/// Unique identifier representing a single annotation in mbgl.
-typedef uint32_t MGLAnnotationTag;
-
/// An indication that the requested annotation was not found or is nonexistent.
enum { MGLAnnotationTagNotFound = UINT32_MAX };
@@ -165,38 +164,6 @@ mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction
return { p1[0], p1[1], p2[0], p2[1] };
}
-@interface MGLAnnotationAccessibilityElement : UIAccessibilityElement
-
-@property (nonatomic) MGLAnnotationTag tag;
-
-- (instancetype)initWithAccessibilityContainer:(id)container tag:(MGLAnnotationTag)identifier NS_DESIGNATED_INITIALIZER;
-
-@end
-
-@implementation MGLAnnotationAccessibilityElement
-
-- (instancetype)initWithAccessibilityContainer:(id)container tag:(MGLAnnotationTag)tag
-{
- if (self = [super initWithAccessibilityContainer:container])
- {
- _tag = tag;
- self.accessibilityTraits = UIAccessibilityTraitButton | UIAccessibilityTraitAdjustable;
- }
- return self;
-}
-
-- (void)accessibilityIncrement
-{
- [self.accessibilityContainer accessibilityIncrement];
-}
-
-- (void)accessibilityDecrement
-{
- [self.accessibilityContainer accessibilityDecrement];
-}
-
-@end
-
/// Lightweight container for metadata about an annotation, including the annotation itself.
class MGLAnnotationContext {
public:
@@ -208,32 +175,12 @@ public:
NSString *viewReuseIdentifier;
};
-/** An accessibility element representing the MGLMapView at large. */
-@interface MGLMapViewProxyAccessibilityElement : UIAccessibilityElement
-
-@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
-
#pragma mark - Private -
@interface MGLMapView () <UIGestureRecognizerDelegate,
GLKViewDelegate,
CLLocationManagerDelegate,
- SMCalloutViewDelegate,
+ MGLSMCalloutViewDelegate,
MGLCalloutViewDelegate,
MGLMultiPointDelegate,
MGLAnnotationImageDelegate>
@@ -328,6 +275,10 @@ public:
BOOL _delegateHasLineWidthsForShapeAnnotations;
MGLCompassDirectionFormatter *_accessibilityCompassFormatter;
+ NS_ARRAY_OF(id <MGLFeature>) *_visiblePlaceFeatures;
+ NS_ARRAY_OF(id <MGLFeature>) *_visibleRoadFeatures;
+ NS_MUTABLE_SET_OF(MGLFeatureAccessibilityElement *) *_featureAccessibilityElements;
+ BOOL _accessibilityValueAnnouncementIsPending;
MGLReachability *_reachability;
}
@@ -1616,7 +1567,9 @@ public:
id<MGLAnnotation>annotation = [self annotationForGestureRecognizer:singleTap persistingResults:YES];
if(annotation)
{
- [self selectAnnotation:annotation animated:YES];
+ CGPoint calloutPoint = [singleTap locationInView:self];
+ CGRect positionRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:calloutPoint];
+ [self selectAnnotation:annotation animated:YES calloutPositioningRect:positionRect];
}
else
{
@@ -1938,7 +1891,7 @@ public:
return [self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)];
}
-- (void)calloutViewClicked:(__unused SMCalloutView *)calloutView
+- (void)calloutViewClicked:(__unused MGLSMCalloutView *)calloutView
{
if ([self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)])
{
@@ -2354,8 +2307,61 @@ public:
- (NSString *)accessibilityValue
{
+ NSMutableArray *facts = [NSMutableArray array];
+
double zoomLevel = round(self.zoomLevel + 1);
- return [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"MAP_A11Y_VALUE", nil, nil, @"Zoom %dx\n%ld annotation(s) visible", @"Map accessibility value"), (int)zoomLevel, (long)self.accessibilityAnnotationCount];
+ [facts addObject:[NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"MAP_A11Y_VALUE_ZOOM", nil, nil, @"Zoom %dx.", @"Map accessibility value; {zoom level}"), (int)zoomLevel]];
+
+ NSInteger annotationCount = self.accessibilityAnnotationCount;
+ if (annotationCount) {
+ [facts addObject:[NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"MAP_A11Y_VALUE_ANNOTATIONS", nil, nil, @"%ld annotation(s) visible.", @"Map accessibility value; {number of visible annotations}"), (long)self.accessibilityAnnotationCount]];
+ }
+
+ NSArray *placeFeatures = self.visiblePlaceFeatures;
+ if (placeFeatures.count) {
+ NSMutableArray *placesArray = [NSMutableArray arrayWithCapacity:placeFeatures.count];
+ NSMutableSet *placesSet = [NSMutableSet setWithCapacity:placeFeatures.count];
+ for (id <MGLFeature> placeFeature in placeFeatures.reverseObjectEnumerator) {
+ NSString *name = [placeFeature attributeForKey:@"name"];
+ if (![placesSet containsObject:name]) {
+ [placesArray addObject:name];
+ [placesSet addObject:name];
+ }
+ if (placesArray.count >= 3) {
+ break;
+ }
+ }
+ NSString *placesString = [placesArray componentsJoinedByString:NSLocalizedStringWithDefaultValue(@"LIST_SEPARATOR", nil, nil, @", ", @"List separator")];
+ [facts addObject:[NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"MAP_A11Y_VALUE_PLACES", nil, nil, @"Places visible: %@.", @"Map accessibility value; {list of visible places}"), placesString]];
+ }
+
+ NSArray *roadFeatures = self.visibleRoadFeatures;
+ if (roadFeatures.count) {
+ [facts addObject:[NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"MAP_A11Y_VALUE_ROADS", nil, nil, @"%ld road(s) visible.", @"Map accessibility value; {number of visible roads}"), roadFeatures.count]];
+ }
+
+ NSString *value = [facts componentsJoinedByString:@" "];
+ return value;
+}
+
+- (NS_ARRAY_OF(id <MGLFeature>) *)visiblePlaceFeatures
+{
+ if (!_visiblePlaceFeatures)
+ {
+ NSArray *placeStyleLayerIdentifiers = [self.style.placeStyleLayers valueForKey:@"identifier"];
+ _visiblePlaceFeatures = [self visibleFeaturesInRect:self.bounds inStyleLayersWithIdentifiers:[NSSet setWithArray:placeStyleLayerIdentifiers]];
+ }
+ return _visiblePlaceFeatures;
+}
+
+- (NS_ARRAY_OF(id <MGLFeature>) *)visibleRoadFeatures
+{
+ if (!_visibleRoadFeatures)
+ {
+ NSArray *roadStyleLayerIdentifiers = [self.style.roadStyleLayers valueForKey:@"identifier"];
+ _visibleRoadFeatures = [self visibleFeaturesInRect:self.bounds inStyleLayersWithIdentifiers:[NSSet setWithArray:roadStyleLayerIdentifiers]];
+ }
+ return _visibleRoadFeatures;
}
- (CGRect)accessibilityFrame
@@ -2389,14 +2395,9 @@ public:
{
if (self.calloutViewForSelectedAnnotation)
{
- return 2 /* selectedAnnotationCalloutView, mapViewProxyAccessibilityElement */;
- }
- NSInteger count = self.accessibilityAnnotationCount + 2 /* compass, attributionButton */;
- if (self.userLocationAnnotationView)
- {
- count++;
+ return 2 /* calloutViewForSelectedAnnotation, mapViewProxyAccessibilityElement */;
}
- return count;
+ return !!self.userLocationAnnotationView + self.accessibilityAnnotationCount + self.visiblePlaceFeatures.count + self.visibleRoadFeatures.count + 2 /* compass, attributionButton */;
}
- (NSInteger)accessibilityAnnotationCount
@@ -2421,67 +2422,123 @@ public:
}
return nil;
}
- std::vector<MGLAnnotationTag> visibleAnnotations = [self annotationTagsInRect:self.bounds];
-
- // Ornaments
- if (index == 0)
+
+ // Compass
+ NSUInteger compassIndex = 0;
+ if (index == compassIndex)
{
return self.compassView;
}
- if ( ! self.userLocationAnnotationView)
- {
- index++;
- }
- else if (index == 1)
+
+ // User location annotation
+ NSRange userLocationAnnotationRange = NSMakeRange(compassIndex + 1, !!self.userLocationAnnotationView);
+ if (NSLocationInRange(index, userLocationAnnotationRange))
{
return self.userLocationAnnotationView;
}
- if (index > 0 && (NSUInteger)index == visibleAnnotations.size() + 2 /* compass, userLocationAnnotationView */)
- {
- 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 - 2) < visibleAnnotations.size())
+
+ // Visible annotations
+ std::vector<MGLAnnotationTag> visibleAnnotations = [self annotationTagsInRect:self.bounds];
+ NSRange visibleAnnotationRange = NSMakeRange(NSMaxRange(userLocationAnnotationRange), visibleAnnotations.size());
+ if (NSLocationInRange(index, visibleAnnotationRange))
+ {
+ std::sort(visibleAnnotations.begin(), visibleAnnotations.end());
+ std::sort(visibleAnnotations.begin(), visibleAnnotations.end(), [&](const MGLAnnotationTag tagA, const MGLAnnotationTag tagB) {
+ CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate];
+ CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate];
+ CGPoint pointA = [self convertCoordinate:coordinateA toPointToView:self];
+ CGPoint pointB = [self convertCoordinate:coordinateB toPointToView:self];
+ CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y);
+ CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y);
+ return deltaA < deltaB;
+ });
+
+ NSUInteger annotationIndex = index - visibleAnnotationRange.location;
+ MGLAnnotationTag annotationTag = visibleAnnotations[annotationIndex];
+ NSAssert(annotationTag != MGLAnnotationTagNotFound, @"Can’t get accessibility element for nonexistent or invisible annotation at index %li.", (long)index);
+ return [self accessibilityElementForAnnotationWithTag:annotationTag];
+ }
+
+ // Visible place features
+ NSArray *visiblePlaceFeatures = self.visiblePlaceFeatures;
+ NSRange visiblePlaceFeatureRange = NSMakeRange(NSMaxRange(visibleAnnotationRange), visiblePlaceFeatures.count);
+ if (NSLocationInRange(index, visiblePlaceFeatureRange))
+ {
+ visiblePlaceFeatures = [visiblePlaceFeatures sortedArrayUsingComparator:^NSComparisonResult(id <MGLFeature> _Nonnull featureA, id <MGLFeature> _Nonnull featureB) {
+ CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self];
+ CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self];
+ CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y);
+ CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y);
+ return [@(deltaA) compare:@(deltaB)];
+ }];
+
+ id <MGLFeature> feature = visiblePlaceFeatures[index - visiblePlaceFeatureRange.location];
+ return [self accessibilityElementForPlaceFeature:feature];
+ }
+
+ // Visible road features
+ NSArray *visibleRoadFeatures = self.visibleRoadFeatures;
+ NSRange visibleRoadFeatureRange = NSMakeRange(NSMaxRange(visiblePlaceFeatureRange), visibleRoadFeatures.count);
+ if (NSLocationInRange(index, visibleRoadFeatureRange))
+ {
+ visibleRoadFeatures = [visibleRoadFeatures sortedArrayUsingComparator:^NSComparisonResult(id <MGLFeature> _Nonnull featureA, id <MGLFeature> _Nonnull featureB) {
+ CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self];
+ CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self];
+ CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y);
+ CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y);
+ return [@(deltaA) compare:@(deltaB)];
+ }];
+
+ id <MGLFeature> feature = visibleRoadFeatures[index - visibleRoadFeatureRange.location];
+ return [self accessibilityElementForRoadFeature:feature];
+ }
+
+ // Attribution button
+ NSUInteger attributionButtonIndex = NSMaxRange(visibleRoadFeatureRange);
+ if (index == attributionButtonIndex)
{
- annotationIndex = index - 2 /* compass, userLocationAnnotationView */;
+ return self.attributionButton;
}
- MGLAnnotationTag annotationTag = visibleAnnotations[annotationIndex];
- NSAssert(annotationTag != MGLAnnotationTagNotFound, @"Can’t get accessibility element for nonexistent or invisible annotation at index %li.", (long)index);
+
+ NSAssert(NO, @"Index %ld not in recognized accessibility element ranges. "
+ @"User location annotation range: %@; visible annotation range: %@; "
+ @"visible place feature range: %@; visible road feature range: %@.",
+ (long)index, NSStringFromRange(userLocationAnnotationRange),
+ NSStringFromRange(visibleAnnotationRange), NSStringFromRange(visiblePlaceFeatureRange),
+ NSStringFromRange(visibleRoadFeatureRange));
+ return nil;
+}
+
+/**
+ Returns an accessibility element corresponding to a visible annotation with the given tag.
+
+ @param annotationTag Tag of the annotation represented by the accessibility element to return.
+ */
+- (id)accessibilityElementForAnnotationWithTag:(MGLAnnotationTag)annotationTag
+{
NSAssert(_annotationContextsByAnnotationTag.count(annotationTag), @"Missing annotation for tag %u.", annotationTag);
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
id <MGLAnnotation> annotation = annotationContext.annotation;
-
+
// Let the annotation view serve as its own accessibility element.
MGLAnnotationView *annotationView = annotationContext.annotationView;
if (annotationView && annotationView.superview)
{
return annotationView;
}
-
+
// Lazily create an accessibility element for the found annotation.
if ( ! annotationContext.accessibilityElement)
{
annotationContext.accessibilityElement = [[MGLAnnotationAccessibilityElement alloc] initWithAccessibilityContainer:self tag:annotationTag];
}
-
+
// Update the accessibility element.
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
CGRect annotationFrame = [self frameOfImage:annotationImage.image centeredAtCoordinate:annotation.coordinate];
@@ -2492,8 +2549,7 @@ public:
annotationFrame = CGRectUnion(annotationFrame, minimumFrame);
CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self);
annotationContext.accessibilityElement.accessibilityFrame = screenRect;
- annotationContext.accessibilityElement.accessibilityHint = NSLocalizedStringWithDefaultValue(@"ANNOTATION_A11Y_HINT", nil, nil, @"Shows more info", @"Accessibility hint");
-
+
if ([annotation respondsToSelector:@selector(title)])
{
annotationContext.accessibilityElement.accessibilityLabel = annotation.title;
@@ -2502,10 +2558,114 @@ public:
{
annotationContext.accessibilityElement.accessibilityValue = annotation.subtitle;
}
-
+
return annotationContext.accessibilityElement;
}
+/**
+ Returns an accessibility element corresponding to the given place feature.
+
+ @param feature The place feature represented by the accessibility element.
+ */
+- (id)accessibilityElementForPlaceFeature:(id <MGLFeature>)feature
+{
+ if (!_featureAccessibilityElements)
+ {
+ _featureAccessibilityElements = [NSMutableSet set];
+ }
+
+ MGLFeatureAccessibilityElement *element = [_featureAccessibilityElements objectsPassingTest:^BOOL(MGLFeatureAccessibilityElement * _Nonnull element, BOOL * _Nonnull stop) {
+ return element.feature.identifier && ![element.feature.identifier isEqual:@0] && [element.feature.identifier isEqual:feature.identifier];
+ }].anyObject;
+ if (!element)
+ {
+ element = [[MGLPlaceFeatureAccessibilityElement alloc] initWithAccessibilityContainer:self feature:feature];
+ }
+ CGPoint center = [self convertCoordinate:feature.coordinate toPointToView:self];
+ CGRect annotationFrame = CGRectInset({center, CGSizeZero}, -MGLAnnotationAccessibilityElementMinimumSize.width / 2, -MGLAnnotationAccessibilityElementMinimumSize.width / 2);
+ CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self);
+ element.accessibilityFrame = screenRect;
+
+ [_featureAccessibilityElements addObject:element];
+
+ return element;
+}
+
+/**
+ Returns an accessibility element corresponding to the given road feature.
+
+ @param feature The road feature represented by the accessibility element.
+ */
+- (id)accessibilityElementForRoadFeature:(id <MGLFeature>)feature
+{
+ if (!_featureAccessibilityElements)
+ {
+ _featureAccessibilityElements = [NSMutableSet set];
+ }
+
+ MGLFeatureAccessibilityElement *element = [_featureAccessibilityElements objectsPassingTest:^BOOL(MGLFeatureAccessibilityElement * _Nonnull element, BOOL * _Nonnull stop) {
+ return element.feature.identifier && ![element.feature.identifier isEqual:@0] && [element.feature.identifier isEqual:feature.identifier];
+ }].anyObject;
+ if (!element)
+ {
+ element = [[MGLRoadFeatureAccessibilityElement alloc] initWithAccessibilityContainer:self feature:feature];
+ }
+
+ UIBezierPath *path;
+ if ([feature isKindOfClass:[MGLPointFeature class]])
+ {
+ CGPoint center = [self convertCoordinate:feature.coordinate toPointToView:self];
+ CGRect annotationFrame = CGRectInset({center, CGSizeZero}, -MGLAnnotationAccessibilityElementMinimumSize.width / 2, -MGLAnnotationAccessibilityElementMinimumSize.width / 2);
+ CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self);
+ element.accessibilityFrame = screenRect;
+ }
+ else if ([feature isKindOfClass:[MGLPolylineFeature class]])
+ {
+ path = [self pathOfPolyline:(MGLPolyline *)feature];
+ }
+ else if ([feature isKindOfClass:[MGLMultiPolylineFeature class]])
+ {
+ path = [UIBezierPath bezierPath];
+ for (MGLPolyline *polyline in [(MGLMultiPolylineFeature *)feature polylines])
+ {
+ [path appendPath:[self pathOfPolyline:polyline]];
+ }
+ }
+
+ if (path)
+ {
+ CGPathRef strokedCGPath = CGPathCreateCopyByStrokingPath(path.CGPath, NULL, MGLAnnotationAccessibilityElementMinimumSize.width, kCGLineCapButt, kCGLineJoinMiter, 0);
+ UIBezierPath *strokedPath = [UIBezierPath bezierPathWithCGPath:strokedCGPath];
+ CGPathRelease(strokedCGPath);
+ UIBezierPath *screenPath = UIAccessibilityConvertPathToScreenCoordinates(strokedPath, self);
+ element.accessibilityPath = screenPath;
+ }
+
+ [_featureAccessibilityElements addObject:element];
+
+ return element;
+}
+
+- (UIBezierPath *)pathOfPolyline:(MGLPolyline *)polyline
+{
+ CLLocationCoordinate2D *coordinates = polyline.coordinates;
+ NSUInteger pointCount = polyline.pointCount;
+ UIBezierPath *path = [UIBezierPath bezierPath];
+ for (NSUInteger i = 0; i < pointCount; i++)
+ {
+ CGPoint point = [self convertCoordinate:coordinates[i] toPointToView:self];
+ if (i)
+ {
+ [path addLineToPoint:point];
+ }
+ else
+ {
+ [path moveToPoint:point];
+ }
+ }
+ return path;
+}
+
- (NSInteger)indexOfAccessibilityElement:(id)element
{
if (self.calloutViewForSelectedAnnotation)
@@ -2513,17 +2673,30 @@ public:
return [@[self.calloutViewForSelectedAnnotation, self.mapViewProxyAccessibilityElement]
indexOfObject:element];
}
+
+ // Compass
+ NSUInteger compassIndex = 0;
if (element == self.compassView)
{
- return 0;
+ return compassIndex;
}
+
+ // User location annotation
+ NSRange userLocationAnnotationRange = NSMakeRange(compassIndex + 1, !!self.userLocationAnnotationView);
if (element == self.userLocationAnnotationView)
{
- return 1;
+ return userLocationAnnotationRange.location;
}
-
+
+ CGPoint centerPoint = self.contentCenter;
+ if (self.userTrackingMode != MGLUserTrackingModeNone)
+ {
+ centerPoint = self.userLocationAnnotationViewCenter;
+ }
+
+ // Visible annotations
std::vector<MGLAnnotationTag> visibleAnnotations = [self annotationTagsInRect:self.bounds];
-
+ NSRange visibleAnnotationRange = NSMakeRange(NSMaxRange(userLocationAnnotationRange), visibleAnnotations.size());
MGLAnnotationTag tag = MGLAnnotationTagNotFound;
if ([element isKindOfClass:[MGLAnnotationView class]])
{
@@ -2534,22 +2707,92 @@ public:
{
tag = [(MGLAnnotationAccessibilityElement *)element tag];
}
- else if (element == self.attributionButton)
- {
- return !!self.userLocationAnnotationView + visibleAnnotations.size();
+
+ if (tag != MGLAnnotationTagNotFound)
+ {
+ std::sort(visibleAnnotations.begin(), visibleAnnotations.end());
+ std::sort(visibleAnnotations.begin(), visibleAnnotations.end(), [&](const MGLAnnotationTag tagA, const MGLAnnotationTag tagB) {
+ CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate];
+ CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate];
+ CGPoint pointA = [self convertCoordinate:coordinateA toPointToView:self];
+ CGPoint pointB = [self convertCoordinate:coordinateB toPointToView:self];
+ CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y);
+ CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y);
+ return deltaA < deltaB;
+ });
+
+ auto foundElement = std::find(visibleAnnotations.begin(), visibleAnnotations.end(), tag);
+ if (foundElement == visibleAnnotations.end())
+ {
+ return NSNotFound;
+ }
+ return visibleAnnotationRange.location + std::distance(visibleAnnotations.begin(), foundElement);
}
- else
- {
- return NSNotFound;
+
+ // Visible place features
+ NSArray *visiblePlaceFeatures = self.visiblePlaceFeatures;
+ NSRange visiblePlaceFeatureRange = NSMakeRange(NSMaxRange(visibleAnnotationRange), visiblePlaceFeatures.count);
+ if ([element isKindOfClass:[MGLPlaceFeatureAccessibilityElement class]])
+ {
+ visiblePlaceFeatures = [visiblePlaceFeatures sortedArrayUsingComparator:^NSComparisonResult(id <MGLFeature> _Nonnull featureA, id <MGLFeature> _Nonnull featureB) {
+ CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self];
+ CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self];
+ CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y);
+ CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y);
+ return [@(deltaA) compare:@(deltaB)];
+ }];
+
+ id <MGLFeature> feature = [(MGLPlaceFeatureAccessibilityElement *)element feature];
+ NSUInteger featureIndex = [visiblePlaceFeatures indexOfObject:feature];
+ if (featureIndex == NSNotFound)
+ {
+ featureIndex = [visiblePlaceFeatures indexOfObjectPassingTest:^BOOL (id <MGLFeature> _Nonnull visibleFeature, NSUInteger idx, BOOL * _Nonnull stop) {
+ return visibleFeature.identifier && ![visibleFeature.identifier isEqual:@0] && [visibleFeature.identifier isEqual:feature.identifier];
+ }];
+ }
+ if (featureIndex == NSNotFound)
+ {
+ return NSNotFound;
+ }
+ return visiblePlaceFeatureRange.location + featureIndex;
}
-
- std::sort(visibleAnnotations.begin(), visibleAnnotations.end());
- auto foundElement = std::find(visibleAnnotations.begin(), visibleAnnotations.end(), tag);
- if (foundElement == visibleAnnotations.end())
+
+ // Visible road features
+ NSArray *visibleRoadFeatures = self.visibleRoadFeatures;
+ NSRange visibleRoadFeatureRange = NSMakeRange(NSMaxRange(visiblePlaceFeatureRange), visibleRoadFeatures.count);
+ if ([element isKindOfClass:[MGLRoadFeatureAccessibilityElement class]])
+ {
+ visibleRoadFeatures = [visibleRoadFeatures sortedArrayUsingComparator:^NSComparisonResult(id <MGLFeature> _Nonnull featureA, id <MGLFeature> _Nonnull featureB) {
+ CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self];
+ CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self];
+ CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y);
+ CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y);
+ return [@(deltaA) compare:@(deltaB)];
+ }];
+
+ id <MGLFeature> feature = [(MGLRoadFeatureAccessibilityElement *)element feature];
+ NSUInteger featureIndex = [visibleRoadFeatures indexOfObject:feature];
+ if (featureIndex == NSNotFound)
+ {
+ featureIndex = [visibleRoadFeatures indexOfObjectPassingTest:^BOOL (id <MGLFeature> _Nonnull visibleFeature, NSUInteger idx, BOOL * _Nonnull stop) {
+ return visibleFeature.identifier && ![visibleFeature.identifier isEqual:@0] && [visibleFeature.identifier isEqual:feature.identifier];
+ }];
+ }
+ if (featureIndex == NSNotFound)
+ {
+ return NSNotFound;
+ }
+ return visibleRoadFeatureRange.location + featureIndex;
+ }
+
+ // Attribution button
+ NSUInteger attributionButtonIndex = NSMaxRange(visibleRoadFeatureRange);
+ if (element == self.attributionButton)
{
- return NSNotFound;
+ return attributionButtonIndex;
}
- return !!self.userLocationAnnotationView + std::distance(visibleAnnotations.begin(), foundElement) + 1 /* compass */;
+
+ return NSNotFound;
}
- (MGLMapViewProxyAccessibilityElement *)mapViewProxyAccessibilityElement
@@ -2580,10 +2823,11 @@ public:
{
centerPoint = self.userLocationAnnotationViewCenter;
}
- _mbglMap->setZoom(_mbglMap->getZoom() + log2(scaleFactor), mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
+ double newZoom = round(self.zoomLevel) + log2(scaleFactor);
+ _mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
[self unrotateIfNeededForGesture];
- UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.accessibilityValue);
+ _accessibilityValueAnnouncementIsPending = YES;
}
#pragma mark - Geography -
@@ -3266,6 +3510,12 @@ public:
}
std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:rect];
+ std::vector<MGLAnnotationTag> shapeAnnotationTags = [self shapeAnnotationTagsInRect:rect];
+
+ if (shapeAnnotationTags.size()) {
+ annotationTags.insert(annotationTags.end(), shapeAnnotationTags.begin(), shapeAnnotationTags.end());
+ }
+
if (annotationTags.size())
{
NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()];
@@ -3382,7 +3632,7 @@ public:
{
annotationViewsForAnnotation[annotationValue] = annotationView;
annotationView.annotation = annotation;
- annotationView.center = [self convertCoordinate:annotation.coordinate toPointToView:self];
+ annotationView.center = MGLPointRounded([self convertCoordinate:annotation.coordinate toPointToView:self]);
[newAnnotationViews addObject:annotationView];
MGLAnnotationImage *annotationImage = self.invisibleAnnotationImage;
@@ -3771,6 +4021,11 @@ public:
queryRect = CGRectInset(queryRect, -MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);
std::vector<MGLAnnotationTag> nearbyAnnotations = [self annotationTagsInRect:queryRect];
+ std::vector<MGLAnnotationTag> nearbyShapeAnnotations = [self shapeAnnotationTagsInRect:queryRect];
+
+ if (nearbyShapeAnnotations.size()) {
+ nearbyAnnotations.insert(nearbyAnnotations.end(), nearbyShapeAnnotations.begin(), nearbyShapeAnnotations.end());
+ }
if (nearbyAnnotations.size())
{
@@ -3778,54 +4033,59 @@ public:
CGRect hitRect = CGRectInset({ point, CGSizeZero },
-MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);
-
+
// Filter out any annotation whose image or view is unselectable or for which
// hit testing fails.
- auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(),
- [&](const MGLAnnotationTag annotationTag)
- {
+ auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag) {
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
NSAssert(annotation, @"Unknown annotation found nearby tap");
if ( ! annotation)
{
return true;
}
-
+
MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
CGRect annotationRect;
-
+
MGLAnnotationView *annotationView = annotationContext.annotationView;
+
if (annotationView)
{
if ( ! annotationView.enabled)
{
return true;
}
-
- CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self];
+
+ CGPoint calloutAnchorPoint = MGLPointRounded([self convertCoordinate:annotation.coordinate toPointToView:self]);
CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2);
annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets);
}
else
{
+ if ([annotation isKindOfClass:[MGLShape class]])
+ {
+ return false;
+ }
+
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
if ( ! annotationImage.enabled)
{
return true;
}
-
+
MGLAnnotationImage *fallbackAnnotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName];
UIImage *fallbackImage = fallbackAnnotationImage.image;
-
+
annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate];
}
-
+
// Filter out the annotation if the fattened finger didn’t land
// within the image’s alignment rect.
return !!!CGRectIntersectsRect(annotationRect, hitRect);
});
-
+
nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end));
+
}
MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound;
@@ -3908,6 +4168,14 @@ public:
});
}
+- (std::vector<MGLAnnotationTag>)shapeAnnotationTagsInRect:(CGRect)rect
+{
+ return _rendererFrontend->getRenderer()->queryShapeAnnotations({
+ { CGRectGetMinX(rect), CGRectGetMinY(rect) },
+ { CGRectGetMaxX(rect), CGRectGetMaxY(rect) },
+ });
+}
+
- (id <MGLAnnotation>)selectedAnnotation
{
if (_userLocationAnnotationIsSelected)
@@ -3957,9 +4225,13 @@ public:
- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated
{
- if ( ! annotation) return;
+ CGRect positioningRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:CGPointZero];
+ [self selectAnnotation:annotation animated:animated calloutPositioningRect:positioningRect];
+}
- if ([annotation isKindOfClass:[MGLMultiPoint class]]) return;
+- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated calloutPositioningRect:(CGRect)calloutPositioningRect
+{
+ if ( ! annotation) return;
if (annotation == self.selectedAnnotation) return;
@@ -3974,9 +4246,6 @@ public:
if (annotationTag == MGLAnnotationTagNotFound) return;
}
- // By default attempt to use the GL annotation image frame as the positioning rect.
- CGRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag];
-
MGLAnnotationView *annotationView = nil;
if (annotation != self.userLocation)
@@ -3984,21 +4253,12 @@ public:
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];
+ // Annotations represented by views use the view frame as the positioning rect.
+ calloutPositioningRect = annotationView.frame;
+ [annotationView.superview bringSubviewToFront:annotationView];
+ [annotationView setSelected:YES animated:animated];
}
}
- }
-
- // The client can request that any annotation be selected (even ones that are offscreen).
- // The annotation can’t be selected if no part of it is hittable.
- if ( ! CGRectIntersectsRect(positioningRect, self.bounds) && annotation != self.userLocation)
- {
- return;
- }
self.selectedAnnotation = annotation;
@@ -4028,7 +4288,7 @@ public:
if (_userLocationAnnotationIsSelected)
{
- positioningRect = [self.userLocationAnnotationView.layer.presentationLayer frame];
+ calloutPositioningRect = [self.userLocationAnnotationView.layer.presentationLayer frame];
CGRect implicitAnnotationFrame = [self.userLocationAnnotationView.layer.presentationLayer frame];
CGRect explicitAnnotationFrame = self.userLocationAnnotationView.frame;
@@ -4044,7 +4304,7 @@ public:
if ([calloutView.leftAccessoryView isKindOfClass:[UIControl class]])
{
UITapGestureRecognizer *calloutAccessoryTap = [[UITapGestureRecognizer alloc] initWithTarget:self
- action:@selector(handleCalloutAccessoryTapGesture:)];
+ action:@selector(handleCalloutAccessoryTapGesture:)];
[calloutView.leftAccessoryView addGestureRecognizer:calloutAccessoryTap];
}
@@ -4057,7 +4317,7 @@ public:
if ([calloutView.rightAccessoryView isKindOfClass:[UIControl class]])
{
UITapGestureRecognizer *calloutAccessoryTap = [[UITapGestureRecognizer alloc] initWithTarget:self
- action:@selector(handleCalloutAccessoryTapGesture:)];
+ action:@selector(handleCalloutAccessoryTapGesture:)];
[calloutView.rightAccessoryView addGestureRecognizer:calloutAccessoryTap];
}
@@ -4067,7 +4327,7 @@ public:
calloutView.delegate = self;
// present popup
- [calloutView presentCalloutFromRect:positioningRect
+ [calloutView presentCalloutFromRect:calloutPositioningRect
inView:self.glView
constrainedToView:self.glView
animated:animated];
@@ -4097,6 +4357,27 @@ public:
/// Returns the rectangle that represents the annotation image of the annotation
/// with the given tag. This rectangle is fitted to the image’s alignment rect
/// and is appropriate for positioning a popover.
+/// If a shape annotation is visible but its centroid is not, and a default point is specified,
+/// the callout view is anchored to the default callout point.
+- (CGRect)positioningRectForAnnotation:(id <MGLAnnotation>)annotation defaultCalloutPoint:(CGPoint)calloutPoint
+{
+ MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
+ CGRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag];
+
+ // For annotations which `coordinate` falls offscreen it will use the current tap point as anchor instead.
+ if ( ! CGRectIntersectsRect(positioningRect, self.bounds) && annotation != self.userLocation)
+ {
+ if (!CGPointEqualToPoint(calloutPoint, CGPointZero)) {
+ positioningRect = CGRectMake(calloutPoint.x, calloutPoint.y, positioningRect.size.width, positioningRect.size.height);
+ }
+ }
+
+ return positioningRect;
+}
+
+/// Returns the rectangle that represents the annotation image of the annotation
+/// with the given tag. This rectangle is fitted to the image’s alignment rect
+/// and is appropriate for positioning a popover.
- (CGRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annotationTag
{
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
@@ -4104,6 +4385,13 @@ public:
{
return CGRectZero;
}
+
+ if ([annotation isKindOfClass:[MGLMultiPoint class]]) {
+ CLLocationCoordinate2D origin = annotation.coordinate;
+ CGPoint originPoint = [self convertCoordinate:origin toPointToView:self];
+ return CGRectMake(originPoint.x, originPoint.y, MGLAnnotationImagePaddingForHitTest, MGLAnnotationImagePaddingForHitTest);
+
+ }
UIImage *image = [self imageOfAnnotationWithTag:annotationTag].image;
if ( ! image)
{
@@ -4125,7 +4413,7 @@ public:
/// image centered at the given coordinate.
- (CGRect)frameOfImage:(UIImage *)image centeredAtCoordinate:(CLLocationCoordinate2D)coordinate
{
- CGPoint calloutAnchorPoint = [self convertCoordinate:coordinate toPointToView:self];
+ CGPoint calloutAnchorPoint = MGLPointRounded([self convertCoordinate:coordinate toPointToView:self]);
CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -image.size.width / 2, -image.size.height / 2);
return UIEdgeInsetsInsetRect(frame, image.alignmentRectInsets);
}
@@ -4564,7 +4852,6 @@ public:
if (_showsUserHeadingIndicator)
{
self.showsUserLocation = YES;
-
}
[self validateUserHeadingUpdating];
}
@@ -5100,12 +5387,26 @@ public:
{
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
{
- UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
+ _featureAccessibilityElements = nil;
+ _visiblePlaceFeatures = nil;
+ _visibleRoadFeatures = nil;
+ if (_accessibilityValueAnnouncementIsPending) {
+ _accessibilityValueAnnouncementIsPending = NO;
+ [self performSelector:@selector(announceAccessibilityValue) withObject:nil afterDelay:0.1];
+ } else {
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
+ }
}
[self.delegate mapView:self regionDidChangeAnimated:animated];
}
}
+- (void)announceAccessibilityValue
+{
+ UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.accessibilityValue);
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
+}
+
- (void)mapViewWillStartLoadingMap {
if (!_mbglMap) {
return;
@@ -5187,6 +5488,8 @@ public:
if (!_mbglMap) {
return;
}
+
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)])
{
@@ -5270,7 +5573,7 @@ public:
if (annotationView)
{
- annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self];
+ annotationView.center = MGLPointRounded([self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self]);
}
}
@@ -5388,7 +5691,7 @@ public:
}
else
{
- userPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self];
+ userPoint = MGLPointRounded([self convertCoordinate:self.userLocation.coordinate toPointToView:self]);
}
if ( ! annotationView.superview)
diff --git a/platform/ios/src/MGLScaleBar.mm b/platform/ios/src/MGLScaleBar.mm
index 966555e46a..139dffdfab 100644
--- a/platform/ios/src/MGLScaleBar.mm
+++ b/platform/ios/src/MGLScaleBar.mm
@@ -175,10 +175,15 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
return [self usesMetricSystem] ? self.metersPerPoint : self.metersPerPoint * MGLFeetPerMeter;
}
-#pragma mark - Convenient methods
+#pragma mark - Convenience methods
- (BOOL)usesRightToLeftLayout {
- return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.superview.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft;
+ // semanticContentAttribute is iOS 9+
+ if ([self.superview respondsToSelector:@selector(semanticContentAttribute)]) {
+ return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.superview.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft;
+ } else {
+ return UIApplication.sharedApplication.userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft;
+ }
}
- (BOOL)usesMetricSystem {
@@ -241,7 +246,7 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
CGFloat alpha = maximumDistance > allowedDistance ? .0f : 1.0f;
- if(self.alpha != alpha) {
+ if (self.alpha != alpha) {
[UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
self.alpha = alpha;
} completion:nil];
@@ -331,7 +336,7 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
}
- (void)layoutBars {
- CGFloat barWidth = (CGRectGetWidth(self.bounds) - self.borderWidth * 2.0f) / self.bars.count;
+ CGFloat barWidth = round((CGRectGetWidth(self.bounds) - self.borderWidth * 2.0f) / self.bars.count);
NSUInteger i = 0;
for (UIView *bar in self.bars) {
@@ -354,11 +359,11 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
}
- (void)layoutLabels {
- CGFloat barWidth = self.bounds.size.width / self.bars.count;
+ CGFloat barWidth = round(self.bounds.size.width / self.bars.count);
BOOL RTL = [self usesRightToLeftLayout];
NSUInteger i = RTL ? self.bars.count : 0;
for (MGLScaleBarLabel *label in self.labels) {
- CGFloat xPosition = barWidth * i - CGRectGetMidX(label.bounds) + self.borderWidth;
+ CGFloat xPosition = round(barWidth * i - CGRectGetMidX(label.bounds) + self.borderWidth);
label.frame = CGRectMake(xPosition, 0,
CGRectGetWidth(label.bounds),
CGRectGetHeight(label.bounds));
diff --git a/platform/ios/src/MGLUserLocation.h b/platform/ios/src/MGLUserLocation.h
index 91abadbcb7..4e01cf00c9 100644
--- a/platform/ios/src/MGLUserLocation.h
+++ b/platform/ios/src/MGLUserLocation.h
@@ -34,7 +34,8 @@ MGL_EXPORT
The heading of the user location. (read-only)
This property is `nil` if the user location tracking mode is not
- `MGLUserTrackingModeFollowWithHeading`.
+ `MGLUserTrackingModeFollowWithHeading` or if
+ `MGLMapView.showsUserHeadingIndicator` is disabled.
*/
@property (nonatomic, readonly, nullable) CLHeading *heading;