summaryrefslogtreecommitdiff
path: root/platform/ios/src
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/src')
-rw-r--r--platform/ios/src/MGLAnnotationContainerView.h17
-rw-r--r--platform/ios/src/MGLAnnotationContainerView.m52
-rw-r--r--platform/ios/src/MGLAnnotationContainerView_Private.h14
-rw-r--r--platform/ios/src/MGLAnnotationImage.h62
-rw-r--r--platform/ios/src/MGLAnnotationImage.m77
-rw-r--r--platform/ios/src/MGLAnnotationImage_Private.h21
-rw-r--r--platform/ios/src/MGLAnnotationView.h286
-rw-r--r--platform/ios/src/MGLAnnotationView.mm387
-rw-r--r--platform/ios/src/MGLAnnotationView_Private.h15
-rw-r--r--platform/ios/src/MGLCalloutView.h140
-rw-r--r--platform/ios/src/MGLCameraChangeReason.h65
-rw-r--r--platform/ios/src/MGLCompactCalloutView.h14
-rw-r--r--platform/ios/src/MGLCompactCalloutView.m39
-rw-r--r--platform/ios/src/MGLCompassButton.h22
-rw-r--r--platform/ios/src/MGLCompassButton.mm135
-rw-r--r--platform/ios/src/MGLCompassButton_Private.h19
-rw-r--r--platform/ios/src/MGLFaux3DUserLocationAnnotationView.h15
-rw-r--r--platform/ios/src/MGLFaux3DUserLocationAnnotationView.m469
-rw-r--r--platform/ios/src/MGLMapAccessibilityElement.h54
-rw-r--r--platform/ios/src/MGLMapAccessibilityElement.mm195
-rw-r--r--platform/ios/src/MGLMapView+IBAdditions.h51
-rw-r--r--platform/ios/src/MGLMapView+Impl.h77
-rw-r--r--platform/ios/src/MGLMapView+Impl.mm114
-rw-r--r--platform/ios/src/MGLMapView+OpenGL.h60
-rw-r--r--platform/ios/src/MGLMapView+OpenGL.mm277
-rw-r--r--platform/ios/src/MGLMapView.h1968
-rw-r--r--platform/ios/src/MGLMapView.mm7010
-rw-r--r--platform/ios/src/MGLMapViewDelegate.h775
-rw-r--r--platform/ios/src/MGLMapView_Experimental.h32
-rw-r--r--platform/ios/src/MGLMapView_Private.h74
-rw-r--r--platform/ios/src/MGLMapboxEvents.h21
-rw-r--r--platform/ios/src/MGLMapboxEvents.m200
-rw-r--r--platform/ios/src/MGLSDKUpdateChecker.h13
-rw-r--r--platform/ios/src/MGLSDKUpdateChecker.mm37
-rw-r--r--platform/ios/src/MGLScaleBar.h9
-rw-r--r--platform/ios/src/MGLScaleBar.mm556
-rw-r--r--platform/ios/src/MGLTelemetryConfig.h18
-rw-r--r--platform/ios/src/MGLTelemetryConfig.m35
-rw-r--r--platform/ios/src/MGLUserLocation.h57
-rw-r--r--platform/ios/src/MGLUserLocation.m124
-rw-r--r--platform/ios/src/MGLUserLocationAnnotationView.h64
-rw-r--r--platform/ios/src/MGLUserLocationAnnotationView.m100
-rw-r--r--platform/ios/src/MGLUserLocationAnnotationView_Private.h15
-rw-r--r--platform/ios/src/MGLUserLocationHeadingArrowLayer.h11
-rw-r--r--platform/ios/src/MGLUserLocationHeadingArrowLayer.m59
-rw-r--r--platform/ios/src/MGLUserLocationHeadingBeamLayer.h11
-rw-r--r--platform/ios/src/MGLUserLocationHeadingBeamLayer.m104
-rw-r--r--platform/ios/src/MGLUserLocationHeadingIndicator.h10
-rw-r--r--platform/ios/src/MGLUserLocation_Private.h19
-rw-r--r--platform/ios/src/Mapbox-Prefix.pch1
-rw-r--r--platform/ios/src/Mapbox.h77
-rw-r--r--platform/ios/src/NSOrthography+MGLAdditions.h18
-rw-r--r--platform/ios/src/NSOrthography+MGLAdditions.m37
-rw-r--r--platform/ios/src/UIColor+MGLAdditions.h22
-rw-r--r--platform/ios/src/UIColor+MGLAdditions.mm86
-rw-r--r--platform/ios/src/UIDevice+MGLAdditions.h7
-rw-r--r--platform/ios/src/UIDevice+MGLAdditions.m53
-rw-r--r--platform/ios/src/UIImage+MGLAdditions.h25
-rw-r--r--platform/ios/src/UIImage+MGLAdditions.mm64
-rw-r--r--platform/ios/src/UIView+MGLAdditions.h19
-rw-r--r--platform/ios/src/UIView+MGLAdditions.m69
-rw-r--r--platform/ios/src/UIViewController+MGLAdditions.h11
-rw-r--r--platform/ios/src/UIViewController+MGLAdditions.m22
63 files changed, 0 insertions, 14510 deletions
diff --git a/platform/ios/src/MGLAnnotationContainerView.h b/platform/ios/src/MGLAnnotationContainerView.h
deleted file mode 100644
index ccec3052a6..0000000000
--- a/platform/ios/src/MGLAnnotationContainerView.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#import <UIKit/UIKit.h>
-
-#import "MGLTypes.h"
-
-@class MGLAnnotationView;
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface MGLAnnotationContainerView : UIView
-
-+ (instancetype)annotationContainerViewWithAnnotationContainerView:(MGLAnnotationContainerView *)annotationContainerView;
-
-- (void)addSubviews:(NSArray<MGLAnnotationView *> *)subviews;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLAnnotationContainerView.m b/platform/ios/src/MGLAnnotationContainerView.m
deleted file mode 100644
index 6c82a1836d..0000000000
--- a/platform/ios/src/MGLAnnotationContainerView.m
+++ /dev/null
@@ -1,52 +0,0 @@
-#import "MGLAnnotationContainerView.h"
-#import "MGLAnnotationView.h"
-
-@interface MGLAnnotationContainerView ()
-
-@property (nonatomic) NSMutableArray<MGLAnnotationView *> *annotationViews;
-
-@end
-
-@implementation MGLAnnotationContainerView
-
-- (instancetype)initWithFrame:(CGRect)frame
-{
- self = [super initWithFrame:frame];
- if (self)
- {
- _annotationViews = [NSMutableArray array];
- }
- return self;
-}
-
-+ (instancetype)annotationContainerViewWithAnnotationContainerView:(nonnull MGLAnnotationContainerView *)annotationContainerView
-{
- MGLAnnotationContainerView *newAnnotationContainerView = [[MGLAnnotationContainerView alloc] initWithFrame:annotationContainerView.frame];
- [newAnnotationContainerView addSubviews:annotationContainerView.subviews];
- return newAnnotationContainerView;
-}
-
-- (void)addSubviews:(NSArray<MGLAnnotationView *> *)subviews
-{
- for (MGLAnnotationView *view in subviews)
- {
- [self addSubview:view];
- [self.annotationViews addObject:view];
- }
-}
-
-#pragma mark UIAccessibility methods
-
-- (UIAccessibilityTraits)accessibilityTraits {
- return UIAccessibilityTraitAdjustable;
-}
-
-- (void)accessibilityIncrement {
- [self.superview.superview accessibilityIncrement];
-}
-
-- (void)accessibilityDecrement {
- [self.superview.superview accessibilityDecrement];
-}
-
-@end
diff --git a/platform/ios/src/MGLAnnotationContainerView_Private.h b/platform/ios/src/MGLAnnotationContainerView_Private.h
deleted file mode 100644
index 9dce54842d..0000000000
--- a/platform/ios/src/MGLAnnotationContainerView_Private.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#import "MGLAnnotationContainerView.h"
-#import "MGLAnnotationView.h"
-
-@class MGLAnnotationView;
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface MGLAnnotationContainerView (Private)
-
-@property (nonatomic) NSMutableArray<MGLAnnotationView *> *annotationViews;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLAnnotationImage.h b/platform/ios/src/MGLAnnotationImage.h
deleted file mode 100644
index 10b13a58c3..0000000000
--- a/platform/ios/src/MGLAnnotationImage.h
+++ /dev/null
@@ -1,62 +0,0 @@
-#import <UIKit/UIKit.h>
-
-#import "MGLFoundation.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-/**
- The `MGLAnnotationImage` class is responsible for presenting point-based
- annotations visually on a map view. Annotation image objects wrap `UIImage`
- objects and may be recycled later and put into a reuse queue that is maintained
- by the map view.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/marker-image/">
- Mark a place on the map with an image</a> example to learn how use an image
- as a marker using `MGLAnnotationImage`.
- */
-MGL_EXPORT
-@interface MGLAnnotationImage : NSObject <NSSecureCoding>
-
-#pragma mark Initializing and Preparing the Image Object
-
-/**
- Initializes and returns a new annotation image object.
-
- @param image The image to be displayed for the annotation.
- @param reuseIdentifier The string that identifies that this annotation image is
- reusable.
- @return The initialized annotation image object or `nil` if there was a problem
- initializing the object.
- */
-+ (instancetype)annotationImageWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier;
-
-#pragma mark Getting and Setting Attributes
-
-/** The image to be displayed for the annotation. */
-@property (nonatomic, strong, nullable) UIImage *image;
-
-/**
- The string that identifies that this annotation image is reusable. (read-only)
-
- You specify the reuse identifier when you create the image object. You use this
- type later to retrieve an annotation image object that was created previously
- but which is currently unused because its annotation is not on screen.
-
- If you define distinctly different types of annotations (with distinctly
- different annotation images to go with them), you can differentiate between the
- annotation types by specifying different reuse identifiers for each one.
- */
-@property (nonatomic, readonly) NSString *reuseIdentifier;
-
-/**
- A Boolean value indicating whether the annotation is enabled.
-
- The default value of this property is `YES`. If the value of this property is
- `NO`, the annotation image ignores touch events and cannot be selected.
- */
-@property (nonatomic, getter=isEnabled) BOOL enabled;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLAnnotationImage.m b/platform/ios/src/MGLAnnotationImage.m
deleted file mode 100644
index 31b67c7a3c..0000000000
--- a/platform/ios/src/MGLAnnotationImage.m
+++ /dev/null
@@ -1,77 +0,0 @@
-#import "MGLAnnotationImage_Private.h"
-#import "MGLLoggingConfiguration_Private.h"
-
-@interface MGLAnnotationImage ()
-
-@property (nonatomic, strong) NSString *reuseIdentifier;
-@property (nonatomic, strong, nullable) NSString *styleIconIdentifier;
-
-@property (nonatomic, weak) id<MGLAnnotationImageDelegate> delegate;
-
-@end
-
-@implementation MGLAnnotationImage
-
-+ (instancetype)annotationImageWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier
-{
- return [[self alloc] initWithImage:image reuseIdentifier:reuseIdentifier];
-}
-
-- (instancetype)initWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier
-{
- MGLLogDebug(@"Initializing with image size: %@ reuseIdentifier: %@", NSStringFromCGSize(image.size), reuseIdentifier);
- self = [super init];
-
- if (self)
- {
- _image = image;
- _reuseIdentifier = [reuseIdentifier copy];
- _enabled = YES;
- }
-
- return self;
-}
-
-+ (BOOL)supportsSecureCoding {
- return YES;
-}
-
-- (instancetype)initWithCoder:(NSCoder *)decoder {
- MGLLogInfo(@"Initializing with coder.");
- if (self = [super init]) {
- _image = [decoder decodeObjectOfClass:[UIImage class] forKey:@"image"];
- _reuseIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"reuseIdentifier"];
- _enabled = [decoder decodeBoolForKey:@"enabled"];
- }
- return self;
-}
-
-- (void)encodeWithCoder:(NSCoder *)coder {
- [coder encodeObject:_image forKey:@"image"];
- [coder encodeObject:_reuseIdentifier forKey:@"reuseIdentifier"];
- [coder encodeBool:_enabled forKey:@"enabled"];
-}
-
-- (BOOL)isEqual:(id)other {
- if (self == other) return YES;
- if (![other isKindOfClass:[MGLAnnotationImage class]]) return NO;
-
- MGLAnnotationImage *otherAnnotationImage = other;
-
- return ((!_reuseIdentifier && !otherAnnotationImage.reuseIdentifier)
- || [_reuseIdentifier isEqualToString:otherAnnotationImage.reuseIdentifier])
- && _enabled == otherAnnotationImage.enabled
- && (_image == otherAnnotationImage.image || [UIImagePNGRepresentation(_image) isEqualToData:UIImagePNGRepresentation(otherAnnotationImage.image)]);
-}
-
-- (NSUInteger)hash {
- return _reuseIdentifier.hash + _enabled + _image.hash;
-}
-
-- (void)setImage:(UIImage *)image {
- MGLLogDebug(@"Setting image: %@", image);
- _image = image;
- [self.delegate annotationImageNeedsRedisplay:self];
-}
-
-@end
diff --git a/platform/ios/src/MGLAnnotationImage_Private.h b/platform/ios/src/MGLAnnotationImage_Private.h
deleted file mode 100644
index dcd8a49bf9..0000000000
--- a/platform/ios/src/MGLAnnotationImage_Private.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#import "MGLAnnotationImage.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@protocol MGLAnnotationImageDelegate <NSObject>
-
-@required
-- (void)annotationImageNeedsRedisplay:(MGLAnnotationImage *)annotationImage;
-
-@end
-
-@interface MGLAnnotationImage (Private)
-
-/// Unique identifier of the sprite image used by the style to represent the receiver’s `image`.
-@property (nonatomic, strong, nullable) NSString *styleIconIdentifier;
-
-@property (nonatomic, weak) id<MGLAnnotationImageDelegate> delegate;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLAnnotationView.h b/platform/ios/src/MGLAnnotationView.h
deleted file mode 100644
index afedeb7908..0000000000
--- a/platform/ios/src/MGLAnnotationView.h
+++ /dev/null
@@ -1,286 +0,0 @@
-#import <UIKit/UIKit.h>
-
-#import "MGLFoundation.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@protocol MGLAnnotation;
-
-/** These constants indicate the current drag state of an annotation view. */
-typedef NS_ENUM(NSUInteger, MGLAnnotationViewDragState) {
- /**
- The view is not involved in a drag operation.
- */
- MGLAnnotationViewDragStateNone = 0,
- /**
- An action occurred that indicated the view should begin dragging.
-
- The map view automatically moves draggable annotation views to this state
- in response to the dragging the view after pressing and holding on it.
- */
- MGLAnnotationViewDragStateStarting,
- /**
- The view is in the midst of a drag operation and is actively tracking the
- user’s gesture.
- */
- MGLAnnotationViewDragStateDragging,
- /**
- An action occurred that indicated the view should cancel the drag
- operation.
- */
- MGLAnnotationViewDragStateCanceling,
- /**
- An action occurred that indicated the view was dropped by the user.
-
- The map view automatically moves annotation views to this state in response
- to the user lifting their finger at the end of a drag gesture.
- */
- MGLAnnotationViewDragStateEnding,
-};
-
-/**
- The `MGLAnnotationView` class is responsible for marking a point annotation
- with a view. Annotation views represent an annotation object, which is an
- object that corresponds to the `MGLAnnotation` protocol. When an annotation’s
- geographic coordinate is visible in the map view, the map view asks its
- delegate to a corresponding annotation view. If an annotation view is created
- with a reuse identifier, the map view may recycle the view when it goes
- offscreen.
-
- Annotation views are compatible with UIKit, Core Animation, and other Cocoa
- Touch frameworks. On the other hand, if you do not need animation or
- interactivity such as dragging, you can use an `MGLAnnotationImage` instead to
- conserve memory and optimize drawing performance.
- */
-MGL_EXPORT
-@interface MGLAnnotationView : UIView <NSSecureCoding>
-
-#pragma mark Initializing and Preparing the View
-
-/**
- Initializes and returns a new annotation view object.
-
- The reuse identifier provides a way for you to improve performance by recycling
- annotation views as they enter and leave the map’s viewport. As an annotation
- leaves the viewport, the map view moves its associated view to a reuse queue.
- When a new annotation becomes visible, you can request a view for that
- annotation by passing the appropriate reuse identifier string to the
- `-[MGLMapView dequeueReusableAnnotationViewWithIdentifier:]` method.
-
- @param reuseIdentifier A unique string identifier for this view that allows you
- to reuse this view with multiple similar annotations. You can set this
- parameter to `nil` if you don’t intend to reuse the view, but it is a good
- idea in general to specify a reuse identifier to avoid creating redundant
- views.
- @return The initialized annotation view object.
- */
-- (instancetype)initWithReuseIdentifier:(nullable NSString *)reuseIdentifier;
-
-/**
- Initializes and returns a new annotation view object.
-
- Providing an annotation allows you to explicitly associate the annotation instance
- with the new view and, in custom subclasses of `MGLAnnotationView`, customize the view
- based on properties of the annotation instance in an overridden initializer. However,
- annotation views that are reused will not necessarily be associated with the
- same annotation they were initialized with. Also, annotation views that are in
- the reuse queue will have a nil value for the annotation property. Passing an annotation
- instance to the view is optional and the map view will automatically associate annotations
- with views when views are provided to the map via the `-[MGLMapViewDelegate mapView:viewForAnnotation:]`
- method.
-
- The reuse identifier provides a way for you to improve performance by recycling
- annotation views as they enter and leave the map’s viewport. As an annotation
- leaves the viewport, the map view moves its associated view to a reuse queue.
- When a new annotation becomes visible, you can request a view for that
- annotation by passing the appropriate reuse identifier string to the
- `-[MGLMapView dequeueReusableAnnotationViewWithIdentifier:]` method.
-
- @param annotation The annotation object to associate with the new view.
- @param reuseIdentifier A unique string identifier for this view that allows you
- to reuse this view with multiple similar annotations. You can set this
- parameter to `nil` if you don’t intend to reuse the view, but it is a good
- idea in general to specify a reuse identifier to avoid creating redundant
- views.
- @return The initialized annotation view object.
- */
-- (instancetype)initWithAnnotation:(nullable id<MGLAnnotation>)annotation reuseIdentifier:(nullable NSString *)reuseIdentifier;
-
-/**
- Called when the view is removed from the reuse queue.
-
- The default implementation of this method does nothing. You can override it in
- your custom annotation view implementation to put the view in a known state
- before it is returned to your map view delegate.
- */
-- (void)prepareForReuse;
-
-/**
- The annotation object currently associated with the view.
-
- You should not change the value of this property directly. This property
- contains a non-`nil` value while the annotation view is visible on the map. If
- the view is queued, waiting to be reused, the value is `nil`.
- */
-@property (nonatomic, nullable) id <MGLAnnotation> annotation;
-
-/**
- The string that identifies that this annotation view is reusable.
-
- You specify the reuse identifier when you create the view. You use the
- identifier later to retrieve an annotation view that was created previously but
- which is currently unused because its annotation is not on-screen.
-
- If you define distinctly different types of annotations (with distinctly
- different annotation views to go with them), you can differentiate between the
- annotation types by specifying different reuse identifiers for each one.
- */
-@property (nonatomic, readonly, nullable) NSString *reuseIdentifier;
-
-#pragma mark Configuring the Appearance
-
-/**
- The offset, measured in points, at which to place the center of the view.
-
- By default, the center point of an annotation view is placed at the geographic
- coordinate point of the associated annotation. If you do not want the view to
- be centered, you can use this property to reposition the view. The offset’s
- `dx` and `dy` values are measured in points. Positive offset values move the
- annotation view down and to the right, while negative values move it up and to
- the left.
-
- Set the offset if the annotation view’s visual center point is somewhere other
- than the logical center of the view. For example, the view may contain an image
- that depicts a downward-pointing pushpin or thumbtack, with the tip positioned
- at the center-bottom of the view. In that case, you would set the offset’s `dx`
- to zero and its `dy` to half the height of the view.
- */
-@property (nonatomic) CGVector centerOffset;
-
-/**
- A Boolean value that determines whether the annotation view grows and shrinks
- as the distance between the viewpoint and the annotation view changes on a
- tilted map.
-
- When the value of this property is `YES` and the map is tilted, the annotation
- view appears smaller if it is towards the top of the view (closer to the
- horizon) and larger if it is towards the bottom of the view (closer to the
- viewpoint). This is also the behavior of `MGLAnnotationImage` objects. When the
- value of this property is `NO` or the map’s pitch is zero, the annotation view
- remains the same size regardless of its position on-screen.
-
- The default value of this property is `NO`. Keep this property set to `NO` if
- the view’s legibility is important.
-
- @note Scaling many on-screen annotation views can contribute to poor map
- performance. Consider keeping this property disabled if your use case
- involves hundreds or thousands of annotation views.
- */
-@property (nonatomic, assign) BOOL scalesWithViewingDistance;
-
-/**
- A Boolean value that determines whether the annotation view rotates together
- with the map.
-
- When the value of this property is `YES` and the map is rotated, the annotation
- view rotates. This is also the behavior of `MGLAnnotationImage` objects. When the
- value of this property is `NO` the annotation has its rotation angle fixed.
-
- The default value of this property is `NO`. Set this property to `YES` if the
- view’s rotation is important.
- */
-@property (nonatomic, assign) BOOL rotatesToMatchCamera;
-
-#pragma mark Managing the Selection State
-
-/**
- A Boolean value indicating whether the annotation view is currently selected.
-
- You should not set the value of this property directly. If the property is set
- to `YES`, the annotation view is displaying a callout.
-
- By default, this property is set to `NO` and becomes `YES` when the user taps
- the view. Selecting another annotation, whether it is associated with an
- `MGLAnnotationView` or `MGLAnnotationImage` object, deselects any currently
- selected view.
-
- Setting this property changes the view’s appearance to reflect the new value
- immediately. If you want the change to be animated, use the
- `-setSelected:animated:` method instead.
- */
-@property (nonatomic, assign, getter=isSelected) BOOL selected;
-
-/**
- Sets the selection state of the annotation view with an optional animation.
-
- You should not call this method directly. A map view calls this method in
- response to user interactions with the annotation. Subclasses may override this
- method in order to customize the appearance of the view depending on its
- selection state.
-
- @param selected `YES` if the view should display itself as selected; `NO`
- if it should display itself as unselected.
- @param animated `YES` if the change in selection state is animated; `NO` if the
- change is immediate.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/annotation-vie
- ws/">Annotation views</a> example to learn how to modify an
- `MGLAnnotationView`'s behavior when it is selected.
- */
-- (void)setSelected:(BOOL)selected animated:(BOOL)animated;
-
-/*
- A Boolean value indicating whether the annotation is enabled.
-
- The default value of this property is `YES`. If the value of this property is
- `NO`, the annotation view ignores touch events and cannot be selected.
- Subclasses may also customize the appearance of the view depending on its
- enabled state.
- */
-@property (nonatomic, assign, getter=isEnabled) BOOL enabled;
-
-#pragma mark Supporting Drag Operations
-
-/**
- A Boolean value indicating whether the annotation view is draggable.
-
- If this property is set to `YES`, the user can drag the annotation after
- pressing and holding the view, and the associated annotation object must also
- implement the `-setCoordinate:` method. The default value of this property is
- `NO`.
-
- Setting this property to `YES` lets the map view know that the annotation is
- always draggable. In other words, you cannot conditionalize drag operations by
- attempting to stop an operation that has already been initiated; doing so can
- lead to undefined behavior. Once begun, the drag operation should always
- continue to completion.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/draggable-views
- /">Draggable annotation views</a> to learn how to enable users to drag
- `MGLAnnotationView` objects on your map.
- */
-@property (nonatomic, assign, getter=isDraggable) BOOL draggable;
-
-/**
- The current drag state of the annotation view.
-
- All states are handled automatically when the `draggable` property is set to
- `YES`. To perform a custom animation in response to a change to this property,
- override the `-setDragState:animated:` method.
- */
-@property (nonatomic, readonly) MGLAnnotationViewDragState dragState;
-
-/**
- Sets the current drag state for the annotation view.
-
- You can override this method to animate a custom annotation view as the user
- drags it. As the system detects user actions that would indicate a drag, it
- calls this method to update the drag state.
- */
-- (void)setDragState:(MGLAnnotationViewDragState)dragState animated:(BOOL)animated NS_REQUIRES_SUPER;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLAnnotationView.mm b/platform/ios/src/MGLAnnotationView.mm
deleted file mode 100644
index 3a2aa60f6c..0000000000
--- a/platform/ios/src/MGLAnnotationView.mm
+++ /dev/null
@@ -1,387 +0,0 @@
-#import "MGLAnnotationView.h"
-#import "MGLAnnotationView_Private.h"
-#import "MGLMapView_Private.h"
-#import "MGLCalloutView.h"
-#import "MGLAnnotation.h"
-#import "MGLPointAnnotation.h"
-#import "MGLLoggingConfiguration_Private.h"
-
-#import "NSBundle+MGLAdditions.h"
-#import "NSValue+MGLAdditions.h"
-
-#include <mbgl/util/constants.hpp>
-
-@interface MGLAnnotationView () <UIGestureRecognizerDelegate>
-
-@property (nonatomic, readwrite, nullable) NSString *reuseIdentifier;
-@property (nonatomic, readwrite) CATransform3D lastAppliedScaleTransform;
-@property (nonatomic, readwrite) CGFloat lastPitch;
-@property (nonatomic, readwrite) CATransform3D lastAppliedRotationTransform;
-@property (nonatomic, readwrite) CGFloat lastDirection;
-@property (nonatomic, weak) UIPanGestureRecognizer *panGestureRecognizer;
-@property (nonatomic, weak) UILongPressGestureRecognizer *longPressRecognizer;
-@property (nonatomic, weak) MGLMapView *mapView;
-
-@end
-
-@implementation MGLAnnotationView
-
-+ (BOOL)supportsSecureCoding {
- return YES;
-}
-
-- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier {
- MGLLogDebug(@"Initializing with identifier: %@", reuseIdentifier);
- self = [super initWithFrame:CGRectZero];
- if (self) {
- [self commonInitWithAnnotation:nil reuseIdentifier:reuseIdentifier];
- }
- return self;
-}
-
-- (instancetype)initWithAnnotation:(nullable id<MGLAnnotation>)annotation reuseIdentifier:(nullable NSString *)reuseIdentifier {
- MGLLogDebug(@"Initializing with annotation: %@ reuseIdentifier: %@", annotation, reuseIdentifier);
- self = [super initWithFrame:CGRectZero];
- if (self) {
- [self commonInitWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
- }
- return self;
-}
-
-- (void)commonInitWithAnnotation:(nullable id<MGLAnnotation>)annotation reuseIdentifier:(nullable NSString *)reuseIdentifier {
- _lastAppliedScaleTransform = CATransform3DIdentity;
- _lastAppliedRotationTransform = CATransform3DIdentity;
- _annotation = annotation;
- _reuseIdentifier = [reuseIdentifier copy];
- _enabled = YES;
-}
-
-- (instancetype)initWithCoder:(NSCoder *)decoder {
- MGLLogInfo(@"Initializing with coder.");
- if (self = [super initWithCoder:decoder]) {
- _reuseIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"reuseIdentifier"];
- _annotation = [decoder decodeObjectOfClass:[NSObject class] forKey:@"annotation"];
- _centerOffset = [decoder decodeCGVectorForKey:@"centerOffset"];
- _scalesWithViewingDistance = [decoder decodeBoolForKey:@"scalesWithViewingDistance"];
- _rotatesToMatchCamera = [decoder decodeBoolForKey:@"rotatesToMatchCamera"];
- _selected = [decoder decodeBoolForKey:@"selected"];
- _enabled = [decoder decodeBoolForKey:@"enabled"];
- self.draggable = [decoder decodeBoolForKey:@"draggable"];
- }
- return self;
-}
-
-- (void)encodeWithCoder:(NSCoder *)coder {
- [super encodeWithCoder:coder];
- [coder encodeObject:_reuseIdentifier forKey:@"reuseIdentifier"];
- [coder encodeObject:_annotation forKey:@"annotation"];
- [coder encodeCGVector:_centerOffset forKey:@"centerOffset"];
- [coder encodeBool:_scalesWithViewingDistance forKey:@"scalesWithViewingDistance"];
- [coder encodeBool:_rotatesToMatchCamera forKey:@"rotatesToMatchCamera"];
- [coder encodeBool:_selected forKey:@"selected"];
- [coder encodeBool:_enabled forKey:@"enabled"];
- [coder encodeBool:_draggable forKey:@"draggable"];
-}
-
-- (void)prepareForReuse
-{
- // Intentionally left blank. The default implementation of this method does nothing.
-}
-
-- (void)setCenterOffset:(CGVector)centerOffset
-{
- MGLLogDebug(@"Setting centerOffset: %@", NSStringFromCGVector(centerOffset));
- _centerOffset = centerOffset;
- self.center = self.center;
-}
-
-- (void)setSelected:(BOOL)selected
-{
- MGLLogDebug(@"Setting selected: %@", MGLStringFromBOOL(selected));
- [self setSelected:selected animated:NO];
-}
-
-- (void)setSelected:(BOOL)selected animated:(BOOL)animated
-{
- MGLLogDebug(@"Setting selected: %@ animated: %@", MGLStringFromBOOL(selected), MGLStringFromBOOL(animated));
- [self willChangeValueForKey:@"selected"];
- _selected = selected;
- [self didChangeValueForKey:@"selected"];
-}
-
-- (CGPoint)center
-{
- CGPoint center = super.center;
- center.x -= _centerOffset.dx;
- center.y -= _centerOffset.dy;
- return center;
-}
-
-- (void)setCenter:(CGPoint)center
-{
- MGLLogDebug(@"Setting center: %@", NSStringFromCGPoint(center));
- center.x += _centerOffset.dx;
- center.y += _centerOffset.dy;
-
- super.center = center;
- [self updateScaleTransformForViewingDistance];
- [self updateRotateTransform];
-}
-
-- (void)setScalesWithViewingDistance:(BOOL)scalesWithViewingDistance
-{
- MGLLogDebug(@"Setting scaleWithViewingDistance: %@", MGLStringFromBOOL(scalesWithViewingDistance));
- if (_scalesWithViewingDistance != scalesWithViewingDistance)
- {
- _scalesWithViewingDistance = scalesWithViewingDistance;
- [self updateScaleTransformForViewingDistance];
- }
-}
-
-- (void)updateScaleTransformForViewingDistance
-{
- if (self.scalesWithViewingDistance == NO || self.dragState == MGLAnnotationViewDragStateDragging) return;
-
- CGFloat superviewHeight = CGRectGetHeight(self.superview.frame);
- if (superviewHeight > 0.0) {
- // Find the maximum amount of scale reduction to apply as the view's center moves from the top
- // of the superview to the bottom. For example, if this view's center has moved 25% of the way
- // from the top of the superview towards the bottom then the maximum scale reduction is 1 - .25
- // or 75%. The range goes from a maximum of 100% to 0% as the view moves from the top to the bottom
- // along the y axis of its superview.
- CGFloat maxScaleReduction = 1.0 - self.center.y / superviewHeight;
-
- // Since it is possible for the map view to report a pitch less than 0 due to the nature of
- // how the gesture information is captured, the value is guarded with MAX.
- CGFloat pitch = MAX(self.mapView.camera.pitch, 0);
-
- // Return early if the map view currently has no pitch and was not previously pitched.
- if (!pitch && !_lastPitch) return;
- _lastPitch = pitch;
-
- // The pitch intensity represents how much the map view is actually pitched compared to
- // what is possible. The value will range from 0% (not pitched at all) to 100% (pitched as much
- // as the map view will allow). The map view's maximum pitch is defined in `mbgl::util::PITCH_MAX`.
- CGFloat pitchIntensity = pitch / MGLDegreesFromRadians(mbgl::util::PITCH_MAX);
-
- // The pitch adjusted scale is the inverse proportion of the maximum possible scale reduction
- // multiplied by the pitch intensity. For example, if the maximum scale reduction is 75% and the
- // map view is 50% pitched then the annotation view should be reduced by 37.5% (.75 * .5). The
- // reduction is then normalized for a scale of 1.0.
- CGFloat pitchAdjustedScale = 1.0 - maxScaleReduction * pitchIntensity;
-
- // We keep track of each viewing distance scale transform that we apply. Each iteration,
- // we can account for it so that we don't get cumulative scaling every time we move.
- // We also avoid clobbering any existing transform passed in by the client or this SDK.
- CATransform3D undoOfLastScaleTransform = CATransform3DInvert(_lastAppliedScaleTransform);
- CATransform3D newScaleTransform = CATransform3DMakeScale(pitchAdjustedScale, pitchAdjustedScale, 1);
- CATransform3D effectiveTransform = CATransform3DConcat(undoOfLastScaleTransform, newScaleTransform);
- self.layer.transform = CATransform3DConcat(self.layer.transform, effectiveTransform);
- _lastAppliedScaleTransform = newScaleTransform;
- }
-}
-
-- (void)setRotatesToMatchCamera:(BOOL)rotatesToMatchCamera
-{
- MGLLogDebug(@"Setting rotatesToMatchCamera: %@", MGLStringFromBOOL(rotatesToMatchCamera));
- if (_rotatesToMatchCamera != rotatesToMatchCamera)
- {
- _rotatesToMatchCamera = rotatesToMatchCamera;
- [self updateRotateTransform];
- }
-}
-
-- (void)updateRotateTransform
-{
- if (self.rotatesToMatchCamera == NO) return;
-
- CGFloat direction = -MGLRadiansFromDegrees(self.mapView.direction);
-
- // Return early if the map view has the same rotation as the already-applied transform.
- if (direction == _lastDirection) return;
- _lastDirection = direction;
-
- // We keep track of each rotation transform that we apply. Each iteration,
- // we can account for it so that we don't get cumulative rotation every time we move.
- // We also avoid clobbering any existing transform passed in by the client or this SDK.
- CATransform3D undoOfLastRotationTransform = CATransform3DInvert(_lastAppliedRotationTransform);
- CATransform3D newRotationTransform = CATransform3DMakeRotation(direction, 0, 0, 1);
- CATransform3D effectiveTransform = CATransform3DConcat(undoOfLastRotationTransform, newRotationTransform);
- self.layer.transform = CATransform3DConcat(self.layer.transform, effectiveTransform);
- _lastAppliedRotationTransform = newRotationTransform;
-}
-
-#pragma mark - Draggable
-
-- (void)setDraggable:(BOOL)draggable
-{
- MGLLogDebug(@"Setting draggable: %@", MGLStringFromBOOL(draggable));
- [self willChangeValueForKey:@"draggable"];
- _draggable = draggable;
- [self didChangeValueForKey:@"draggable"];
-
- if (draggable)
- {
- [self enableDrag];
- }
- else
- {
- [self disableDrag];
- }
-}
-
-- (void)enableDrag
-{
- if (!_longPressRecognizer)
- {
- UILongPressGestureRecognizer *recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
- recognizer.delegate = self;
- [self addGestureRecognizer:recognizer];
- _longPressRecognizer = recognizer;
- }
-
- if (!_panGestureRecognizer)
- {
- UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
- recognizer.delegate = self;
- [self addGestureRecognizer:recognizer];
- _panGestureRecognizer = recognizer;
- }
-}
-
-- (void)disableDrag
-{
- [self removeGestureRecognizer:_longPressRecognizer];
- [self removeGestureRecognizer:_panGestureRecognizer];
-}
-
-- (void)handleLongPress:(UILongPressGestureRecognizer *)sender
-{
- switch (sender.state) {
- case UIGestureRecognizerStateBegan:
- self.dragState = MGLAnnotationViewDragStateStarting;
- break;
- case UIGestureRecognizerStateChanged:
- self.dragState = MGLAnnotationViewDragStateDragging;
- break;
- case UIGestureRecognizerStateCancelled:
- self.dragState = MGLAnnotationViewDragStateCanceling;
- break;
- case UIGestureRecognizerStateEnded:
- self.dragState = MGLAnnotationViewDragStateEnding;
- break;
- case UIGestureRecognizerStateFailed:
- self.dragState = MGLAnnotationViewDragStateNone;
- break;
- case UIGestureRecognizerStatePossible:
- break;
- }
-}
-
-- (void)handlePan:(UIPanGestureRecognizer *)sender
-{
- self.center = [sender locationInView:sender.view.superview];
-
- if (sender.state == UIGestureRecognizerStateEnded) {
- self.dragState = MGLAnnotationViewDragStateNone;
- }
-}
-
-- (void)setDragState:(MGLAnnotationViewDragState)dragState
-{
- MGLLogDebug(@"Setting dragState: %lu", (unsigned long)dragState);
- [self setDragState:dragState animated:YES];
-}
-
-- (void)setDragState:(MGLAnnotationViewDragState)dragState animated:(BOOL)animated
-{
- MGLLogDebug(@"Setting dragState: %lu animated: %@", (unsigned long)dragState, MGLStringFromBOOL(animated));
- [self willChangeValueForKey:@"dragState"];
- _dragState = dragState;
- [self didChangeValueForKey:@"dragState"];
-
- if (dragState == MGLAnnotationViewDragStateStarting)
- {
- [self.mapView.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated];
- [self.superview bringSubviewToFront:self];
- }
- else if (dragState == MGLAnnotationViewDragStateCanceling)
- {
- if (!self.annotation) {
- [NSException raise:NSInvalidArgumentException
- format:@"Annotation property should not be nil."];
- }
- self.panGestureRecognizer.enabled = NO;
- self.longPressRecognizer.enabled = NO;
- self.center = [self.mapView convertCoordinate:self.annotation.coordinate toPointToView:self.mapView];
- self.panGestureRecognizer.enabled = YES;
- self.longPressRecognizer.enabled = YES;
- self.dragState = MGLAnnotationViewDragStateNone;
- }
- else if (dragState == MGLAnnotationViewDragStateEnding)
- {
- if ([self.annotation respondsToSelector:@selector(setCoordinate:)])
- {
- CLLocationCoordinate2D coordinate = [self.mapView convertPoint:self.center toCoordinateFromView:self.mapView];
- [(NSObject *)self.annotation setValue:[NSValue valueWithMGLCoordinate:coordinate] forKey:@"coordinate"];
- }
- }
-}
-
-- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
-{
- BOOL isDragging = self.dragState == MGLAnnotationViewDragStateDragging;
-
- if (gestureRecognizer == _panGestureRecognizer && !(isDragging))
- {
- return NO;
- }
-
- return YES;
-}
-
-- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
-{
- return otherGestureRecognizer == _longPressRecognizer || otherGestureRecognizer == _panGestureRecognizer;
-}
-
-#pragma mark UIAccessibility methods
-
-- (BOOL)isAccessibilityElement {
- return !self.hidden;
-}
-
-- (UIAccessibilityTraits)accessibilityTraits {
- return UIAccessibilityTraitButton | UIAccessibilityTraitAdjustable;
-}
-
-- (NSString *)accessibilityLabel {
- return [self.annotation respondsToSelector:@selector(title)] ? self.annotation.title : super.accessibilityLabel;
-}
-
-- (NSString *)accessibilityValue {
- return [self.annotation respondsToSelector:@selector(subtitle)] ? self.annotation.subtitle : super.accessibilityValue;
-}
-
-- (NSString *)accessibilityHint {
- return NSLocalizedStringWithDefaultValue(@"ANNOTATION_A11Y_HINT", nil, nil, @"Shows more info", @"Accessibility hint");
-}
-
-- (CGRect)accessibilityFrame {
- CGRect accessibilityFrame = self.frame;
- CGRect minimumFrame = CGRectInset({ self.center, CGSizeZero },
- -MGLAnnotationAccessibilityElementMinimumSize.width / 2,
- -MGLAnnotationAccessibilityElementMinimumSize.height / 2);
- accessibilityFrame = CGRectUnion(accessibilityFrame, minimumFrame);
- return accessibilityFrame;
-}
-
-- (void)accessibilityIncrement {
- [self.superview accessibilityIncrement];
-}
-
-- (void)accessibilityDecrement {
- [self.superview accessibilityDecrement];
-}
-
-@end
diff --git a/platform/ios/src/MGLAnnotationView_Private.h b/platform/ios/src/MGLAnnotationView_Private.h
deleted file mode 100644
index c4695051c5..0000000000
--- a/platform/ios/src/MGLAnnotationView_Private.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#import "MGLAnnotationView.h"
-#import "MGLAnnotation.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@class MGLMapView;
-
-@interface MGLAnnotationView (Private)
-
-@property (nonatomic, readwrite, nullable) NSString *reuseIdentifier;
-@property (nonatomic, weak) MGLMapView *mapView;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLCalloutView.h b/platform/ios/src/MGLCalloutView.h
deleted file mode 100644
index 71bae71d07..0000000000
--- a/platform/ios/src/MGLCalloutView.h
+++ /dev/null
@@ -1,140 +0,0 @@
-#import <Foundation/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@protocol MGLCalloutViewDelegate;
-@protocol MGLAnnotation;
-
-/**
- A protocol for a `UIView` subclass that displays information about a selected
- annotation near that annotation.
-
- To receive updates from an object that conforms to the `MGLCalloutView` protocol,
- use the optional methods available in the `MGLCalloutViewDelegate` protocol.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/custom-callout/">
- Display custom views as callouts</a> example to learn how to customize an
- `MGLCalloutView`.
- */
-@protocol MGLCalloutView <NSObject>
-
-/**
- An object conforming to the `MGLAnnotation` protocol whose details this callout
- view displays.
- */
-@property (nonatomic, strong) id <MGLAnnotation> representedObject;
-
-/**
- A view that the user may tap to perform an action. This view is conventionally
- positioned on the left side of the callout view.
- */
-@property (nonatomic, strong) UIView *leftAccessoryView;
-
-/**
- A view that the user may tap to perform an action. This view is conventionally
- positioned on the right side of the callout view.
- */
-@property (nonatomic, strong) UIView *rightAccessoryView;
-
-/**
- An object conforming to the `MGLCalloutViewDelegate` method that receives
- messages related to the callout view’s interactive subviews.
- */
-@property (nonatomic, weak) id<MGLCalloutViewDelegate> delegate;
-
-/**
- Presents a callout view by adding it to `view` and pointing at the given rect
- of `view`’s bounds. Constrains the callout to the bounds of the given view.
- */
-- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated __attribute__((unavailable("Use `-presentCalloutFromRect:inView:constrainedToRect:animated:` instead.")));
-
-
-/**
- Presents a callout view by adding it to `view` and pointing at the given rect
- of `view`’s bounds. Constrains the callout to the rect in the space of `view`.
- */
-- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToRect:(CGRect)constrainedRect animated:(BOOL)animated;
-
-/**
- Dismisses the callout view.
- */
-- (void)dismissCalloutAnimated:(BOOL)animated;
-
-@optional
-
-/**
- If implemented, should provide margins to expand the rect the callout is presented from.
-
- These are used to determine positioning. Currently only the top and bottom properties of the return
- value are used. For example, `{ .top = -50.0, .left = -10.0, .bottom = 0.0, .right = -10.0 }` indicates
- a 50 point margin above the presentation origin rect (and 10 point margins to the left and the right)
- in which the callout is assumed to be displayed.
-
- There are no assumed defaults for these margins, as they should be calculated from the callout that
- is to be presented. For example, `SMCalloutView` generates the top margin from the callout height, but
- the left and right margins from a minimum width that the callout should have.
-
- @param rect Rect that the callout is presented from. This should be the same as the one passed in
- `-[MGLCalloutView presentCalloutFromRect:inView:constrainedToRect:animated:]`
- @return `UIEdgeInsets` representing the margins. Values should be negative.
- */
-- (UIEdgeInsets)marginInsetsHintForPresentationFromRect:(CGRect)rect NS_SWIFT_NAME(marginInsetsHintForPresentation(from:));
-
-/**
- A Boolean value indicating whether the callout view should be anchored to
- the corresponding annotation. You can adjust the callout view’s precise location by
- overriding -[UIView setCenter:]. The callout view will not be anchored to the
- annotation if this optional property is unimplemented.
- */
-@property (nonatomic, readonly, assign, getter=isAnchoredToAnnotation) BOOL anchoredToAnnotation;
-
-/**
- A Boolean value indicating whether the callout view should be dismissed automatically
- when the map view’s viewport changes. Note that a single tap on the map view
- still dismisses the callout view regardless of the value of this property.
- The callout view will be dismissed if this optional property is unimplemented.
- */
-@property (nonatomic, readonly, assign) BOOL dismissesAutomatically;
-
-@end
-
-/**
- The `MGLCalloutViewDelegate` protocol defines a set of optional methods that
- you can use to receive messages from an object that conforms to the
- `MGLCalloutView` protocol. The callout view uses these methods to inform the
- delegate that the user has interacted with the the callout view.
- */
-@protocol MGLCalloutViewDelegate <NSObject>
-
-@optional
-/**
- Returns a Boolean value indicating whether the entire callout view “highlights”
- when tapped. The default value is `YES`, which means the callout view
- highlights when tapped.
-
- The return value of this method is ignored unless the delegate also responds to
- the `-calloutViewTapped` method.
- */
-- (BOOL)calloutViewShouldHighlight:(UIView<MGLCalloutView> *)calloutView;
-
-/**
- Tells the delegate that the callout view has been tapped.
- */
-- (void)calloutViewTapped:(UIView<MGLCalloutView> *)calloutView;
-
-/**
- Called before the callout view appears on screen, or before the appearance
- animation will start.
- */
-- (void)calloutViewWillAppear:(UIView<MGLCalloutView> *)calloutView;
-
-/**
- Called after the callout view appears on screen, or after the appearance
- animation is complete.
- */
-- (void)calloutViewDidAppear:(UIView<MGLCalloutView> *)calloutView;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLCameraChangeReason.h b/platform/ios/src/MGLCameraChangeReason.h
deleted file mode 100644
index f439d3e7ea..0000000000
--- a/platform/ios/src/MGLCameraChangeReason.h
+++ /dev/null
@@ -1,65 +0,0 @@
-#import "MGLFoundation.h"
-
-/**
- :nodoc:
- Bitmask values that describe why a camera move occurred.
-
- Values of this type are passed to the `MGLMapView`'s delegate in the following methods:
-
- - `-mapView:shouldChangeFromCamera:toCamera:reason:`
- - `-mapView:regionWillChangeWithReason:animated:`
- - `-mapView:regionIsChangingWithReason:`
- - `-mapView:regionDidChangeWithReason:animated:`
-
- It's important to note that it's almost impossible to perform a rotate without zooming (in or out),
- so if you'll often find `MGLCameraChangeReasonGesturePinch` set alongside `MGLCameraChangeReasonGestureRotate`.
-
- Since there are several reasons why a zoom or rotation has occurred, it is worth considering
- creating a combined constant, for example:
-
- ```
- static const MGLCameraChangeReason anyZoom = MGLCameraChangeReasonGesturePinch |
- MGLCameraChangeReasonGestureZoomIn |
- MGLCameraChangeReasonGestureZoomOut |
- MGLCameraChangeReasonGestureOneFingerZoom;
-
- static const MGLCameraChangeReason anyRotation = MGLCameraChangeReasonResetNorth | MGLCameraChangeReasonGestureRotate;
- ```
- */
-typedef NS_OPTIONS(NSUInteger, MGLCameraChangeReason)
-{
- /// :nodoc: The reason for the camera change has not be specified.
- MGLCameraChangeReasonNone = 0,
-
- /// :nodoc: Set when a public API that moves the camera is called. This may be set for some gestures,
- /// for example MGLCameraChangeReasonResetNorth.
- MGLCameraChangeReasonProgrammatic = 1 << 0,
-
- /// :nodoc: The user tapped the compass to reset the map orientation so North is up.
- MGLCameraChangeReasonResetNorth = 1 << 1,
-
- /// :nodoc: The user panned the map.
- MGLCameraChangeReasonGesturePan = 1 << 2,
-
- /// :nodoc: The user pinched to zoom in/out.
- MGLCameraChangeReasonGesturePinch = 1 << 3,
-
- // :nodoc: The user rotated the map.
- MGLCameraChangeReasonGestureRotate = 1 << 4,
-
- /// :nodoc: The user zoomed the map in (one finger double tap).
- MGLCameraChangeReasonGestureZoomIn = 1 << 5,
-
- /// :nodoc: The user zoomed the map out (two finger single tap).
- MGLCameraChangeReasonGestureZoomOut = 1 << 6,
-
- /// :nodoc: The user long pressed on the map for a quick zoom (single tap, then long press and drag up/down).
- MGLCameraChangeReasonGestureOneFingerZoom = 1 << 7,
-
- // :nodoc: The user panned with two fingers to tilt the map (two finger drag).
- MGLCameraChangeReasonGestureTilt = 1 << 8,
-
- // :nodoc: Cancelled
- MGLCameraChangeReasonTransitionCancelled = 1 << 16
-
-};
diff --git a/platform/ios/src/MGLCompactCalloutView.h b/platform/ios/src/MGLCompactCalloutView.h
deleted file mode 100644
index 5cecf37ff6..0000000000
--- a/platform/ios/src/MGLCompactCalloutView.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#import "SMCalloutView.h"
-#import "MGLCalloutView.h"
-
-/**
- A concrete implementation of `MGLCalloutView` based on
- <a href="https://github.com/nfarina/calloutview">SMCalloutView</a>. This
- callout view displays the represented annotation’s title, subtitle, and
- accessory views in a compact, two-line layout.
- */
-@interface MGLCompactCalloutView : MGLSMCalloutView <MGLCalloutView>
-
-+ (instancetype)platformCalloutView;
-
-@end
diff --git a/platform/ios/src/MGLCompactCalloutView.m b/platform/ios/src/MGLCompactCalloutView.m
deleted file mode 100644
index e499b7832f..0000000000
--- a/platform/ios/src/MGLCompactCalloutView.m
+++ /dev/null
@@ -1,39 +0,0 @@
-#import "MGLCompactCalloutView.h"
-
-#import "MGLAnnotation.h"
-
-@implementation MGLCompactCalloutView
-{
- id <MGLAnnotation> _representedObject;
-}
-
-@synthesize representedObject = _representedObject;
-
-+ (instancetype)platformCalloutView
-{
- return [[self alloc] init];
-}
-
-- (BOOL)isAnchoredToAnnotation {
- return YES;
-}
-
-- (BOOL)dismissesAutomatically {
- return NO;
-}
-
-- (void)setRepresentedObject:(id <MGLAnnotation>)representedObject
-{
- _representedObject = representedObject;
-
- if ([representedObject respondsToSelector:@selector(title)])
- {
- self.title = representedObject.title;
- }
- if ([representedObject respondsToSelector:@selector(subtitle)])
- {
- self.subtitle = representedObject.subtitle;
- }
-}
-
-@end
diff --git a/platform/ios/src/MGLCompassButton.h b/platform/ios/src/MGLCompassButton.h
deleted file mode 100644
index 9c3d9d1c77..0000000000
--- a/platform/ios/src/MGLCompassButton.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#import <UIKit/UIKit.h>
-
-#import "MGLTypes.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-/**
- A specialized view that displays the current compass heading for its associated map.
- */
-MGL_EXPORT
-@interface MGLCompassButton : UIImageView
-
-/**
- The visibility of the compass button.
-
- You can configure a compass button to be visible all the time or only when the compass heading changes.
- */
-@property (nonatomic, assign) MGLOrnamentVisibility compassVisibility;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLCompassButton.mm b/platform/ios/src/MGLCompassButton.mm
deleted file mode 100644
index 5bbd9bf923..0000000000
--- a/platform/ios/src/MGLCompassButton.mm
+++ /dev/null
@@ -1,135 +0,0 @@
-#import "MGLCompassButton_Private.h"
-#import "MGLCompassDirectionFormatter.h"
-
-#import "MGLGeometry.h"
-
-#import "MGLMapView_Private.h"
-#import "UIImage+MGLAdditions.h"
-#import "NSBundle+MGLAdditions.h"
-
-#include <mbgl/math/wrap.hpp>
-
-@interface MGLCompassButton ()
-
-@property (nonatomic, weak) MGLMapView *mapView;
-@property (nonatomic) MGLCompassDirectionFormatter *accessibilityCompassFormatter;
-
-@end
-
-@implementation MGLCompassButton
-
-+ (instancetype)compassButtonWithMapView:(MGLMapView *)mapView {
- return [[MGLCompassButton alloc] initWithMapView:mapView];
-}
-
-- (instancetype)initWithMapView:(MGLMapView *)mapView {
- if (self = [super init]) {
- self.mapView = mapView;
- [self commonInit];
- }
- return self;
-}
-
-- (void)commonInit {
- self.image = self.compassImage;
-
- self.compassVisibility = MGLOrnamentVisibilityAdaptive;
-
- self.alpha = 0;
- self.userInteractionEnabled = YES;
- self.translatesAutoresizingMaskIntoConstraints = NO;
-
- UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
- [self addGestureRecognizer:tapGesture];
-
- self.accessibilityTraits = UIAccessibilityTraitButton;
- self.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"COMPASS_A11Y_LABEL", nil, nil, @"Compass", @"Accessibility label");
- self.accessibilityHint = NSLocalizedStringWithDefaultValue(@"COMPASS_A11Y_HINT", nil, nil, @"Rotates the map to face due north", @"Accessibility hint");
-
- self.accessibilityCompassFormatter = [[MGLCompassDirectionFormatter alloc] init];
- self.accessibilityCompassFormatter.unitStyle = NSFormattingUnitStyleLong;
-
- [self sizeToFit];
-}
-
-- (void)setCompassVisibility:(MGLOrnamentVisibility)compassVisibility {
- if (_compassVisibility == compassVisibility) { return; }
- _compassVisibility = compassVisibility;
-
- [self updateCompassAnimated:NO];
-}
-
-- (UIImage *)compassImage {
- UIImage *scaleImage = [UIImage mgl_resourceImageNamed:@"Compass"];
- UIGraphicsBeginImageContextWithOptions(scaleImage.size, NO, UIScreen.mainScreen.scale);
- [scaleImage drawInRect:{CGPointZero, scaleImage.size}];
-
- UIFont *northFont;
- if (@available(iOS 13.0, *)) {
- northFont = [UIFont systemFontOfSize:11 weight:UIFontWeightLight];
- } else {
- northFont = [UIFont systemFontOfSize:11 weight:UIFontWeightUltraLight];
- }
-
- NSAttributedString *north = [[NSAttributedString alloc] initWithString:NSLocalizedStringWithDefaultValue(@"COMPASS_NORTH", nil, nil, @"N", @"Compass abbreviation for north") attributes:@{
- NSFontAttributeName: northFont,
- NSForegroundColorAttributeName: [UIColor whiteColor],
- }];
- CGRect stringRect = CGRectMake((scaleImage.size.width - north.size.width) / 2,
- scaleImage.size.height * 0.435,
- north.size.width, north.size.height);
- [north drawInRect:stringRect];
-
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return image;
-}
-
-- (void)handleTapGesture:(__unused UITapGestureRecognizer *)sender {
- [self.mapView resetNorth];
-}
-
-- (void)updateCompass {
- [self updateCompassAnimated:YES];
-}
-
-- (void)updateCompassAnimated:(BOOL)animated {
- CLLocationDirection direction = self.mapView.direction;
- CLLocationDirection plateDirection = mbgl::util::wrap(-direction, 0., 360.);
- self.transform = CGAffineTransformMakeRotation(MGLRadiansFromDegrees(plateDirection));
-
- self.isAccessibilityElement = direction > 0;
- self.accessibilityValue = [self.accessibilityCompassFormatter stringFromDirection:direction];
-
- switch (self.compassVisibility) {
- case MGLOrnamentVisibilityAdaptive:
- if (direction > 0 && self.alpha < 1) {
- [self showCompass:animated];
- } else if (direction == 0 && self.alpha > 0) {
- [self hideCompass:animated];
- }
- break;
- case MGLOrnamentVisibilityVisible:
- [self showCompass:animated];
- break;
- case MGLOrnamentVisibilityHidden:
- [self hideCompass:animated];
- break;
- }
-}
-
-- (void)showCompass:(BOOL)animated {
- animated ? [self animateToAlpha:1] : [self setAlpha:1];
-}
-
-- (void)hideCompass:(BOOL)animated {
- animated ? [self animateToAlpha:0] : [self setAlpha:0];
-}
-
-- (void)animateToAlpha:(CGFloat)alpha {
- [UIView animateWithDuration:MGLAnimationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
- self.alpha = alpha;
- } completion:nil];
-}
-
-@end
diff --git a/platform/ios/src/MGLCompassButton_Private.h b/platform/ios/src/MGLCompassButton_Private.h
deleted file mode 100644
index 9ef55cfa5b..0000000000
--- a/platform/ios/src/MGLCompassButton_Private.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#import <UIKit/UIKit.h>
-
-#import "MGLCompassButton.h"
-
-@class MGLMapView;
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface MGLCompassButton (Private)
-
-+ (instancetype)compassButtonWithMapView:(MGLMapView *)mapView;
-
-@property (nonatomic, weak) MGLMapView *mapView;
-
-- (void)updateCompass;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.h b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.h
deleted file mode 100644
index 35fb31a342..0000000000
--- a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#import <UIKit/UIKit.h>
-#import "MGLUserLocationAnnotationView.h"
-
-extern const CGFloat MGLUserLocationAnnotationDotSize;
-extern const CGFloat MGLUserLocationAnnotationHaloSize;
-
-extern const CGFloat MGLUserLocationAnnotationPuckSize;
-extern const CGFloat MGLUserLocationAnnotationArrowSize;
-
-// Threshold in radians between heading indicator rotation updates.
-extern const CGFloat MGLUserLocationHeadingUpdateThreshold;
-
-@interface MGLFaux3DUserLocationAnnotationView : MGLUserLocationAnnotationView
-
-@end
diff --git a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m
deleted file mode 100644
index a1d9fb1d48..0000000000
--- a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m
+++ /dev/null
@@ -1,469 +0,0 @@
-#import "MGLFaux3DUserLocationAnnotationView.h"
-
-#import "MGLMapView.h"
-#import "MGLUserLocation.h"
-#import "MGLUserLocationHeadingIndicator.h"
-#import "MGLUserLocationHeadingArrowLayer.h"
-#import "MGLUserLocationHeadingBeamLayer.h"
-
-const CGFloat MGLUserLocationAnnotationDotSize = 22.0;
-const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
-
-const CGFloat MGLUserLocationAnnotationPuckSize = 45.0;
-const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.5;
-
-const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01;
-
-@implementation MGLFaux3DUserLocationAnnotationView
-{
- BOOL _puckModeActivated;
-
- CALayer *_puckDot;
- CAShapeLayer *_puckArrow;
-
- CALayer<MGLUserLocationHeadingIndicator> *_headingIndicatorLayer;
- CALayer *_accuracyRingLayer;
- CALayer *_dotBorderLayer;
- CALayer *_dotLayer;
- CALayer *_haloLayer;
-
- CLLocationDirection _oldHeadingAccuracy;
- CLLocationAccuracy _oldHorizontalAccuracy;
- double _oldZoom;
- double _oldPitch;
-}
-
-- (CALayer *)hitTestLayer
-{
- // Only the main dot should be interactive (i.e., exclude the accuracy ring and halo).
- return _dotBorderLayer ?: _puckDot;
-}
-
-- (void)update
-{
- if (CGSizeEqualToSize(self.frame.size, CGSizeZero))
- {
- CGFloat frameSize = (self.mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse) ? MGLUserLocationAnnotationPuckSize : MGLUserLocationAnnotationDotSize;
- [self updateFrameWithSize:frameSize];
- }
-
- if (CLLocationCoordinate2DIsValid(self.userLocation.coordinate))
- {
- (self.mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse) ? [self drawPuck] : [self drawDot];
- [self updatePitch];
- }
-
- _haloLayer.hidden = ! CLLocationCoordinate2DIsValid(self.mapView.userLocation.coordinate) || self.mapView.userLocation.location.horizontalAccuracy > 10;
-}
-
-- (void)setTintColor:(UIColor *)tintColor
-{
- CGColorRef newTintColor = [tintColor CGColor];
-
- if (_puckModeActivated)
- {
- _puckArrow.fillColor = newTintColor;
- _puckArrow.strokeColor = newTintColor;
- }
- else
- {
- _accuracyRingLayer.backgroundColor = newTintColor;
- _haloLayer.backgroundColor = newTintColor;
- _dotLayer.backgroundColor = newTintColor;
- [_headingIndicatorLayer updateTintColor:newTintColor];
- }
-}
-
-- (void)updatePitch
-{
- if (self.mapView.camera.pitch != _oldPitch)
- {
- // disable implicit animation
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
-
- CATransform3D t = CATransform3DRotate(CATransform3DIdentity, MGLRadiansFromDegrees(self.mapView.camera.pitch), 1.0, 0, 0);
- self.layer.sublayerTransform = t;
-
- [self updateFaux3DEffect];
-
- [CATransaction commit];
-
- _oldPitch = self.mapView.camera.pitch;
- }
-}
-
-- (void)updateFaux3DEffect
-{
- CGFloat pitch = MGLRadiansFromDegrees(self.mapView.camera.pitch);
-
- if (_puckDot)
- {
- _puckDot.shadowOffset = CGSizeMake(0, fmaxf(pitch * 10.f, 1.f));
- _puckDot.shadowRadius = fmaxf(pitch * 5.f, 0.75f);
- }
-
- if (_dotBorderLayer)
- {
- _dotBorderLayer.shadowOffset = CGSizeMake(0.f, pitch * 10.f);
- _dotBorderLayer.shadowRadius = fmaxf(pitch * 5.f, 3.f);
- }
-
- if (_dotLayer)
- {
- _dotLayer.zPosition = pitch * 2.f;
- }
-}
-
-- (void)updateFrameWithSize:(CGFloat)size
-{
- CGSize newSize = CGSizeMake(size, size);
- if (CGSizeEqualToSize(self.frame.size, newSize))
- {
- return;
- }
-
- // Update frame size, keeping the existing center point.
- CGPoint oldCenter = self.center;
- CGRect newFrame = self.frame;
- newFrame.size = newSize;
- [self setFrame:newFrame];
- [self setCenter:oldCenter];
-}
-
-- (void)drawPuck
-{
- if ( ! _puckModeActivated)
- {
- self.layer.sublayers = nil;
-
- _headingIndicatorLayer = nil;
- _accuracyRingLayer = nil;
- _haloLayer = nil;
- _dotBorderLayer = nil;
- _dotLayer = nil;
-
- [self updateFrameWithSize:MGLUserLocationAnnotationPuckSize];
- }
-
- // background dot (white with black shadow)
- //
- if ( ! _puckDot)
- {
- _puckDot = [self circleLayerWithSize:MGLUserLocationAnnotationPuckSize];
- _puckDot.backgroundColor = [[UIColor whiteColor] CGColor];
- _puckDot.shadowColor = [[UIColor blackColor] CGColor];
- _puckDot.shadowOpacity = 0.25;
- _puckDot.shadowPath = [[UIBezierPath bezierPathWithOvalInRect:_puckDot.bounds] CGPath];
-
- if (self.mapView.camera.pitch)
- {
- [self updateFaux3DEffect];
- }
- else
- {
- _puckDot.shadowOffset = CGSizeMake(0, 1);
- _puckDot.shadowRadius = 0.75;
- }
-
- [self.layer addSublayer:_puckDot];
- }
-
- // arrow
- //
- if ( ! _puckArrow)
- {
- _puckArrow = [CAShapeLayer layer];
- _puckArrow.path = [[self puckArrow] CGPath];
- _puckArrow.fillColor = [self.mapView.tintColor CGColor];
- _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)
- {
- _puckArrow.affineTransform = CGAffineTransformRotate(CGAffineTransformIdentity, -MGLRadiansFromDegrees(self.mapView.direction - self.userLocation.location.course));
- }
-
- if ( ! _puckModeActivated)
- {
- _puckModeActivated = YES;
-
- [self updateFaux3DEffect];
- }
-}
-
-- (UIBezierPath *)puckArrow
-{
- CGFloat max = MGLUserLocationAnnotationArrowSize;
-
- UIBezierPath *bezierPath = UIBezierPath.bezierPath;
- [bezierPath moveToPoint: CGPointMake(max * 0.5, 0)];
- [bezierPath addLineToPoint: CGPointMake(max * 0.1, max)];
- [bezierPath addLineToPoint: CGPointMake(max * 0.5, max * 0.65)];
- [bezierPath addLineToPoint: CGPointMake(max * 0.9, max)];
- [bezierPath addLineToPoint: CGPointMake(max * 0.5, 0)];
- [bezierPath closePath];
-
- return bezierPath;
-}
-
-- (void)drawDot
-{
- if (_puckModeActivated)
- {
- self.layer.sublayers = nil;
-
- _puckDot = nil;
- _puckArrow = nil;
-
- [self updateFrameWithSize:MGLUserLocationAnnotationDotSize];
- }
-
- // heading indicator (tinted, beam or arrow)
- //
- BOOL headingTrackingModeEnabled = self.mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading;
- BOOL showHeadingIndicator = self.mapView.showsUserHeadingIndicator || headingTrackingModeEnabled;
-
- if (showHeadingIndicator)
- {
- _headingIndicatorLayer.hidden = NO;
- CLLocationDirection headingAccuracy = self.userLocation.heading.headingAccuracy;
-
- if (([_headingIndicatorLayer isMemberOfClass:[MGLUserLocationHeadingBeamLayer class]] && ! headingTrackingModeEnabled) ||
- ([_headingIndicatorLayer isMemberOfClass:[MGLUserLocationHeadingArrowLayer class]] && headingTrackingModeEnabled))
- {
- [_headingIndicatorLayer removeFromSuperlayer];
- _headingIndicatorLayer = nil;
- _oldHeadingAccuracy = -1;
- }
-
- if ( ! _headingIndicatorLayer && headingAccuracy)
- {
- if (headingTrackingModeEnabled)
- {
- _headingIndicatorLayer = [[MGLUserLocationHeadingBeamLayer alloc] initWithUserLocationAnnotationView:self];
- [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer];
- }
- else
- {
- _headingIndicatorLayer = [[MGLUserLocationHeadingArrowLayer alloc] initWithUserLocationAnnotationView:self];
- [self.layer addSublayer:_headingIndicatorLayer];
- _headingIndicatorLayer.zPosition = 1;
- }
- }
-
- if (_oldHeadingAccuracy != headingAccuracy)
- {
- [_headingIndicatorLayer updateHeadingAccuracy:headingAccuracy];
- _oldHeadingAccuracy = headingAccuracy;
- }
-
- if (self.userLocation.heading.trueHeading >= 0)
- {
- CGFloat rotation = -MGLRadiansFromDegrees(self.mapView.direction - self.userLocation.heading.trueHeading);
-
- // Don't rotate if the change is imperceptible.
- if (fabs(rotation) > MGLUserLocationHeadingUpdateThreshold)
- {
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
-
- _headingIndicatorLayer.affineTransform = CGAffineTransformRotate(CGAffineTransformIdentity, rotation);
-
- [CATransaction commit];
- }
- }
- }
- else
- {
- [_headingIndicatorLayer removeFromSuperlayer];
- _headingIndicatorLayer = nil;
- }
-
- // update accuracy ring (if zoom or horizontal accuracy have changed)
- //
- if (_accuracyRingLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.userLocation.location.horizontalAccuracy))
- {
- CGFloat accuracyRingSize = [self calculateAccuracyRingSize];
-
- // only show the accuracy ring if it won't be obscured by the location dot
- if (accuracyRingSize > MGLUserLocationAnnotationDotSize + 15)
- {
- _accuracyRingLayer.hidden = NO;
-
- // disable implicit animation of the accuracy ring, unless triggered by a change in accuracy
- BOOL shouldDisableActions = _oldHorizontalAccuracy == self.userLocation.location.horizontalAccuracy;
-
- [CATransaction begin];
- [CATransaction setDisableActions:shouldDisableActions];
-
- _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize);
- _accuracyRingLayer.cornerRadius = accuracyRingSize / 2.0;
-
- // match the halo to the accuracy ring
- _haloLayer.bounds = _accuracyRingLayer.bounds;
- _haloLayer.cornerRadius = _accuracyRingLayer.cornerRadius;
- _haloLayer.shouldRasterize = NO;
-
- [CATransaction commit];
- }
- else
- {
- _accuracyRingLayer.hidden = YES;
-
- _haloLayer.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize);
- _haloLayer.cornerRadius = MGLUserLocationAnnotationHaloSize / 2.0;
- _haloLayer.shouldRasterize = YES;
- _haloLayer.rasterizationScale = [UIScreen mainScreen].scale;
- }
-
- // store accuracy and zoom so we're not redrawing unchanged location updates
- _oldHorizontalAccuracy = self.userLocation.location.horizontalAccuracy;
- _oldZoom = self.mapView.zoomLevel;
- }
-
- // accuracy ring (circular, tinted, mostly-transparent)
- //
- if ( ! _accuracyRingLayer && self.userLocation.location.horizontalAccuracy)
- {
- CGFloat accuracyRingSize = [self calculateAccuracyRingSize];
- _accuracyRingLayer = [self circleLayerWithSize:accuracyRingSize];
- _accuracyRingLayer.backgroundColor = [self.mapView.tintColor CGColor];
- _accuracyRingLayer.opacity = 0.1;
- _accuracyRingLayer.shouldRasterize = NO;
- _accuracyRingLayer.allowsGroupOpacity = NO;
-
- [self.layer addSublayer:_accuracyRingLayer];
- }
-
- // expanding sonar-like pulse (circular, tinted, fades out)
- //
- if ( ! _haloLayer)
- {
- _haloLayer = [self circleLayerWithSize:MGLUserLocationAnnotationHaloSize];
- _haloLayer.backgroundColor = [self.mapView.tintColor CGColor];
- _haloLayer.allowsGroupOpacity = NO;
- _haloLayer.zPosition = -0.1f;
-
- // set defaults for the animations
- CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0];
-
- // scale out radially with initial acceleration
- CAKeyframeAnimation *boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"];
- boundsAnimation.values = @[@0, @0.35, @1];
- boundsAnimation.keyTimes = @[@0, @0.2, @1];
-
- // go transparent as scaled out, start semi-opaque
- CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
- opacityAnimation.values = @[@0.4, @0.4, @0];
- opacityAnimation.keyTimes = @[@0, @0.2, @1];
-
- animationGroup.animations = @[boundsAnimation, opacityAnimation];
-
- [_haloLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"];
-
- [self.layer addSublayer:_haloLayer];
- }
-
- // background dot (white with black shadow)
- //
- if ( ! _dotBorderLayer)
- {
- _dotBorderLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize];
- _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor];
- _dotBorderLayer.shadowColor = [[UIColor blackColor] CGColor];
- _dotBorderLayer.shadowOpacity = 0.25;
- _dotBorderLayer.shadowPath = [[UIBezierPath bezierPathWithOvalInRect:_dotBorderLayer.bounds] CGPath];
-
- if (self.mapView.camera.pitch)
- {
- [self updateFaux3DEffect];
- }
- else
- {
- _dotBorderLayer.shadowOffset = CGSizeMake(0, 0);
- _dotBorderLayer.shadowRadius = 3;
- }
-
- [self.layer addSublayer:_dotBorderLayer];
- }
-
- // inner dot (pulsing, tinted)
- //
- if ( ! _dotLayer)
- {
- _dotLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize * 0.75];
- _dotLayer.backgroundColor = [self.mapView.tintColor CGColor];
-
- // set defaults for the animations
- CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5];
- animationGroup.autoreverses = YES;
- animationGroup.fillMode = kCAFillModeBoth;
-
- // scale the dot up and down
- CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"];
- pulseAnimation.fromValue = @0.8;
- pulseAnimation.toValue = @1;
-
- // fade opacity in and out, subtly
- CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
- opacityAnimation.fromValue = @0.8;
- opacityAnimation.toValue = @1;
-
- animationGroup.animations = @[pulseAnimation, opacityAnimation];
-
- [_dotLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"];
-
- [self.layer addSublayer:_dotLayer];
- }
-
- if (_puckModeActivated)
- {
- _puckModeActivated = NO;
-
- [self updateFaux3DEffect];
- }
-}
-
-- (CALayer *)circleLayerWithSize:(CGFloat)layerSize
-{
- layerSize = round(layerSize);
-
- CALayer *circleLayer = [CALayer layer];
- circleLayer.bounds = CGRectMake(0, 0, layerSize, layerSize);
- circleLayer.position = CGPointMake(CGRectGetMidX(super.bounds), CGRectGetMidY(super.bounds));
- circleLayer.cornerRadius = layerSize / 2.0;
- circleLayer.shouldRasterize = YES;
- circleLayer.rasterizationScale = [UIScreen mainScreen].scale;
- circleLayer.drawsAsynchronously = YES;
-
- return circleLayer;
-}
-
-- (CAAnimationGroup *)loopingAnimationGroupWithDuration:(CGFloat)animationDuration
-{
- CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
- animationGroup.duration = animationDuration;
- animationGroup.repeatCount = INFINITY;
- animationGroup.removedOnCompletion = NO;
- animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
-
- return animationGroup;
-}
-
-- (CGFloat)calculateAccuracyRingSize
-{
- // diameter in screen points
- 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
deleted file mode 100644
index 6c5fad62ce..0000000000
--- a/platform/ios/src/MGLMapAccessibilityElement.h
+++ /dev/null
@@ -1,54 +0,0 @@
-#import <UIKit/UIKit.h>
-
-#import "MGLFoundation.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@protocol MGLFeature;
-
-/// Unique identifier representing a single annotation in mbgl.
-typedef uint64_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
deleted file mode 100644
index 240eab1545..0000000000
--- a/platform/ios/src/MGLMapAccessibilityElement.mm
+++ /dev/null
@@ -1,195 +0,0 @@
-#import "MGLMapAccessibilityElement.h"
-#import "MGLDistanceFormatter.h"
-#import "MGLCompassDirectionFormatter.h"
-#import "MGLFeature.h"
-
-#import "MGLGeometry_Private.h"
-#import "MGLVectorTileSource_Private.h"
-
-#import "NSBundle+MGLAdditions.h"
-#import "NSOrthography+MGLAdditions.h"
-#import "NSString+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 = [MGLVectorTileSource 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.
- // Attempt to transform to the script of the preferred language, keeping
- // the original string if no transform exists or if transformation fails.
- NSString *dominantScript = [NSOrthography mgl_dominantScriptForMapboxStreetsLanguage:languageCode];
- name = [name mgl_stringByTransliteratingIntoScript:dominantScript];
-
- 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.
- NSString *languageCode = [MGLVectorTileSource preferredMapboxStreetsLanguage];
- NSString *categoryAttribute = [NSString stringWithFormat:@"category_%@", languageCode];
- if (attributes[categoryAttribute]) {
- [facts addObject:attributes[categoryAttribute]];
- } else 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"]) {
- [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+IBAdditions.h b/platform/ios/src/MGLMapView+IBAdditions.h
deleted file mode 100644
index 64016e8319..0000000000
--- a/platform/ios/src/MGLMapView+IBAdditions.h
+++ /dev/null
@@ -1,51 +0,0 @@
-#import <Foundation/Foundation.h>
-
-@class MGLMapView;
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface MGLMapView (IBAdditions)
-
-// Core properties that can be manipulated in the Attributes inspector in
-// Interface Builder. These redeclarations merely add the IBInspectable keyword.
-// They appear here to ensure that they appear above the convenience properties;
-// inspectables declared in MGLMapView.h are always sorted before those in
-// MGLMapView+IBAdditions.h, due to ASCII sort order.
-
-#if TARGET_INTERFACE_BUILDER
-
-// HACK: We want this property to look like a URL bar in the Attributes
-// inspector, but just calling it styleURL would violate Cocoa naming
-// conventions and conflict with the existing NSURL property. Fortunately, IB
-// strips out the two underscores for display.
-@property (nonatomic, nullable) IBInspectable NSString *styleURL__;
-
-#endif // TARGET_INTERFACE_BUILDER
-
-// Convenience properties related to the initial viewport. These properties
-// are not meant to be used outside of Interface Builder. latitude and longitude
-// are backed by properties of type CLLocationDegrees, but these declarations
-// must use the type double because Interface Builder is unaware that
-// CLLocationDegrees is a typedef for double.
-
-@property (nonatomic) IBInspectable double latitude;
-@property (nonatomic) IBInspectable double longitude;
-@property (nonatomic) IBInspectable double zoomLevel;
-@property (nonatomic) IBInspectable double minimumZoomLevel;
-@property (nonatomic) IBInspectable double maximumZoomLevel;
-
-// Renamed properties. Interface Builder derives the display name of each
-// inspectable from the runtime name, but runtime names don’t always make sense
-// in UI.
-
-@property (nonatomic) IBInspectable BOOL allowsZooming;
-@property (nonatomic) IBInspectable BOOL allowsScrolling;
-@property (nonatomic) IBInspectable BOOL allowsRotating;
-@property (nonatomic) IBInspectable BOOL allowsTilting;
-@property (nonatomic) IBInspectable BOOL showsUserLocation;
-@property (nonatomic) IBInspectable BOOL showsHeading;
-@property (nonatomic) IBInspectable BOOL showsScale;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLMapView+Impl.h b/platform/ios/src/MGLMapView+Impl.h
deleted file mode 100644
index 232215bd1b..0000000000
--- a/platform/ios/src/MGLMapView+Impl.h
+++ /dev/null
@@ -1,77 +0,0 @@
-#import <mbgl/gfx/renderer_backend.hpp>
-#import <mbgl/map/map_observer.hpp>
-#import <mbgl/util/image.hpp>
-
-#import <UIKit/UIView.h>
-#import <UIKit/UIImage.h>
-#import <QuartzCore/CALayer.h>
-
-@class MGLMapView;
-
-class MGLMapViewImpl : public mbgl::MapObserver {
-public:
- static std::unique_ptr<MGLMapViewImpl> Create(MGLMapView*);
-
- MGLMapViewImpl(MGLMapView*);
- virtual ~MGLMapViewImpl() = default;
-
- virtual mbgl::gfx::RendererBackend& getRendererBackend() = 0;
-
- // Returns a handle to the OpenGL context object if this view is rendered with OpenGL.
- virtual EAGLContext* getEAGLContext() {
- return nullptr;
- }
-
- // Gets called when the opaqueness of the view changes.
- virtual void setOpaque(bool) = 0;
-
- // Triggers an immediate render of the underlying view.
- virtual void display() = 0;
-
- // We update the transaction mode when the user adds annotation views that need to be layered on
- // top of the view.
- virtual void setPresentsWithTransaction(bool) = 0;
-
- // Called when initially creating the rendering view, and when resuming rendering after returning
- // from the background.
- virtual void createView() = 0;
-
- // We expose the underlying UIView because we need it in order to add annotation views above it.
- virtual UIView* getView() = 0;
-
- // Called when the application goes to the background and we've replaced the interactively
- // rendered map view with a static image.
- virtual void deleteView() = 0;
-
- // called before the application goes to the background. The resulting image is used as a place
- // holder instead of the regular view. This allows us to drop the framebuffers associated with
- // the rendering context and reduce memory while in the background.
- virtual UIImage* snapshot() = 0;
-
- // Called when UIView's layout has changed.
- virtual void layoutChanged() {};
-
- // Called by the view delegate when it's time to render.
- void render();
-
- // mbgl::MapObserver implementation
- void onCameraWillChange(mbgl::MapObserver::CameraChangeMode) override;
- void onCameraIsChanging() override;
- void onCameraDidChange(mbgl::MapObserver::CameraChangeMode) override;
- void onWillStartLoadingMap() override;
- void onDidFinishLoadingMap() override;
- void onDidFailLoadingMap(mbgl::MapLoadError mapError, const std::string& what) override;
- void onWillStartRenderingFrame() override;
- void onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus) override;
- void onWillStartRenderingMap() override;
- void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode) override;
- void onDidFinishLoadingStyle() override;
- void onSourceChanged(mbgl::style::Source& source) override;
- void onDidBecomeIdle() override;
- void onStyleImageMissing(const std::string& imageIdentifier) override;
- bool onCanRemoveUnusedStyleImage(const std::string& imageIdentifier) override;
-
-protected:
- /// Cocoa map view that this adapter bridges to.
- __weak MGLMapView *mapView = nullptr;
-};
diff --git a/platform/ios/src/MGLMapView+Impl.mm b/platform/ios/src/MGLMapView+Impl.mm
deleted file mode 100644
index 0b9ab75699..0000000000
--- a/platform/ios/src/MGLMapView+Impl.mm
+++ /dev/null
@@ -1,114 +0,0 @@
-#import "MGLMapView+Impl.h"
-#import "MGLMapView+OpenGL.h"
-#import "MGLStyle_Private.h"
-#import "NSBundle+MGLAdditions.h"
-
-#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
-#import "MMEEventsManager.h"
-#endif
-
-std::unique_ptr<MGLMapViewImpl> MGLMapViewImpl::Create(MGLMapView* nativeView) {
- return std::make_unique<MGLMapViewOpenGLImpl>(nativeView);
-}
-
-MGLMapViewImpl::MGLMapViewImpl(MGLMapView* nativeView_) : mapView(nativeView_) {
-}
-
-void MGLMapViewImpl::render() {
- [mapView renderSync];
-}
-
-void MGLMapViewImpl::onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode) {
- bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated;
- [mapView cameraWillChangeAnimated:animated];
-}
-
-void MGLMapViewImpl::onCameraIsChanging() {
- [mapView cameraIsChanging];
-}
-
-void MGLMapViewImpl::onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode) {
- bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated;
- [mapView cameraDidChangeAnimated:animated];
-}
-
-void MGLMapViewImpl::onWillStartLoadingMap() {
- [mapView mapViewWillStartLoadingMap];
-}
-
-void MGLMapViewImpl::onDidFinishLoadingMap() {
- [mapView mapViewDidFinishLoadingMap];
-}
-
-void MGLMapViewImpl::onDidFailLoadingMap(mbgl::MapLoadError mapError, const std::string& what) {
- NSString *description;
- MGLErrorCode code;
- switch (mapError) {
- case mbgl::MapLoadError::StyleParseError:
- code = MGLErrorCodeParseStyleFailed;
- description = NSLocalizedStringWithDefaultValue(@"PARSE_STYLE_FAILED_DESC", nil, nil, @"The map failed to load because the style is corrupted.", @"User-friendly error description");
- break;
- case mbgl::MapLoadError::StyleLoadError:
- code = MGLErrorCodeLoadStyleFailed;
- description = NSLocalizedStringWithDefaultValue(@"LOAD_STYLE_FAILED_DESC", nil, nil, @"The map failed to load because the style can't be loaded.", @"User-friendly error description");
- break;
- case mbgl::MapLoadError::NotFoundError:
- code = MGLErrorCodeNotFound;
- description = NSLocalizedStringWithDefaultValue(@"STYLE_NOT_FOUND_DESC", nil, nil, @"The map failed to load because the style can’t be found or is incompatible.", @"User-friendly error description");
- break;
- default:
- code = MGLErrorCodeUnknown;
- description = NSLocalizedStringWithDefaultValue(@"LOAD_MAP_FAILED_DESC", nil, nil, @"The map failed to load because an unknown error occurred.", @"User-friendly error description");
- }
- NSDictionary *userInfo = @{
- NSLocalizedDescriptionKey: description,
- NSLocalizedFailureReasonErrorKey: @(what.c_str()),
- };
- NSError *error = [NSError errorWithDomain:MGLErrorDomain code:code userInfo:userInfo];
-#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
- [[MMEEventsManager sharedManager] reportError:error];
-#endif
- [mapView mapViewDidFailLoadingMapWithError:error];
-}
-
-void MGLMapViewImpl::onWillStartRenderingFrame() {
- [mapView mapViewWillStartRenderingFrame];
-}
-
-void MGLMapViewImpl::onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) {
- bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full;
- [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered];
-}
-
-void MGLMapViewImpl::onWillStartRenderingMap() {
- [mapView mapViewWillStartRenderingMap];
-}
-
-void MGLMapViewImpl::onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) {
- bool fullyRendered = mode == mbgl::MapObserver::RenderMode::Full;
- [mapView mapViewDidFinishRenderingMapFullyRendered:fullyRendered];
-}
-
-void MGLMapViewImpl::onDidFinishLoadingStyle() {
- [mapView mapViewDidFinishLoadingStyle];
-}
-
-void MGLMapViewImpl::onSourceChanged(mbgl::style::Source& source) {
- NSString *identifier = @(source.getID().c_str());
- MGLSource * nativeSource = [mapView.style sourceWithIdentifier:identifier];
- [mapView sourceDidChange:nativeSource];
-}
-
-void MGLMapViewImpl::onDidBecomeIdle() {
- [mapView mapViewDidBecomeIdle];
-}
-
-void MGLMapViewImpl::onStyleImageMissing(const std::string& imageIdentifier) {
- NSString *imageName = [NSString stringWithUTF8String:imageIdentifier.c_str()];
- [mapView didFailToLoadImage:imageName];
-}
-
-bool MGLMapViewImpl::onCanRemoveUnusedStyleImage(const std::string &imageIdentifier) {
- NSString *imageName = [NSString stringWithUTF8String:imageIdentifier.c_str()];
- return [mapView shouldRemoveStyleImage:imageName];
-}
diff --git a/platform/ios/src/MGLMapView+OpenGL.h b/platform/ios/src/MGLMapView+OpenGL.h
deleted file mode 100644
index b1c13724cb..0000000000
--- a/platform/ios/src/MGLMapView+OpenGL.h
+++ /dev/null
@@ -1,60 +0,0 @@
-#import "MGLMapView+Impl.h"
-#import "MGLMapView_Private.h"
-
-#include <mbgl/gfx/renderable.hpp>
-#include <mbgl/gl/renderer_backend.hpp>
-
-@class MGLMapViewImplDelegate;
-
-/// Adapter responsible for bridging calls from mbgl to MGLMapView and Cocoa.
-class MGLMapViewOpenGLImpl final : public MGLMapViewImpl,
- public mbgl::gl::RendererBackend,
- public mbgl::gfx::Renderable {
-public:
- MGLMapViewOpenGLImpl(MGLMapView*);
- ~MGLMapViewOpenGLImpl() override;
-
-public:
- void restoreFramebufferBinding();
-
-#ifdef MGL_RECREATE_GL_IN_AN_EMERGENCY
-private:
- void emergencyRecreateGL();
-#endif
-
- // Implementation of mbgl::gfx::RendererBackend
-public:
- mbgl::gfx::Renderable& getDefaultRenderable() override {
- return *this;
- }
-
-private:
- void activate() override;
- void deactivate() override;
- // End implementation of mbgl::gfx::RendererBackend
-
- // Implementation of mbgl::gl::RendererBackend
-public:
- void updateAssumedState() override;
-
-private:
- mbgl::gl::ProcAddress getExtensionFunctionPointer(const char* name) override;
- // End implementation of mbgl::gl::Rendererbackend
-
- // Implementation of MGLMapViewImpl
-public:
- mbgl::gfx::RendererBackend& getRendererBackend() override {
- return *this;
- }
-
- EAGLContext* getEAGLContext() override;
- void setOpaque(bool) override;
- void display() override;
- void setPresentsWithTransaction(bool) override;
- void createView() override;
- UIView* getView() override;
- void deleteView() override;
- UIImage* snapshot() override;
- void layoutChanged() override;
- // End implementation of MGLMapViewImpl
-};
diff --git a/platform/ios/src/MGLMapView+OpenGL.mm b/platform/ios/src/MGLMapView+OpenGL.mm
deleted file mode 100644
index ad30b608e5..0000000000
--- a/platform/ios/src/MGLMapView+OpenGL.mm
+++ /dev/null
@@ -1,277 +0,0 @@
-#import "MGLFoundation_Private.h"
-#import "MGLLoggingConfiguration_Private.h"
-#import "MGLMapView+OpenGL.h"
-
-#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
-#import "MMEConstants.h"
-#import "MGLMapboxEvents.h"
-#endif
-
-#include <mbgl/gl/renderable_resource.hpp>
-
-#import <GLKit/GLKit.h>
-#import <OpenGLES/EAGL.h>
-#import <QuartzCore/CAEAGLLayer.h>
-
-@interface MGLMapViewImplDelegate : NSObject <GLKViewDelegate>
-@end
-
-@implementation MGLMapViewImplDelegate {
- MGLMapViewOpenGLImpl* _impl;
-}
-
-- (instancetype)initWithImpl:(MGLMapViewOpenGLImpl*)impl {
- if (self = [super init]) {
- _impl = impl;
- }
- return self;
-}
-
-- (void)glkView:(nonnull GLKView*)view drawInRect:(CGRect)rect {
- _impl->render();
-}
-
-@end
-
-namespace {
-CGFloat contentScaleFactor() {
- return [UIScreen instancesRespondToSelector:@selector(nativeScale)]
- ? [[UIScreen mainScreen] nativeScale]
- : [[UIScreen mainScreen] scale];
-}
-} // namespace
-
-class MGLMapViewOpenGLRenderableResource final : public mbgl::gl::RenderableResource {
-public:
- MGLMapViewOpenGLRenderableResource(MGLMapViewOpenGLImpl& backend_)
- : backend(backend_),
- delegate([[MGLMapViewImplDelegate alloc] initWithImpl:&backend]),
- atLeastiOS_12_2_0([NSProcessInfo.processInfo
- isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ 12, 2, 0 }]) {
- }
-
- void bind() override {
- backend.restoreFramebufferBinding();
- }
-
- mbgl::Size framebufferSize() {
- assert(glView);
- return { static_cast<uint32_t>(glView.drawableWidth),
- static_cast<uint32_t>(glView.drawableHeight) };
- }
-
-private:
- MGLMapViewOpenGLImpl& backend;
-
-public:
- MGLMapViewImplDelegate* delegate = nil;
- GLKView *glView = nil;
- EAGLContext *context = nil;
- const bool atLeastiOS_12_2_0;
-
- // We count how often the context was activated/deactivated so that we can truly deactivate it
- // after the activation count drops to 0.
- NSUInteger activationCount = 0;
-};
-
-MGLMapViewOpenGLImpl::MGLMapViewOpenGLImpl(MGLMapView* nativeView_)
- : MGLMapViewImpl(nativeView_),
- mbgl::gl::RendererBackend(mbgl::gfx::ContextMode::Unique),
- mbgl::gfx::Renderable({ 0, 0 }, std::make_unique<MGLMapViewOpenGLRenderableResource>(*this)) {
-}
-
-MGLMapViewOpenGLImpl::~MGLMapViewOpenGLImpl() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- if (resource.context && [[EAGLContext currentContext] isEqual:resource.context]) {
- [EAGLContext setCurrentContext:nil];
- }
-}
-
-void MGLMapViewOpenGLImpl::setOpaque(const bool opaque) {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- resource.glView.opaque = opaque;
- resource.glView.layer.opaque = opaque;
-}
-
-void MGLMapViewOpenGLImpl::setPresentsWithTransaction(const bool value) {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- CAEAGLLayer* eaglLayer = MGL_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer);
- eaglLayer.presentsWithTransaction = value;
-}
-
-void MGLMapViewOpenGLImpl::display() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
-
- // See https://github.com/mapbox/mapbox-gl-native/issues/14232
- // glClear can be blocked for 1 second. This code is an "escape hatch",
- // an attempt to detect this situation and rebuild the GL views.
- if (mapView.enablePresentsWithTransaction && resource.atLeastiOS_12_2_0) {
- CFTimeInterval before = CACurrentMediaTime();
- [resource.glView display];
- CFTimeInterval after = CACurrentMediaTime();
-
- if (after - before >= 1.0) {
-#ifdef MGL_RECREATE_GL_IN_AN_EMERGENCY
- dispatch_async(dispatch_get_main_queue(), ^{
- emergencyRecreateGL();
- });
-#else
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSError *error = [NSError errorWithDomain:MGLErrorDomain
- code:MGLErrorCodeRenderingError
- userInfo:@{ NSLocalizedFailureReasonErrorKey :
- @"https://github.com/mapbox/mapbox-gl-native/issues/14232" }];
- [[MMEEventsManager sharedManager] reportError:error];
- });
-#endif
- }
- } else {
- [resource.glView display];
- }
-}
-
-void MGLMapViewOpenGLImpl::createView() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- if (resource.glView) {
- return;
- }
-
- if (!resource.context) {
- resource.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
- assert(resource.context);
- }
-
- resource.glView = [[GLKView alloc] initWithFrame:mapView.bounds context:resource.context];
- resource.glView.delegate = resource.delegate;
- resource.glView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- resource.glView.contentScaleFactor = contentScaleFactor();
- resource.glView.contentMode = UIViewContentModeCenter;
- resource.glView.drawableStencilFormat = GLKViewDrawableStencilFormat8;
- resource.glView.drawableDepthFormat = GLKViewDrawableDepthFormat16;
- resource.glView.opaque = mapView.opaque;
- resource.glView.layer.opaque = mapView.opaque;
- resource.glView.enableSetNeedsDisplay = NO;
- CAEAGLLayer* eaglLayer = MGL_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer);
- eaglLayer.presentsWithTransaction = NO;
-
- [mapView insertSubview:resource.glView atIndex:0];
-}
-
-UIView* MGLMapViewOpenGLImpl::getView() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- return resource.glView;
-}
-
-void MGLMapViewOpenGLImpl::deleteView() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- [resource.glView deleteDrawable];
-}
-
-#ifdef MGL_RECREATE_GL_IN_AN_EMERGENCY
-// See https://github.com/mapbox/mapbox-gl-native/issues/14232
-void MGLMapViewOpenGLImpl::emergencyRecreateGL() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- MGLLogError(@"Rendering took too long - creating GL views");
-
- CAEAGLLayer* eaglLayer = MGL_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer);
- eaglLayer.presentsWithTransaction = NO;
-
- [mapView pauseRendering:nil];
-
- // Just performing a pauseRendering:/resumeRendering: pair isn't sufficient - in this case
- // we can still get errors when calling bindDrawable. Here we completely
- // recreate the GLKView
-
- [mapView.userLocationAnnotationView removeFromSuperview];
- [resource.glView removeFromSuperview];
-
- // Recreate the view
- resource.glView = nil;
- createView();
-
- if (mapView.annotationContainerView) {
- [resource.glView insertSubview:mapView.annotationContainerView atIndex:0];
- }
-
- [mapView updateUserLocationAnnotationView];
-
- // Do not bind...yet
-
- if (mapView.window) {
- [mapView resumeRendering:nil];
- eaglLayer = MGL_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer);
- eaglLayer.presentsWithTransaction = mapView.enablePresentsWithTransaction;
- } else {
- MGLLogDebug(@"No window - skipping resumeRendering");
- }
-}
-#endif
-
-mbgl::gl::ProcAddress MGLMapViewOpenGLImpl::getExtensionFunctionPointer(const char* name) {
- static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles"));
- if (!framework) {
- throw std::runtime_error("Failed to load OpenGL framework.");
- }
-
- return reinterpret_cast<mbgl::gl::ProcAddress>(CFBundleGetFunctionPointerForName(
- framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
-}
-
-void MGLMapViewOpenGLImpl::activate() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- if (resource.activationCount++) {
- return;
- }
-
- [EAGLContext setCurrentContext:resource.context];
-}
-
-void MGLMapViewOpenGLImpl::deactivate() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- if (--resource.activationCount) {
- return;
- }
-
- [EAGLContext setCurrentContext:nil];
-}
-
-/// This function is called before we start rendering, when iOS invokes our rendering method.
-/// iOS already sets the correct framebuffer and viewport for us, so we need to update the
-/// context state with the anticipated values.
-void MGLMapViewOpenGLImpl::updateAssumedState() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- assumeFramebufferBinding(ImplicitFramebufferBinding);
- assumeViewport(0, 0, resource.framebufferSize());
-}
-
-void MGLMapViewOpenGLImpl::restoreFramebufferBinding() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- if (!implicitFramebufferBound()) {
- // Something modified our state, and we need to bind the original drawable again.
- // Doing this also sets the viewport to the full framebuffer.
- // Note that in reality, iOS does not use the Framebuffer 0 (it's typically 1), and we
- // only use this is a placeholder value.
- [resource.glView bindDrawable];
- updateAssumedState();
- } else {
- // Our framebuffer is still bound, but the viewport might have changed.
- setViewport(0, 0, resource.framebufferSize());
- }
-}
-
-UIImage* MGLMapViewOpenGLImpl::snapshot() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- return resource.glView.snapshot;
-}
-
-void MGLMapViewOpenGLImpl::layoutChanged() {
- const auto scaleFactor = contentScaleFactor();
- size = { static_cast<uint32_t>(mapView.bounds.size.width * scaleFactor),
- static_cast<uint32_t>(mapView.bounds.size.height * scaleFactor) };
-}
-
-EAGLContext* MGLMapViewOpenGLImpl::getEAGLContext() {
- auto& resource = getResource<MGLMapViewOpenGLRenderableResource>();
- return resource.context;
-}
diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h
deleted file mode 100644
index 8f27adee9e..0000000000
--- a/platform/ios/src/MGLMapView.h
+++ /dev/null
@@ -1,1968 +0,0 @@
-#import <UIKit/UIKit.h>
-
-#import "MGLCompassButton.h"
-#import "MGLFoundation.h"
-#import "MGLGeometry.h"
-#import "MGLMapCamera.h"
-#import "MGLTypes.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@class MGLAnnotationView;
-@class MGLAnnotationImage;
-@class MGLUserLocation;
-@class MGLPolyline;
-@class MGLPolygon;
-@class MGLShape;
-@class MGLStyle;
-
-@protocol MGLMapViewDelegate;
-@protocol MGLAnnotation;
-@protocol MGLOverlay;
-@protocol MGLCalloutView;
-@protocol MGLFeature;
-@protocol MGLLocationManager;
-
-/** Options for `MGLMapView.decelerationRate`. */
-typedef CGFloat MGLMapViewDecelerationRate NS_TYPED_EXTENSIBLE_ENUM;
-
-/** The default deceleration rate for a map view. */
-FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewDecelerationRate MGLMapViewDecelerationRateNormal;
-
-/** A fast deceleration rate for a map view. */
-FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewDecelerationRate MGLMapViewDecelerationRateFast;
-
-/** Disables deceleration in a map view. */
-FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewDecelerationRate MGLMapViewDecelerationRateImmediate;
-
-/**
- The vertical alignment of an annotation within a map view. Used with
- `MGLMapView.userLocationVerticalAlignment`.
- */
-typedef NS_ENUM(NSUInteger, MGLAnnotationVerticalAlignment) {
- /** Aligns the annotation vertically in the center of the map view. */
- MGLAnnotationVerticalAlignmentCenter = 0,
- /** Aligns the annotation vertically at the top of the map view. */
- MGLAnnotationVerticalAlignmentTop,
- /** Aligns the annotation vertically at the bottom of the map view. */
- MGLAnnotationVerticalAlignmentBottom,
-};
-
-/**
- The position of scale bar, compass, logo and attribution in a map view. Used with
- `MGLMapView.scaleBarPosition`,
- `MGLMapView.compassViewPosition`,
- `MGLMapView.logoViewPosition`,
- `MGLMapView.attributionButtonPosition`.
- */
-typedef NS_ENUM(NSUInteger, MGLOrnamentPosition) {
- /** Place the ornament in the top left of the map view. */
- MGLOrnamentPositionTopLeft = 0,
- /** Place the ornament in the top right of the map view. */
- MGLOrnamentPositionTopRight,
- /** Place the ornament in the bottom left of the map view. */
- MGLOrnamentPositionBottomLeft,
- /** Place the ornament in the bottom right of the map view. */
- MGLOrnamentPositionBottomRight,
-};
-
-/**
- The mode used to track the user location on the map. Used with
- `MGLMapView.userTrackingMode`.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/user-tracking-mode/">
- Switch between user tracking modes</a> example to learn how to toggle modes and
- how each mode behaves.
- */
-typedef NS_ENUM(NSUInteger, MGLUserTrackingMode) {
- /** The map does not follow the user location. */
- MGLUserTrackingModeNone = 0,
- /** The map follows the user location. This tracking mode falls back
- to `MGLUserTrackingModeNone` if the user pans the map view. */
- MGLUserTrackingModeFollow,
- /**
- The map follows the user location and rotates when the heading changes.
- The default user location annotation displays a fan-shaped indicator with
- the current heading. The heading indicator represents the direction the
- device is facing, which is sized according to the reported accuracy.
-
- This tracking mode is disabled if the user pans the map view, but
- remains enabled if the user zooms in. If the user rotates the map
- view, this tracking mode will fall back to `MGLUserTrackingModeFollow`.
- */
- MGLUserTrackingModeFollowWithHeading,
- /**
- The map follows the user location and rotates when the course changes.
- Course represents the direction in which the device is traveling.
- The default user location annotation shows a puck-shaped indicator
- that rotates as the course changes.
-
- This tracking mode is disabled if the user pans the map view, but
- remains enabled if the user zooms in. If the user rotates the map view,
- this tracking mode will fall back to `MGLUserTrackingModeFollow`.
- */
- MGLUserTrackingModeFollowWithCourse,
-};
-
-/** Options for `MGLMapView.preferredFramesPerSecond`. */
-typedef NSInteger MGLMapViewPreferredFramesPerSecond NS_TYPED_EXTENSIBLE_ENUM;
-
-/**
- The default frame rate. This can be either 30 FPS or 60 FPS, depending on
- device capabilities.
- */
-FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondDefault;
-
-/** A conservative frame rate; typically 30 FPS. */
-FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondLowPower;
-
-/** The maximum supported frame rate; typically 60 FPS. */
-FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondMaximum;
-
-FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const MGLMissingLocationServicesUsageDescriptionException;
-FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const MGLUserLocationAnnotationTypeException;
-
-/**
- An interactive, customizable map view with an interface similar to the one
- provided by Apple’s MapKit.
-
- Using `MGLMapView`, you can embed the map inside a view, allow users to
- manipulate it with standard gestures, animate the map between different
- viewpoints, and present information in the form of annotations and overlays.
-
- The map view loads scalable vector tiles that conform to the
- <a href="https://github.com/mapbox/vector-tile-spec">Mapbox Vector Tile Specification</a>.
- It styles them with a style that conforms to the
- <a href="https://www.mapbox.com/mapbox-gl-style-spec/">Mapbox Style Specification</a>.
- Such styles can be designed in
- <a href="https://www.mapbox.com/studio/">Mapbox Studio</a> and hosted on
- mapbox.com.
-
- A collection of Mapbox-hosted styles is available through the `MGLStyle`
- class. These basic styles use
- <a href="https://www.mapbox.com/developers/vector-tiles/mapbox-streets">Mapbox Streets</a>
- or <a href="https://www.mapbox.com/satellite/">Mapbox Satellite</a> data
- sources, but you can specify a custom style that makes use of your own data.
-
- Mapbox-hosted vector tiles and styles require an API access token, which you
- can obtain from the
- <a href="https://www.mapbox.com/studio/account/tokens/">Mapbox account page</a>.
- Access tokens associate requests to Mapbox’s vector tile and style APIs with
- your Mapbox account. They also deter other developers from using your styles
- without your permission.
-
- Because `MGLMapView` loads asynchronously, several delegate methods are available
- for receiving map-related updates. These methods can be used to ensure that certain operations
- have completed before taking any additional actions. Information on these methods is located
- in the `MGLMapViewDelegate` protocol documentation.
-
- Adding your own gesture recognizer to `MGLMapView` will block the corresponding
- gesture recognizer built into `MGLMapView`. To avoid conflicts, define which
- gesture takes precedence. For example, you can create your own
- `UITapGestureRecognizer` that will be invoked only if the default `MGLMapView`
- tap gesture fails:
-
- ```swift
- let mapTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(myCustomFunction))
- for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
- mapTapGestureRecognizer.require(toFail: recognizer)
- }
- mapView.addGestureRecognizer(mapTapGestureRecognizer)
- ```
-
- @note You are responsible for getting permission to use the map data and for
- ensuring that your use adheres to the relevant terms of use.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/simple-map-view/">
- Simple map view</a> example to learn how to initialize a basic `MGLMapView`.
- */
-MGL_EXPORT
-@interface MGLMapView : UIView
-
-#pragma mark Creating Instances
-
-/**
- Initializes and returns a newly allocated map view with the specified frame
- and the default style.
-
- @param frame The frame for the view, measured in points.
- @return An initialized map view.
- */
-- (instancetype)initWithFrame:(CGRect)frame;
-
-/**
- Initializes and returns a newly allocated map view with the specified frame
- and style URL.
-
- @param frame The frame for the view, measured in points.
- @param styleURL URL of the map style to display. The URL may be a full HTTP
- or HTTPS URL, a Mapbox style URL
- (`mapbox://styles/{user}/{style}`), or a path to a local file relative
- to the application’s resource path. Specify `nil` for the default style.
- @return An initialized map view.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/custom-style/">
- Apply a style designed in Mapbox Studio</a> example to learn how to
- initialize an `MGLMapView` with a custom style. See the
- <a href="https://docs.mapbox.com/ios/maps/examples/raster-styles/"> Apply a
- style designed in Mapbox Studio Classic</a> example to learn how to intialize
- an `MGLMapView` with a Studio Classic style _or_ a custom style JSON. See the
- <a href="https://docs.mapbox.com/ios/maps/examples/source-custom-vector/"> Use
- third-party vector tiles</a> example to learn how to initialize an
- `MGLMapView` with a third-party tile source.
- */
-- (instancetype)initWithFrame:(CGRect)frame styleURL:(nullable NSURL *)styleURL;
-
-#pragma mark Accessing the Delegate
-
-/**
- The receiver’s delegate.
-
- A map view sends messages to its delegate to notify it of changes to its
- contents or the viewpoint. The delegate also provides information about
- annotations displayed on the map, such as the styles to apply to individual
- annotations.
- */
-@property(nonatomic, weak, nullable) IBOutlet id<MGLMapViewDelegate> delegate;
-
-#pragma mark Configuring the Map’s Appearance
-
-/**
- The style currently displayed in the receiver.
-
- Unlike the `styleURL` property, this property is set to an object that allows
- you to manipulate every aspect of the style locally.
-
- If the style is loading, this property is set to `nil` until the style finishes
- loading. If the style has failed to load, this property is set to `nil`.
- Because the style loads asynchronously, you should manipulate it in the
- `-[MGLMapViewDelegate mapView:didFinishLoadingStyle:]` or
- `-[MGLMapViewDelegate mapViewDidFinishLoadingMap:]` method. It is not possible
- to manipulate the style before it has finished loading.
-
- @note The default styles provided by Mapbox contain sources and layers with
- identifiers that will change over time. Applications that use APIs that
- manipulate a style’s sources and layers must first set the style URL to an
- explicitly versioned style using a convenience method like
- `+[MGLStyle outdoorsStyleURLWithVersion:]`, `MGLMapView`’s “Style URL”
- inspectable in Interface Builder, or a manually constructed `NSURL`.
- */
-@property (nonatomic, readonly, nullable) MGLStyle *style;
-
-/**
- URL of the style currently displayed in the receiver.
-
- The URL may be a full HTTP or HTTPS URL, a Mapbox
- style URL (`mapbox://styles/{user}/{style}`), or a path to a local file
- relative to the application’s resource path.
-
- If you set this property to `nil`, the receiver will use the default style
- and this property will automatically be set to that style’s URL.
-
- If you want to modify the current style without replacing it outright, or if
- you want to introspect individual style attributes, use the `style` property.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/switch-styles/">
- Switch between map styles</a> example to learn how to change the style of
- a map at runtime.
- */
-@property (nonatomic, null_resettable) NSURL *styleURL;
-
-/**
- Reloads the style.
-
- You do not normally need to call this method. The map view automatically
- responds to changes in network connectivity by reloading the style. You may
- need to call this method if you change the access token after a style has
- loaded but before loading a style associated with a different Mapbox account.
-
- This method does not bust the cache. Even if the style has recently changed on
- the server, calling this method does not necessarily ensure that the map view
- reflects those changes.
- */
-- (IBAction)reloadStyle:(nullable id)sender;
-
-/**
- A boolean value that indicates if whether the map view should automatically
- adjust its content insets.
-
- When this property is set to `YES` the map automatically updates its
- `contentInset` property to account for any area not covered by navigation bars,
- tab bars, toolbars, and other ancestors that obscure the map view.
-
- */
-@property (assign) BOOL automaticallyAdjustsContentInset;
-
-/**
- A Boolean value indicating whether the map may display scale information.
-
- The scale bar may not be shown at all zoom levels. The scale bar becomes visible
- when the maximum distance visible on the map view is less than 400 miles (800
- kilometers). The zoom level where this occurs depends on the latitude at the map
- view’s center coordinate, as well as the device screen width. At latitudes
- farther from the equator, the scale bar becomes visible at lower zoom levels.
-
- The unit of measurement is determined by the user's device locale.
-
- The view controlled by this property is available at `scaleBar`. The default value
- of this property is `NO`.
- */
-@property (nonatomic, assign) BOOL showsScale;
-
-/**
- A control indicating the scale of the map. The scale bar is positioned in the
- upper-left corner. Enable the scale bar via `showsScale`.
- */
-@property (nonatomic, readonly) UIView *scaleBar;
-
-/**
- The position of the scale bar. The default value is `MGLOrnamentPositionTopLeft`.
- */
-@property (nonatomic, assign) MGLOrnamentPosition scaleBarPosition;
-
-/**
- A `CGPoint` indicating the position offset of the scale bar.
- */
-@property (nonatomic, assign) CGPoint scaleBarMargins;
-
-/**
- A control indicating the map’s direction and allowing the user to manipulate
- the direction, positioned in the upper-right corner.
- */
-@property (nonatomic, readonly) MGLCompassButton *compassView;
-
-/**
- The position of the compass view. The default value is `MGLOrnamentPositionTopRight`.
- */
-@property (nonatomic, assign) MGLOrnamentPosition compassViewPosition;
-
-/**
- A `CGPoint` indicating the position offset of the compass.
- */
-@property (nonatomic, assign) CGPoint compassViewMargins;
-
-/**
- The Mapbox logo, positioned in the lower-left corner.
-
- @note The Mapbox terms of service, which governs the use of Mapbox-hosted
- vector tiles and styles,
- <a href="https://docs.mapbox.com/help/how-mapbox-works/attribution/">requires</a> most Mapbox
- customers to display the Mapbox logo. If this applies to you, do not
- hide this view or change its contents.
- */
-@property (nonatomic, readonly) UIImageView *logoView;
-
-/**
- The position of the logo view. The default value is `MGLOrnamentPositionBottomLeft`.
- */
-@property (nonatomic, assign) MGLOrnamentPosition logoViewPosition;
-
-/**
- A `CGPoint` indicating the position offset of the logo.
- */
-@property (nonatomic, assign) CGPoint logoViewMargins;
-
-
-/**
- A view showing legally required copyright notices and telemetry settings,
- positioned at the bottom-right of the map view.
-
- If you choose to reimplement this view, assign the `-showAttribution:` method
- as the action for your view to present the default notices and settings.
-
- @note The Mapbox terms of service, which governs the use of Mapbox-hosted
- vector tiles and styles,
- <a href="https://www.mapbox.com/tos/#[FamaFama]">requires</a> these
- copyright notices to accompany any map that features Mapbox-designed styles,
- OpenStreetMap data, or other Mapbox data such as satellite or terrain
- data. If that applies to this map view, do not hide this view or remove
- any notices from it.
-
- @note You are additionally
- <a href="https://www.mapbox.com/tos/#[FamaFama]">required</a>
- to provide users with the option to disable anonymous usage and location
- sharing (telemetry). If this view is hidden, you must implement this
- setting elsewhere in your app or via `Settings.bundle`. See our
- <a href="https://docs.mapbox.com/help/how-mapbox-works/attribution/#mapbox-maps-sdk-for-ios">website</a> for
- implementation help.
- */
-@property (nonatomic, readonly) UIButton *attributionButton;
-
-/**
- The position of the attribution button. The default value is `MGLOrnamentPositionBottomRight`.
- */
-@property (nonatomic, assign) MGLOrnamentPosition attributionButtonPosition;
-
-/**
- A `CGPoint` indicating the position offset of the attribution.
- */
-@property (nonatomic, assign) CGPoint attributionButtonMargins;
-
-/**
- Show the attribution and telemetry action sheet.
-
- This action is performed when the user taps on the attribution button provided
- by default via the `attributionButton` property. If you implement a custom
- attribution button, you should add this action to the button.
- */
-- (IBAction)showAttribution:(id)sender;
-
-/**
- The preferred frame rate at which the map view is rendered.
-
- The default value for this property is
- `MGLMapViewPreferredFramesPerSecondDefault`, which will adaptively set the
- preferred frame rate based on the capability of the user’s device to maintain
- a smooth experience.
-
- In addition to the provided `MGLMapViewPreferredFramesPerSecond` options, this
- property can be set to arbitrary integer values.
-
- @see `CADisplayLink.preferredFramesPerSecond`
- */
-@property (nonatomic, assign) MGLMapViewPreferredFramesPerSecond preferredFramesPerSecond;
-
-/**
- A Boolean value indicating whether the map should prefetch tiles.
-
- When this property is set to `YES`, the map view prefetches tiles designed for
- a low zoom level and displays them until receiving more detailed tiles for the
- current zoom level. The prefetched tiles typically contain simplified versions
- of each shape, improving the map view’s perceived performance.
-
- The default value of this property is `YES`.
- */
-@property (nonatomic, assign) BOOL prefetchesTiles;
-
-#pragma mark Displaying the User’s Location
-
-/**
- The object that this map view uses to start and stop the delivery of
- location-related updates.
-
- To receive the current user location, implement the
- `-[MGLMapViewDelegate mapView:didUpdateUserLocation:]` and
- `-[MGLMapViewDelegate mapView:didFailToLocateUserWithError:]` methods.
-
- If setting this property to `nil` or if no custom manager is provided this
- property is set to the default location manager.
-
- `MGLMapView` uses a default location manager. If you want to substitute your
- own location manager, you should do so by setting this property before setting
- `showsUserLocation` to `YES`. To restore the default location manager,
- set this property to `nil`.
- */
-@property (nonatomic, null_resettable) id<MGLLocationManager> locationManager;
-
-/**
- A Boolean value indicating whether the map may display the user location.
-
- Setting this property to `YES` causes the map view to use the Core Location
- framework to find the current location. As long as this property is `YES`, the
- map view continues to track the user’s location and update it periodically.
-
- This property does not indicate whether the user’s position is actually visible
- on the map, only whether the map view is allowed to display it. To determine
- whether the user’s position is visible, use the `userLocationVisible` property.
- The default value of this property is `NO`.
-
- Your app must specify a value for `NSLocationWhenInUseUsageDescription` or
- `NSLocationAlwaysUsageDescription` in its `Info.plist` to satisfy the
- requirements of the underlying Core Location framework when enabling this
- property.
-
- If you implement a custom location manager, set the `locationManager` before
- calling `showsUserLocation`.
- */
-@property (nonatomic, assign) BOOL showsUserLocation;
-
-/**
- A Boolean value indicating whether the device’s current location is visible in
- the map view.
-
- Use `showsUserLocation` to control the visibility of the on-screen user
- location annotation.
- */
-@property (nonatomic, assign, readonly, getter=isUserLocationVisible) BOOL userLocationVisible;
-
-/**
- Returns the annotation object indicating the user’s current location.
- */
-@property (nonatomic, readonly, nullable) MGLUserLocation *userLocation;
-
-/**
- The mode used to track the user location. The default value is
- `MGLUserTrackingModeNone`.
-
- Changing the value of this property updates the map view with an animated
- transition. If you don’t want to animate the change, use the
- `-setUserTrackingMode:animated:` method instead.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/user-location-annotation/">
- Customize the user location annotation</a> to learn how to customize the
- default user location annotation shown by `MGLUserTrackingMode`.
- */
-@property (nonatomic, assign) MGLUserTrackingMode userTrackingMode;
-
-/**
- Deprecated. Sets the mode used to track the user location, with an optional transition.
-
- To specify a completion handler to execute after the animation finishes, use
- the `-setUserTrackingMode:animated:completionHandler:` method.
-
- @param mode The mode used to track the user location.
- @param animated If `YES`, there is an animated transition from the current
- viewport to a viewport that results from the change to `mode`. If `NO`, the
- map view instantaneously changes to the new viewport. This parameter only
- affects the initial transition; subsequent changes to the user location or
- heading are always animated.
- */
-- (void)setUserTrackingMode:(MGLUserTrackingMode)mode animated:(BOOL)animated __attribute__((deprecated("Use `-setUserTrackingMode:animated:completionHandler:` instead.")));
-
-/**
- Sets the mode used to track the user location, with an optional transition and
- completion handler.
-
- @param mode The mode used to track the user location.
- @param animated If `YES`, there is an animated transition from the current
- viewport to a viewport that results from the change to `mode`. If `NO`, the
- map view instantaneously changes to the new viewport. This parameter only
- affects the initial transition; subsequent changes to the user location or
- heading are always animated.
- @param completion The block executed after the animation finishes.
- */
-- (void)setUserTrackingMode:(MGLUserTrackingMode)mode animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion;
-
-/**
- The vertical alignment of the user location annotation within the receiver. The
- default value is `MGLAnnotationVerticalAlignmentCenter`.
-
- Changing the value of this property updates the map view with an animated
- transition. If you don’t want to animate the change, use the
- `-setUserLocationVerticalAlignment:animated:` method instead.
- */
-@property (nonatomic, assign) MGLAnnotationVerticalAlignment userLocationVerticalAlignment __attribute__((deprecated("Use `-[MGLMapViewDelegate mapViewUserLocationAnchorPoint:]` instead.")));
-
-/**
- Sets the vertical alignment of the user location annotation within the
- receiver, with an optional transition.
-
- @param alignment The vertical alignment of the user location annotation.
- @param animated If `YES`, the user location annotation animates to its new
- position within the map view. If `NO`, the user location annotation
- instantaneously moves to its new position.
- */
-- (void)setUserLocationVerticalAlignment:(MGLAnnotationVerticalAlignment)alignment animated:(BOOL)animated __attribute__((deprecated("Use `-[MGLMapViewDelegate mapViewUserLocationAnchorPoint:]` instead.")));
-
-/**
- Updates the position of the user location annotation view by retreiving the user's last
- known location.
- */
-- (void)updateUserLocationAnnotationView;
-
-/**
- Updates the position of the user location annotation view by retreiving the user's last
- known location with a specified duration.
- @param duration The duration to animate the change in seconds.
-*/
-- (void)updateUserLocationAnnotationViewAnimatedWithDuration:(NSTimeInterval)duration;
-
-/**
- A Boolean value indicating whether the user location annotation may display a
- permanent heading indicator.
-
- Setting this property to `YES` causes the default user location annotation to
- appear and always show an arrow-shaped heading indicator, if heading is
- available. This property does not rotate the map; for that, see
- `MGLUserTrackingModeFollowWithHeading`.
-
- This property has no effect when `userTrackingMode` is
- `MGLUserTrackingModeFollowWithHeading` or
- `MGLUserTrackingModeFollowWithCourse`.
-
- The default value of this property is `NO`.
- */
-@property (nonatomic, assign) BOOL showsUserHeadingIndicator;
-
-/**
- Whether the map view should display a heading calibration alert when necessary.
- The default value is `YES`.
- */
-@property (nonatomic, assign) BOOL displayHeadingCalibration;
-
-/**
- The geographic coordinate that is the subject of observation as the user
- location is being tracked.
-
- By default, this property is set to an invalid coordinate, indicating that
- there is no target. In course tracking mode, the target forms one of two foci
- in the viewport, the other being the user location annotation. Typically, this
- property is set to a destination or waypoint in a real-time navigation scene.
- As the user annotation moves toward the target, the map automatically zooms in
- to fit both foci optimally within the viewport.
-
- This property has no effect if the `userTrackingMode` property is set to a
- value other than `MGLUserTrackingModeFollowWithCourse`.
-
- Changing the value of this property updates the map view with an animated
- transition. If you don’t want to animate the change, use the
- `-setTargetCoordinate:animated:` method instead.
- */
-@property (nonatomic, assign) CLLocationCoordinate2D targetCoordinate;
-
-/**
- Deprecated. Sets the geographic coordinate that is the subject of observation as
- the user location is being tracked, with an optional transition animation.
-
- By default, the target coordinate is set to an invalid coordinate, indicating
- that there is no target. In course tracking mode, the target forms one of two
- foci in the viewport, the other being the user location annotation. Typically,
- the target is set to a destination or waypoint in a real-time navigation scene.
- As the user annotation moves toward the target, the map automatically zooms in
- to fit both foci optimally within the viewport.
-
- This method has no effect if the `userTrackingMode` property is set to a value
- other than `MGLUserTrackingModeFollowWithCourse`.
-
- To specify a completion handler to execute after the animation finishes, use
- the `-setTargetCoordinate:animated:completionHandler:` method.
-
- @param targetCoordinate The target coordinate to fit within the viewport.
- @param animated If `YES`, the map animates to fit the target within the map
- view. If `NO`, the map fits the target instantaneously.
- */
-- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate animated:(BOOL)animated __attribute__((deprecated("Use `-setTargetCoordinate:animated:completionHandler:` instead.")));
-
-/**
- Sets the geographic coordinate that is the subject of observation as the user
- location is being tracked, with an optional transition animation and completion
- handler.
-
- By default, the target coordinate is set to an invalid coordinate, indicating
- that there is no target. In course tracking mode, the target forms one of two
- foci in the viewport, the other being the user location annotation. Typically,
- the target is set to a destination or waypoint in a real-time navigation scene.
- As the user annotation moves toward the target, the map automatically zooms in
- to fit both foci optimally within the viewport.
-
- This method has no effect if the `userTrackingMode` property is set to a value
- other than `MGLUserTrackingModeFollowWithCourse`.
-
- @param targetCoordinate The target coordinate to fit within the viewport.
- @param animated If `YES`, the map animates to fit the target within the map
- view. If `NO`, the map fits the target instantaneously.
- @param completion The block executed after the animation finishes.
- */
-- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion;
-
-#pragma mark Configuring How the User Interacts with the Map
-
-/**
- A Boolean value that determines whether the user may zoom the map in and
- out, changing the zoom level.
-
- When this property is set to `YES`, the default, the user may zoom the map
- in and out by pinching two fingers or by double tapping, holding, and moving
- the finger up and down.
-
- This property controls only user interactions with the map. If you set the
- value of this property to `NO`, you may still change the map zoom
- programmatically.
- */
-@property(nonatomic, getter=isZoomEnabled) BOOL zoomEnabled;
-
-/**
- A Boolean value that determines whether the user may scroll around the map,
- changing the center coordinate.
-
- When this property is set to `YES`, the default, the user may scroll the map
- by dragging or swiping with one finger.
-
- This property controls only user interactions with the map. If you set the
- value of this property to `NO`, you may still change the map location
- programmatically.
- */
-@property(nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
-
-/**
- A Boolean value that determines whether the user may rotate the map,
- changing the direction.
-
- When this property is set to `YES`, the default, the user may rotate the map
- by moving two fingers in a circular motion.
-
- This property controls only user interactions with the map. If you set the
- value of this property to `NO`, you may still rotate the map
- programmatically.
- */
-@property(nonatomic, getter=isRotateEnabled) BOOL rotateEnabled;
-
-/**
- A Boolean value that determines whether the user may change the pitch (tilt) of
- the map.
-
- When this property is set to `YES`, the default, the user may tilt the map by
- vertically dragging two fingers.
-
- This property controls only user interactions with the map. If you set the
- value of this property to `NO`, you may still change the pitch of the map
- programmatically.
-
- The default value of this property is `YES`.
- */
-@property(nonatomic, getter=isPitchEnabled) BOOL pitchEnabled;
-
-/**
- A Boolean value that determines whether the user will receive haptic feedback
- for certain interactions with the map.
-
- When this property is set to `YES`, the default, a `UIImpactFeedbackStyleLight`
- haptic feedback event be played when the user rotates the map to due north
- (0°).
-
- This feature requires a device that supports haptic feedback, running iOS 10 or
- newer.
- */
-@property(nonatomic, getter=isHapticFeedbackEnabled) BOOL hapticFeedbackEnabled;
-
-/**
- A floating-point value that determines the rate of deceleration after the user
- lifts their finger.
-
- Your application can use the `MGLMapViewDecelerationRateNormal` and
- `MGLMapViewDecelerationRateFast` constants as reference points for reasonable
- deceleration rates. `MGLMapViewDecelerationRateImmediate` can be used to
- disable deceleration entirely.
- */
-@property(nonatomic) CGFloat decelerationRate;
-
-#pragma mark Manipulating the Viewpoint
-
-/**
- The geographic coordinate at the center of the map view.
-
- Changing the value of this property centers the map on the new coordinate
- without changing the current zoom level.
-
- Changing the value of this property updates the map view immediately. If you
- want to animate the change, use the `-setCenterCoordinate:animated:` method
- instead.
- */
-@property (nonatomic) CLLocationCoordinate2D centerCoordinate;
-
-/**
- Changes the center coordinate of the map and optionally animates the change.
-
- Changing the center coordinate centers the map on the new coordinate without
- changing the current zoom level. For animated changes, wait until the map view has
- finished loading before calling this method.
-
- @param coordinate The new center coordinate for the map.
- @param animated Specify `YES` if you want the map view to scroll to the new
- location or `NO` if you want the map to display the new location
- immediately.
-
- @note The behavior of this method is undefined if called in response to
- `UIApplicationWillTerminateNotification`.
- */
-- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated;
-
-/**
- Changes the center coordinate and zoom level of the map and optionally animates
- the change. For animated changes, wait until the map view has
- finished loading before calling this method.
-
- @param centerCoordinate The new center coordinate for the map.
- @param zoomLevel The new zoom level for the map.
- @param animated Specify `YES` if you want the map view to animate scrolling and
- zooming to the new location or `NO` if you want the map to display the new
- location immediately.
-
- @note The behavior of this method is undefined if called in response to
- `UIApplicationWillTerminateNotification`.
- */
-- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated;
-
-/**
- Changes the center coordinate, zoom level, and direction of the map and
- optionally animates the change. For animated changes, wait until the map view has
- finished loading before calling this method.
-
- @param centerCoordinate The new center coordinate for the map.
- @param zoomLevel The new zoom level for the map.
- @param direction The new direction for the map, measured in degrees relative to
- true north. A negative value leaves the map’s direction unchanged.
- @param animated Specify `YES` if you want the map view to animate scrolling,
- zooming, and rotating to the new location or `NO` if you want the map to
- display the new location immediately.
-
- @note The behavior of this method is undefined if called in response to
- `UIApplicationWillTerminateNotification`.
- */
-- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated;
-
-/**
- Changes the center coordinate, zoom level, and direction of the map, calling a
- completion handler at the end of an optional animation. For animated changes,
- wait until the map view has finished loading before calling this method.
-
- @param centerCoordinate The new center coordinate for the map.
- @param zoomLevel The new zoom level for the map.
- @param direction The new direction for the map, measured in degrees relative to
- true north. A negative value leaves the map’s direction unchanged.
- @param animated Specify `YES` if you want the map view to animate scrolling,
- zooming, and rotating to the new location or `NO` if you want the map to
- display the new location immediately.
- @param completion The block executed after the animation finishes.
-
- @note The behavior of this method is undefined if called in response to
- `UIApplicationWillTerminateNotification`.
- */
-- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion;
-
-/** The zoom level of the receiver.
-
- In addition to affecting the visual size and detail of features on the map,
- the zoom level affects the size of the vector tiles that are loaded. At zoom
- level 0, each tile covers the entire world map; at zoom level 1, it covers ¼
- of the world; at zoom level 2, <sup>1</sup>⁄<sub>16</sub> of the world, and
- so on.
-
- Changing the value of this property updates the map view immediately. If you
- want to animate the change, use the `-setZoomLevel:animated:` method instead.
- */
-@property (nonatomic) double zoomLevel;
-
-/**
- Changes the zoom level of the map and optionally animates the change.
-
- Changing the zoom level scales the map without changing the current center
- coordinate.
-
- @param zoomLevel The new zoom level for the map.
- @param animated Specify `YES` if you want the map view to animate the change
- to the new zoom level or `NO` if you want the map to display the new
- zoom level immediately.
- */
-- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated;
-
-/**
- * The minimum zoom level at which the map can be shown.
- *
- * Depending on the map view’s aspect ratio, the map view may be prevented
- * from reaching the minimum zoom level, in order to keep the map from
- * repeating within the current viewport.
- *
- * If the value of this property is greater than that of the
- * maximumZoomLevel property, the behavior is undefined.
- *
- * The default minimumZoomLevel is 0.
- */
-@property (nonatomic) double minimumZoomLevel;
-
-/**
- * The maximum zoom level the map can be shown at.
- *
- * If the value of this property is smaller than that of the
- * minimumZoomLevel property, the behavior is undefined.
- *
- * The default maximumZoomLevel is 22. The upper bound for this property
- * is 25.5.
- */
-@property (nonatomic) double maximumZoomLevel;
-
-/**
- The heading of the map, measured in degrees clockwise from true north.
-
- The value `0` means that the top edge of the map view corresponds to true
- north. The value `90` means the top of the map is pointing due east. The
- value `180` means the top of the map points due south, and so on.
-
- Changing the value of this property updates the map view immediately. If you
- want to animate the change, use the `-setDirection:animated:` method instead.
- */
-@property (nonatomic) CLLocationDirection direction;
-
-/**
- Changes the heading of the map and optionally animates the change.
-
- @param direction The heading of the map, measured in degrees clockwise from
- true north.
- @param animated Specify `YES` if you want the map view to animate the change
- to the new heading or `NO` if you want the map to display the new
- heading immediately.
-
- Changing the heading rotates the map without changing the current center
- coordinate or zoom level.
- */
-- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated;
-
-/**
- Resets the map rotation to a northern heading — a `direction` of `0` degrees.
- */
-- (IBAction)resetNorth;
-
-/**
- Resets the map to the current style’s default viewport.
-
- If the style doesn’t specify a default viewport, the map resets to a minimum
- zoom level, a center coordinate of (0, 0), and a northern heading.
- */
-- (IBAction)resetPosition;
-
-/**
- The coordinate bounds visible in the receiver’s viewport.
-
- Changing the value of this property updates the receiver immediately. If you
- want to animate the change, call `-setVisibleCoordinateBounds:animated:`
- instead.
-
- If a longitude is less than −180 degrees or greater than 180 degrees, the
- visible bounds straddles the antimeridian or international date line. For
- example, if both Tokyo and San Francisco are visible, the visible bounds might
- extend from (35.68476, −220.24257) to (37.78428, −122.41310).
- */
-@property (nonatomic) MGLCoordinateBounds visibleCoordinateBounds;
-
-/**
- Changes the receiver’s viewport to fit the given coordinate bounds,
- optionally animating the change.
-
- To bring both sides of the antimeridian or international date line into view,
- specify some longitudes less than −180 degrees or greater than 180 degrees. For
- example, to show both Tokyo and San Francisco simultaneously, you could set the
- visible bounds to extend from (35.68476, −220.24257) to (37.78428, −122.41310).
-
- @param bounds The bounds that the viewport will show in its entirety.
- @param animated Specify `YES` to animate the change by smoothly scrolling
- and zooming or `NO` to immediately display the given bounds.
- */
-- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds animated:(BOOL)animated;
-
-/**
- Deprecated. Changes the receiver’s viewport to fit the given coordinate bounds with
- some additional padding on each side.
-
- To bring both sides of the antimeridian or international date line into view,
- specify some longitudes less than −180 degrees or greater than 180 degrees. For
- example, to show both Tokyo and San Francisco simultaneously, you could set the
- visible bounds to extend from (35.68476, −220.24257) to (37.78428, −122.41310).
-
- To specify a completion handler to execute after the animation finishes, use
- the `-setVisibleCoordinateBounds:edgePadding:animated:completionHandler:` method.
-
- @param bounds The bounds that the viewport will show in its entirety.
- @param insets The minimum padding (in screen points) that will be visible
- around the given coordinate bounds.
- @param animated Specify `YES` to animate the change by smoothly scrolling and
- zooming or `NO` to immediately display the given bounds.
- */
-- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated __attribute__((deprecated("Use `-setVisibleCoordinateBounds:edgePadding:animated:completionHandler:` instead.")));
-
-/**
- Changes the receiver’s viewport to fit the given coordinate bounds with some
- additional padding on each side, optionally calling a completion handler.
-
- To bring both sides of the antimeridian or international date line into view,
- specify some longitudes less than −180 degrees or greater than 180 degrees. For
- example, to show both Tokyo and San Francisco simultaneously, you could set the
- visible bounds to extend from (35.68476, −220.24257) to (37.78428, −122.41310).
-
- @param bounds The bounds that the viewport will show in its entirety.
- @param insets The minimum padding (in screen points) that will be visible
- around the given coordinate bounds.
- @param animated Specify `YES` to animate the change by smoothly scrolling and
- zooming or `NO` to immediately display the given bounds.
- @param completion The block executed after the animation finishes.
- */
-- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion;
-
-/**
- Changes the receiver’s viewport to fit all of the given coordinates with some
- additional padding on each side.
-
- To bring both sides of the antimeridian or international date line into view,
- specify some longitudes less than −180 degrees or greater than 180 degrees. For
- example, to show both Tokyo and San Francisco simultaneously, you could set the
- visible coordinates to (35.68476, −220.24257) and (37.78428, −122.41310).
-
- @param coordinates The coordinates that the viewport will show.
- @param count The number of coordinates. This number must not be greater than
- the number of elements in `coordinates`.
- @param insets The minimum padding (in screen points) that will be visible
- around the given coordinate bounds.
- @param animated Specify `YES` to animate the change by smoothly scrolling and
- zooming or `NO` to immediately display the given bounds.
- */
-- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated;
-
-/**
- Changes the receiver’s viewport to fit all of the given coordinates with some
- additional padding on each side, optionally calling a completion handler.
-
- To bring both sides of the antimeridian or international date line into view,
- specify some longitudes less than −180 degrees or greater than 180 degrees. For
- example, to show both Tokyo and San Francisco simultaneously, you could set the
- visible coordinates to (35.68476, −220.24257) and (37.78428, −122.41310).
-
- @param coordinates The coordinates that the viewport will show.
- @param count The number of coordinates. This number must not be greater than
- the number of elements in `coordinates`.
- @param insets The minimum padding (in screen points) that will be visible
- around the given coordinate bounds.
- @param direction The direction to rotate the map to, measured in degrees
- relative to true north. A negative value leaves the map’s direction
- unchanged.
- @param duration The duration to animate the change in seconds.
- @param function The timing function to animate the change.
- @param completion The block executed after the animation finishes.
- */
-- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion;
-
-/**
- Sets the visible region so that the map displays the specified annotations.
-
- Calling this method updates the value in the `visibleCoordinateBounds` property
- and potentially other properties to reflect the new map region. A small amount
- of padding is reserved around the edges of the map view. To specify a different
- amount of padding, use the `-showAnnotations:edgePadding:animated:` method.
-
- @param annotations The annotations that you want to be visible in the map.
- @param animated `YES` if you want the map region change to be animated, or `NO`
- if you want the map to display the new region immediately without animations.
- */
-- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations animated:(BOOL)animated;
-
-/**
- Deprecated. Sets the visible region so that the map displays the specified
- annotations with the specified amount of padding on each side.
-
- Calling this method updates the value in the `visibleCoordinateBounds` property
- and potentially other properties to reflect the new map region.
-
- To specify a completion handler to execute after the animation finishes, use
- the `-showAnnotations:edgePadding:animated:completionHandler:` method.
-
- @param annotations The annotations that you want to be visible in the map.
- @param insets The minimum padding (in screen points) around the edges of the
- map view to keep clear of annotations.
- @param animated `YES` if you want the map region change to be animated, or `NO`
- if you want the map to display the new region immediately without animations.
- */
-- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated __attribute__((deprecated("Use `-showAnnotations:edgePadding:animated:completionHandler:` instead.")));
-
-/**
- Sets the visible region so that the map displays the specified annotations with
- the specified amount of padding on each side and an optional completion
- handler.
-
- Calling this method updates the value in the `visibleCoordinateBounds` property
- and potentially other properties to reflect the new map region.
-
- @param annotations The annotations that you want to be visible in the map.
- @param insets The minimum padding (in screen points) around the edges of the
- map view to keep clear of annotations.
- @param animated `YES` if you want the map region change to be animated, or `NO`
- if you want the map to display the new region immediately without animations.
- @param completion The block executed after the animation finishes.
- */
-- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion;
-
-/**
- A camera representing the current viewpoint of the map.
- */
-@property (nonatomic, copy) MGLMapCamera *camera;
-
-/**
- Moves the viewpoint to a different location with respect to the map with an
- optional transition animation. For animated changes, wait until the map view has
- finished loading before calling this method.
-
- @param camera The new viewpoint.
- @param animated Specify `YES` if you want the map view to animate the change to
- the new viewpoint or `NO` if you want the map to display the new viewpoint
- immediately.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/camera-animation/">
- Camera animation</a> example to learn how to trigger an animation that
- rotates around a central point.
- */
-- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated;
-
-/**
- Moves the viewpoint to a different location with respect to the map with an
- optional transition duration and timing function. For animated changes, wait
- until the map view has finished loading before calling this method.
-
- @param camera The new viewpoint.
- @param duration The amount of time, measured in seconds, that the transition
- animation should take. Specify `0` to jump to the new viewpoint
- instantaneously.
- @param function A timing function used for the animation. Set this parameter to
- `nil` for a transition that matches most system animations. If the duration
- is `0`, this parameter is ignored.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/camera-animation/">
- Camera animation</a> example to learn how to create a timed animation that
- rotates around a central point for a specific duration.
- */
-- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function;
-
-/**
- Moves the viewpoint to a different location with respect to the map with an
- optional transition duration and timing function. For animated changes, wait
- until the map view has finished loading before calling this method.
-
- @param camera The new viewpoint.
- @param duration The amount of time, measured in seconds, that the transition
- animation should take. Specify `0` to jump to the new viewpoint
- instantaneously.
- @param function A timing function used for the animation. Set this parameter to
- `nil` for a transition that matches most system animations. If the duration
- is `0`, this parameter is ignored.
- @param completion The block to execute after the animation finishes.
- */
-- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion;
-
-/**
- Moves the viewpoint to a different location with respect to the map with an
- optional transition duration and timing function, and optionally some additional
- padding on each side. For animated changes, wait until the map view has
- finished loading before calling this method.
-
- @param camera The new viewpoint.
- @param duration The amount of time, measured in seconds, that the transition
- animation should take. Specify `0` to jump to the new viewpoint
- instantaneously.
- @param function A timing function used for the animation. Set this parameter to
- `nil` for a transition that matches most system animations. If the duration
- is `0`, this parameter is ignored.
- @param edgePadding The minimum padding (in screen points) that would be visible
- around the returned camera object if it were set as the receiver’s camera.
- @param completion The block to execute after the animation finishes.
- */
-- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(UIEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion;
-
-/**
- Moves the viewpoint to a different location using a transition animation that
- evokes powered flight and a default duration based on the length of the flight
- path.
-
- The transition animation seamlessly incorporates zooming and panning to help
- the user find his or her bearings even after traversing a great distance.
-
- @param camera The new viewpoint.
- @param completion The block to execute after the animation finishes.
- */
-- (void)flyToCamera:(MGLMapCamera *)camera completionHandler:(nullable void (^)(void))completion;
-
-/**
- Moves the viewpoint to a different location using a transition animation that
- evokes powered flight and an optional transition duration.
-
- The transition animation seamlessly incorporates zooming and panning to help
- the user find his or her bearings even after traversing a great distance.
-
- @param camera The new viewpoint.
- @param duration The amount of time, measured in seconds, that the transition
- animation should take. Specify `0` to jump to the new viewpoint
- instantaneously. Specify a negative value to use the default duration, which
- is based on the length of the flight path.
- @param completion The block to execute after the animation finishes.
- */
-- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration completionHandler:(nullable void (^)(void))completion;
-
-/**
- Moves the viewpoint to a different location using a transition animation that
- evokes powered flight and an optional transition duration and peak altitude.
-
- The transition animation seamlessly incorporates zooming and panning to help
- the user find his or her bearings even after traversing a great distance.
-
- @param camera The new viewpoint.
- @param duration The amount of time, measured in seconds, that the transition
- animation should take. Specify `0` to jump to the new viewpoint
- instantaneously. Specify a negative value to use the default duration, which
- is based on the length of the flight path.
- @param peakAltitude The altitude, measured in meters, at the midpoint of the
- animation. The value of this parameter is ignored if it is negative or if
- the animation transition resulting from a similar call to
- `-setCamera:animated:` would have a midpoint at a higher altitude.
- @param completion The block to execute after the animation finishes.
- */
-- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion;
-
-/**
- Returns the camera that best fits the given coordinate bounds.
-
- @param bounds The coordinate bounds to fit to the receiver’s viewport.
- @return A camera object centered on the same location as the coordinate
- bounds with zoom level as high (close to the ground) as possible while still
- including the entire coordinate bounds. The camera object uses the current
- direction and pitch.
-
- @note The behavior of this method is undefined if called in response to
- `UIApplicationWillTerminateNotification`; you may receive a `nil` return value
- depending on the order of notification delivery.
- */
-- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds;
-
-/**
- Returns the camera that best fits the given coordinate bounds with some
- additional padding on each side.
-
- @param bounds The coordinate bounds to fit to the receiver’s viewport.
- @param insets The minimum padding (in screen points) that would be visible
- around the returned camera object if it were set as the receiver’s camera.
- @return A camera object centered on the same location as the coordinate bounds
- with zoom level as high (close to the ground) as possible while still
- including the entire coordinate bounds. The camera object uses the current
- direction and pitch.
-
- @note The behavior of this method is undefined if called in response to
- `UIApplicationWillTerminateNotification`; you may receive a `nil` return value
- depending on the order of notification delivery.
- */
-- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets;
-
-/**
- Returns the camera that best fits the given coordinate bounds with some
- additional padding on each side, matching an existing camera as much as
- possible.
-
- @param camera The camera that the return camera should adhere to. All values
- on this camera will be manipulated except for pitch and direction.
- @param bounds The coordinate bounds to fit to the receiver’s viewport.
- @param insets The minimum padding (in screen points) that would be visible
- around the returned camera object if it were set as the receiver’s camera.
- @return A camera object centered on the same location as the coordinate bounds
- with zoom level as high (close to the ground) as possible while still
- including the entire coordinate bounds. The initial camera's pitch and
- direction will be honored.
-
- @note The behavior of this method is undefined if called in response to
- `UIApplicationWillTerminateNotification`; you may receive a `nil` return value
- depending on the order of notification delivery.
- */
-- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets;
-
-/**
- Returns the camera that best fits the given shape with some additional padding
- on each side, matching an existing camera as much as possible.
-
- @param camera The camera that the return camera should adhere to. All values
- on this camera will be manipulated except for pitch and direction.
- @param shape The shape to fit to the receiver’s viewport.
- @param insets The minimum padding (in screen points) that would be visible
- around the returned camera object if it were set as the receiver’s camera.
- @return A camera object centered on the shape's center with zoom level as high
- (close to the ground) as possible while still including the entire shape.
- The initial camera's pitch and direction will be honored.
-
- @note The behavior of this method is undefined if called in response to
- `UIApplicationWillTerminateNotification`; you may receive a `nil` return value
- depending on the order of notification delivery.
- */
-- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingShape:(MGLShape *)shape edgePadding:(UIEdgeInsets)insets;
-
-/**
- Returns the camera that best fits the given shape with some additional padding
- on each side while looking in the specified direction.
-
- @param shape The shape to fit to the receiver’s viewport.
- @param direction The direction of the viewport, measured in degrees clockwise
- from true north.
- @param insets The minimum padding (in screen points) that would be visible
- around the returned camera object if it were set as the receiver’s camera.
- @return A camera object centered on the shape's center with zoom level as high
- (close to the ground) as possible while still including the entire shape.
- The camera object uses the current pitch.
-
- @note The behavior of this method is undefined if called in response to
- `UIApplicationWillTerminateNotification`; you may receive a `nil` return value
- depending on the order of notification delivery.
- */
-- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(UIEdgeInsets)insets;
-
-/**
- Returns the point in this view’s coordinate system on which to “anchor” in
- response to a user-initiated gesture.
-
- For example, a pinch-to-zoom gesture would anchor the map at the midpoint of
- the pinch.
-
- If the `userTrackingMode` property is not `MGLUserTrackingModeNone`, the
- user annotation is used as the anchor point.
-
- Subclasses may override this method to provide specialized behavior - for
- example, anchoring on the map’s center point to provide a "locked" zooming
- mode.
-
- @param gesture An anchorable user gesture.
- @return The point on which to anchor in response to the gesture.
- */
-- (CGPoint)anchorPointForGesture:(UIGestureRecognizer *)gesture;
-
-/**
- The distance from the edges of the map view’s frame to the edges of the map
- view’s logical viewport.
-
- When the value of this property is equal to `UIEdgeInsetsZero`, viewport
- properties such as `centerCoordinate` assume a viewport that matches the map
- view’s frame. Otherwise, those properties are inset, excluding part of the
- frame from the viewport. For instance, if the only the top edge is inset, the
- map center is effectively shifted downward.
-
- When the map view’s superview is an instance of `UIViewController` whose
- `automaticallyAdjustsScrollViewInsets` property is `YES`, the value of this
- property may be overridden at any time.
-
- The usage of `automaticallyAdjustsScrollViewInsets` has been deprecated
- use the map view’s property `MGLMapView.automaticallyAdjustsContentInset`instead.
-
- Changing the value of this property updates the map view immediately. If you
- want to animate the change, use the `-setContentInset:animated:completionHandler:`
- method instead.
- */
-@property (nonatomic, assign) UIEdgeInsets contentInset;
-
-/**
- Deprecated. Sets the distance from the edges of the map view’s frame to the edges
- of the map view’s logical viewport with an optional transition animation.
-
- When the value of this property is equal to `UIEdgeInsetsZero`, viewport
- properties such as `centerCoordinate` assume a viewport that matches the map
- view’s frame. Otherwise, those properties are inset, excluding part of the
- frame from the viewport. For instance, if the only the top edge is inset, the
- map center is effectively shifted downward.
-
- When the map view’s superview is an instance of `UIViewController` whose
- `automaticallyAdjustsScrollViewInsets` property is `YES`, the value of this
- property may be overridden at any time.
-
- The usage of `automaticallyAdjustsScrollViewInsets` has been deprecated
- use the map view’s property `MGLMapView.automaticallyAdjustsContentInset`instead.
-
- To specify a completion handler to execute after the animation finishes, use
- the `-setContentInset:animated:completionHandler:` method.
-
- @param contentInset The new values to inset the content by.
- @param animated Specify `YES` if you want the map view to animate the change to
- the content inset or `NO` if you want the map to inset the content
- immediately.
- */
-- (void)setContentInset:(UIEdgeInsets)contentInset animated:(BOOL)animated __attribute__((deprecated("Use `-setContentInset:animated:completionHandler:` instead.")));
-
-/**
- Sets the distance from the edges of the map view’s frame to the edges of the
- map view’s logical viewport with an optional transition animation and
- completion handler.
-
- When the value of this property is equal to `UIEdgeInsetsZero`, viewport
- properties such as `centerCoordinate` assume a viewport that matches the map
- view’s frame. Otherwise, those properties are inset, excluding part of the
- frame from the viewport. For instance, if the only the top edge is inset, the
- map center is effectively shifted downward.
-
- When the map view’s superview is an instance of `UIViewController` whose
- `automaticallyAdjustsScrollViewInsets` property is `YES`, the value of this
- property may be overridden at any time.
-
- The usage of `automaticallyAdjustsScrollViewInsets` has been deprecated
- use the map view’s property `MGLMapView.automaticallyAdjustsContentInset`instead.
-
- @param contentInset The new values to inset the content by.
- @param animated Specify `YES` if you want the map view to animate the change to
- the content inset or `NO` if you want the map to inset the content
- immediately.
- @param completion The block executed after the animation finishes.
- */
-- (void)setContentInset:(UIEdgeInsets)contentInset animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion;
-
-#pragma mark Converting Geographic Coordinates
-
-/**
- Converts a point in the given view’s coordinate system to a geographic
- coordinate.
-
- @param point The point to convert.
- @param view The view in whose coordinate system the point is expressed.
- @return The geographic coordinate at the given point.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/point-conversion/">
- Point conversion</a> example to learn how to convert a `CGPoint` to a map
- coordinate.
- */
-- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view;
-
-/**
- Converts a geographic coordinate to a point in the given view’s coordinate
- system.
-
- @param coordinate The geographic coordinate to convert.
- @param view The view in whose coordinate system the returned point should be
- expressed. If this parameter is `nil`, the returned point is expressed
- in the window’s coordinate system. If `view` is not `nil`, it must
- belong to the same window as the map view.
- @return The point (in the appropriate view or window coordinate system)
- corresponding to the given geographic coordinate.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/point-conversion/">
- Point conversion</a> example to learn how to convert a map coordinate to a
- `CGPoint` object.
- */
-- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable UIView *)view;
-
-/**
- Converts a rectangle in the given view’s coordinate system to a geographic
- bounding box.
-
- If the returned coordinate bounds contains a longitude is less than −180 degrees
- or greater than 180 degrees, the bounding box straddles the antimeridian or
- international date line.
-
- @param rect The rectangle to convert.
- @param view The view in whose coordinate system the rectangle is expressed.
- @return The geographic bounding box coextensive with the given rectangle.
- */
-- (MGLCoordinateBounds)convertRect:(CGRect)rect toCoordinateBoundsFromView:(nullable UIView *)view;
-
-/**
- Converts a geographic bounding box to a rectangle in the given view’s
- coordinate system.
-
- To bring both sides of the antimeridian or international date line into view,
- specify some longitudes less than −180 degrees or greater than 180 degrees. For
- example, to show both Tokyo and San Francisco simultaneously, you could set the
- visible bounds to extend from (35.68476, −220.24257) to (37.78428, −122.41310).
-
- @param bounds The geographic bounding box to convert.
- @param view The view in whose coordinate system the returned rectangle should
- be expressed. If this parameter is `nil`, the returned rectangle is
- expressed in the window’s coordinate system. If `view` is not `nil`, it must
- belong to the same window as the map view.
- */
-- (CGRect)convertCoordinateBounds:(MGLCoordinateBounds)bounds toRectToView:(nullable UIView *)view;
-
-/**
- Returns the distance spanned by one point in the map view’s coordinate system
- at the given latitude and current zoom level.
-
- The distance between points decreases as the latitude approaches the poles.
- This relationship parallels the relationship between longitudinal coordinates
- at different latitudes.
-
- @param latitude The latitude of the geographic coordinate represented by the
- point.
- @return The distance in meters spanned by a single point.
- */
-- (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude;
-
-#pragma mark Annotating the Map
-
-/**
- The complete list of annotations associated with the receiver. (read-only)
-
- The objects in this array must adopt the `MGLAnnotation` protocol. If no
- annotations are associated with the map view, the value of this property is
- `nil`.
- */
-@property (nonatomic, readonly, nullable) NSArray<id <MGLAnnotation>> *annotations;
-
-/**
- Adds an annotation to the map view.
-
- @note `MGLMultiPolyline`, `MGLMultiPolygon`, `MGLShapeCollection`, and
- `MGLPointCollection` objects cannot be added to the map view at this time.
- Any multipoint, multipolyline, multipolygon, shape or point collection
- object that is specified is silently ignored.
-
- @param annotation The annotation object to add to the receiver. This object
- must conform to the `MGLAnnotation` protocol. The map view retains the
- annotation object.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/annotation-models/">
- Annotation models</a> and <a href="https://docs.mapbox.com/ios/maps/examples/line-geojson/">
- Add a line annotation from GeoJSON</a> examples to learn how to add an
- annotation to an `MGLMapView` object.
- */
-- (void)addAnnotation:(id <MGLAnnotation>)annotation;
-
-/**
- Adds an array of annotations to the map view.
-
- @note `MGLMultiPolyline`, `MGLMultiPolygon`, and `MGLShapeCollection` objects
- cannot be added to the map view at this time. Nor can `MGLMultiPoint`
- objects that are not instances of `MGLPolyline` or `MGLPolygon`. Any
- multipoint, multipolyline, multipolygon, or shape collection objects that
- are specified are silently ignored.
-
- @param annotations An array of annotation objects. Each object in the array
- must conform to the `MGLAnnotation` protocol. The map view retains each
- individual annotation object.
- */
-- (void)addAnnotations:(NSArray<id <MGLAnnotation>> *)annotations;
-
-/**
- Removes an annotation from the map view, deselecting it if it is selected.
-
- Removing an annotation object dissociates it from the map view entirely,
- preventing it from being displayed on the map. Thus you would typically call
- this method only when you want to hide or delete a given annotation.
-
- @param annotation The annotation object to remove. This object must conform
- to the `MGLAnnotation` protocol
- */
-- (void)removeAnnotation:(id <MGLAnnotation>)annotation;
-
-/**
- Removes an array of annotations from the map view, deselecting any selected
- annotations in the array.
-
- Removing annotation objects dissociates them from the map view entirely,
- preventing them from being displayed on the map. Thus you would typically
- call this method only when you want to hide or delete the given annotations.
-
- @param annotations The array of annotation objects to remove. Objects in the
- array must conform to the `MGLAnnotation` protocol.
- */
-- (void)removeAnnotations:(NSArray<id <MGLAnnotation>> *)annotations;
-
-/**
- Returns an `MGLAnnotationView` if the given annotation is currently associated
- with a view, otherwise nil.
-
- @param annotation The annotation associated with the view.
- Annotation must conform to the `MGLAnnotation` protocol.
- */
-- (nullable MGLAnnotationView *)viewForAnnotation:(id <MGLAnnotation>)annotation;
-
-/**
- Returns a reusable annotation image object associated with its identifier.
-
- For performance reasons, you should generally reuse `MGLAnnotationImage`
- objects for identical-looking annotations in your map views. Dequeueing
- saves time and memory during performance-critical operations such as
- scrolling.
-
- @param identifier A string identifying the annotation image to be reused.
- This string is the same one you specify when initially returning the
- annotation image object using the `-mapView:imageForAnnotation:` method.
- @return An annotation image object with the given identifier, or `nil` if no
- such object exists in the reuse queue.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/annotation-view-image/">
- Add annotation views and images</a> example learn how to most efficiently
- reuse an `MGLAnnotationImage`.
- */
-- (nullable __kindof MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier;
-
-/**
- Returns a reusable annotation view object associated with its identifier.
-
- For performance reasons, you should generally reuse `MGLAnnotationView`
- objects for identical-looking annotations in your map views. Dequeueing
- saves time and memory during performance-critical operations such as
- scrolling.
-
- @param identifier A string identifying the annotation view to be reused.
- This string is the same one you specify when initially returning the
- annotation view object using the `-mapView:viewForAnnotation:` method.
- @return An annotation view object with the given identifier, or `nil` if no
- such object exists in the reuse queue.
- */
-- (nullable __kindof MGLAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier;
-
-/**
- The complete list of annotations associated with the receiver that are
- currently visible.
-
- The objects in this array must adopt the `MGLAnnotation` protocol. If no
- annotations are associated with the map view or if no annotations associated
- with the map view are currently visible, the value of this property is `nil`.
- */
-@property (nonatomic, readonly, nullable) NSArray<id <MGLAnnotation>> *visibleAnnotations;
-
-/**
- Returns the list of annotations associated with the receiver that intersect with
- the given rectangle.
-
- @param rect A rectangle expressed in the map view’s coordinate system.
- @return An array of objects that adopt the `MGLAnnotation` protocol or `nil` if
- no annotations associated with the map view are currently visible in the
- rectangle.
- */
-- (nullable NSArray<id <MGLAnnotation>> *)visibleAnnotationsInRect:(CGRect)rect;
-
-#pragma mark Managing Annotation Selections
-
-/**
- The currently selected annotations.
-
- Assigning a new array to this property selects only the first annotation in
- the array.
-
- If the annotation is of type `MGLPointAnnotation` and is offscreen, the camera
- will animate to bring the annotation and its callout just on screen. If you
- need finer control, consider using `-selectAnnotation:animated:`.
-
- @note In versions prior to `4.0.0` if the annotation was offscreen it was not
- selected.
- */
-@property (nonatomic, copy) NSArray<id <MGLAnnotation>> *selectedAnnotations;
-
-/**
- Deprecated. Selects an annotation and displays its callout view.
-
- The `animated` parameter determines whether the selection is animated including whether the map is
- panned to bring the annotation into view, specifically:
-
- | `animated` parameter | Effect |
- |------------------|--------|
- | `NO` | The annotation is selected, and the callout is presented. However the map is not panned to bring the annotation or callout into view. The presentation of the callout is NOT animated. |
- | `YES` | The annotation is selected, and the callout is presented. If the annotation is not visible (or is partially visible) *and* is of type `MGLPointAnnotation`, the map is panned so that the annotation and its callout are brought into view. The annotation is *not* centered within the viewport. |
-
- Note that a selection initiated by a single tap gesture is always animated.
-
- To specify a completion handler to execute after the animation finishes, use
- the `-selectAnnotation:animated:completionHandler:` method.
-
- @param annotation The annotation object to select.
- @param animated If `YES`, the annotation and callout view are animated on-screen.
-
- @note In versions prior to `4.0.0` selecting an offscreen annotation did not
- change the camera.
- */
-- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated __attribute__((deprecated("Use `-selectAnnotation:animated:completionHandler:` instead.")));
-
-/**
- Selects an annotation and displays its callout view with an optional completion
- handler.
-
- The `animated` parameter determines whether the selection is animated including whether the map is
- panned to bring the annotation into view, specifically:
-
- | `animated` parameter | Effect |
- |------------------|--------|
- | `NO` | The annotation is selected, and the callout is presented. However the map is not panned to bring the annotation or callout into view. The presentation of the callout is NOT animated. |
- | `YES` | The annotation is selected, and the callout is presented. If the annotation is not visible (or is partially visible) *and* is of type `MGLPointAnnotation`, the map is panned so that the annotation and its callout are brought into view. The annotation is *not* centered within the viewport. |
-
- Note that a selection initiated by a single tap gesture is always animated.
-
- @param annotation The annotation object to select.
- @param animated If `YES`, the annotation and callout view are animated on-screen.
- @param completion The block executed after the animation finishes.
-
- @note In versions prior to `4.0.0` selecting an offscreen annotation did not
- change the camera.
- */
-- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion;
-
-/**
- :nodoc:
- Selects an annotation and displays its callout view with an optional completion
- handler. This method should be considered "alpha" and as such is subject to
- change.
-
- @param annotation The annotation object to select.
- @param moveIntoView If the annotation is not visible (or is partially visible) *and* is of type `MGLPointAnnotation`, the map is panned so that the annotation and its callout are brought into view. The annotation is *not* centered within the viewport.
- @param animateSelection If `YES`, the annotation's selection state and callout view's presentation are animated.
- @param completion The block executed after the animation finishes.
- */
-- (void)selectAnnotation:(id <MGLAnnotation>)annotation moveIntoView:(BOOL)moveIntoView animateSelection:(BOOL)animateSelection completionHandler:(nullable void (^)(void))completion;
-
-/**
- Deselects an annotation and hides its callout view.
-
- @param annotation The annotation object to deselect.
- @param animated If `YES`, the callout view is animated offscreen.
- */
-- (void)deselectAnnotation:(nullable id <MGLAnnotation>)annotation animated:(BOOL)animated;
-
-#pragma mark Overlaying the Map
-
-/**
- The complete list of overlays associated with the receiver. (read-only)
-
- The objects in this array must adopt the `MGLOverlay` protocol. If no
- overlays are associated with the map view, the value of this property is
- empty array.
- */
-@property (nonatomic, readonly, nonnull) NSArray<id <MGLOverlay>> *overlays;
-
-/**
- Adds a single overlay object to the map.
-
- To remove an overlay from a map, use the `-removeOverlay:` method.
-
- @param overlay The overlay object to add. This object must conform to the
- `MGLOverlay` protocol. */
-- (void)addOverlay:(id <MGLOverlay>)overlay;
-
-/**
- Adds an array of overlay objects to the map.
-
- To remove multiple overlays from a map, use the `-removeOverlays:` method.
-
- @param overlays An array of objects, each of which must conform to the
- `MGLOverlay` protocol.
- */
-- (void)addOverlays:(NSArray<id <MGLOverlay>> *)overlays;
-
-/**
- Removes a single overlay object from the map.
-
- If the specified overlay is not currently associated with the map view, this
- method does nothing.
-
- @param overlay The overlay object to remove.
- */
-- (void)removeOverlay:(id <MGLOverlay>)overlay;
-
-/**
- Removes one or more overlay objects from the map.
-
- If a given overlay object is not associated with the map view, it is ignored.
-
- @param overlays An array of objects, each of which conforms to the `MGLOverlay`
- protocol.
- */
-- (void)removeOverlays:(NSArray<id <MGLOverlay>> *)overlays;
-
-#pragma mark Accessing the Underlying Map Data
-
-/**
- Returns an array of rendered map features that intersect with a given point.
-
- This method may return features from any of the map’s style layers. To restrict
- the search to a particular layer or layers, use the
- `-visibleFeaturesAtPoint:inStyleLayersWithIdentifiers:` method. For more
- information about searching for map features, see that method’s documentation.
-
- @param point A point expressed in the map view’s coordinate system.
- @return An array of objects conforming to the `MGLFeature` protocol that
- represent features in the sources used by the current style.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/select-layer/">
- Select a feature within a layer</a> example to learn how to query an
- `MGLMapView` object for visible `MGLFeature` objects.
- */
-- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(CGPoint)point NS_SWIFT_NAME(visibleFeatures(at:));
-
-/**
- Returns an array of rendered map features that intersect with a given point,
- restricted to the given style layers.
-
- This method returns all the intersecting features from the specified layers. To
- filter the returned features, use the
- `-visibleFeaturesAtPoint:inStyleLayersWithIdentifiers:predicate:` method. For
- more information about searching for map features, see that method’s
- documentation.
-
- @param point A point expressed in the map view’s coordinate system.
- @param styleLayerIdentifiers A set of strings that correspond to the names
- of layers defined in the current style. Only the features contained in
- these layers are included in the returned array.
- @return An array of objects conforming to the `MGLFeature` protocol that
- represent features in the sources used by the current style.
- */
-- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(CGPoint)point inStyleLayersWithIdentifiers:(nullable NSSet<NSString *> *)styleLayerIdentifiers NS_SWIFT_NAME(visibleFeatures(at:styleLayerIdentifiers:));
-
-/**
- Returns an array of rendered map features that intersect with a given point,
- restricted to the given style layers and filtered by the given predicate.
-
- Each object in the returned array represents a feature rendered by the
- current style and provides access to attributes specified by the relevant map
- content sources. The returned array includes features loaded by
- `MGLShapeSource` and `MGLVectorTileSource` objects but does not include
- anything from `MGLRasterTileSource` objects, or from video or canvas sources,
- which are unsupported by this SDK.
-
- The returned features are drawn by a style layer in the current style. For
- example, suppose the current style uses the
- <a href="https://www.mapbox.com/vector-tiles/mapbox-streets/">Mapbox Streets source</a>,
- but none of the specified style layers includes features that have the `maki`
- property set to `bus`. If you pass a point corresponding to the location of a
- bus stop into this method, the bus stop feature does not appear in the
- resulting array. On the other hand, if the style does include bus stops, an
- `MGLFeature` object representing that bus stop is returned and its
- `featureAttributes` dictionary has the `maki` key set to `bus` (along with
- other attributes). The dictionary contains only the attributes provided by the
- tile source; it does not include computed attribute values or rules about how
- the feature is rendered by the current style.
-
- The returned array is sorted by z-order, starting with the topmost rendered
- feature and ending with the bottommost rendered feature. A feature that is
- rendered multiple times due to wrapping across the antimeridian at low zoom
- levels is included only once, subject to the caveat that follows.
-
- Features come from tiled vector data or GeoJSON data that is converted to tiles
- internally, so feature geometries are clipped at tile boundaries and features
- may appear duplicated across tiles. For example, suppose the specified point
- lies along a road that spans the screen. The resulting array includes those
- parts of the road that lie within the map tile that contain the specified
- point, even if the road extends into other tiles.
-
- To find out the layer names in a particular style, view the style in
- <a href="https://www.mapbox.com/studio/">Mapbox Studio</a>.
-
- Only visible features are returned. To obtain features regardless of
- visibility, use the
- `-[MGLVectorTileSource featuresInSourceLayersWithIdentifiers:predicate:]` and
- `-[MGLShapeSource featuresMatchingPredicate:]` methods on the relevant sources.
-
- The returned features may also include features corresponding to annotations.
- These features are not object-equal to the `MGLAnnotation` objects that were
- originally added to the map. To query the map for annotations, use
- `visibleAnnotations` or `-[MGLMapView visibleAnnotationsInRect:]`.
-
- @note Layer identifiers are not guaranteed to exist across styles or different
- versions of the same style. Applications that use this API must first set
- the style URL to an explicitly versioned style using a convenience method
- like `+[MGLStyle outdoorsStyleURLWithVersion:]`, `MGLMapView`’s “Style URL”
- inspectable in Interface Builder, or a manually constructed `NSURL`. This
- approach also avoids layer identifer name changes that will occur in the
- default style’s layers over time.
-
- @param point A point expressed in the map view’s coordinate system.
- @param styleLayerIdentifiers A set of strings that correspond to the names of
- layers defined in the current style. Only the features contained in these
- layers are included in the returned array.
- @param predicate A predicate to filter the returned features.
- @return An array of objects conforming to the `MGLFeature` protocol that
- represent features in the sources used by the current style.
- */
-- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(CGPoint)point inStyleLayersWithIdentifiers:(nullable NSSet<NSString *> *)styleLayerIdentifiers predicate:(nullable NSPredicate *)predicate NS_SWIFT_NAME(visibleFeatures(at:styleLayerIdentifiers:predicate:));
-
-/**
- Returns an array of rendered map features that intersect with the given
- rectangle.
-
- This method may return features from any of the map’s style layers. To restrict
- the search to a particular layer or layers, use the
- `-visibleFeaturesAtPoint:inStyleLayersWithIdentifiers:` method. For more
- information about searching for map features, see that method’s documentation.
-
- @param rect A rectangle expressed in the map view’s coordinate system.
- @return An array of objects conforming to the `MGLFeature` protocol that
- represent features in the sources used by the current style.
- */
-- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(CGRect)rect NS_SWIFT_NAME(visibleFeatures(in:));
-
-/**
- Returns an array of rendered map features that intersect with the given
- rectangle, restricted to the given style layers.
-
- This method returns all the intersecting features from the specified layers. To
- filter the returned features, use the
- `-visibleFeaturesAtPoint:inStyleLayersWithIdentifiers:predicate:` method. For
- more information about searching for map features, see that method’s
- documentation.
-
- @param rect A rectangle expressed in the map view’s coordinate system.
- @param styleLayerIdentifiers A set of strings that correspond to the names of
- layers defined in the current style. Only the features contained in these
- layers are included in the returned array.
- @return An array of objects conforming to the `MGLFeature` protocol that
- represent features in the sources used by the current style.
- */
-- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(CGRect)rect inStyleLayersWithIdentifiers:(nullable NSSet<NSString *> *)styleLayerIdentifiers NS_SWIFT_NAME(visibleFeatures(in:styleLayerIdentifiers:));
-
-/**
- Returns an array of rendered map features that intersect with the given
- rectangle, restricted to the given style layers and filtered by the given
- predicate.
-
- Each object in the returned array represents a feature rendered by the
- current style and provides access to attributes specified by the relevant map
- content sources. The returned array includes features loaded by
- `MGLShapeSource` and `MGLVectorTileSource` objects but does not include
- anything from `MGLRasterTileSource` objects, or from video or canvas sources,
- which are unsupported by this SDK.
-
- The returned features are drawn by a style layer in the current style. For
- example, suppose the current style uses the
- <a href="https://www.mapbox.com/vector-tiles/mapbox-streets/">Mapbox Streets source</a>,
- but none of the specified style layers includes features that have the `maki`
- property set to `bus`. If you pass a rectangle containing the location of a bus
- stop into this method, the bus stop feature does not appear in the resulting
- array. On the other hand, if the style does include bus stops, an `MGLFeature`
- object representing that bus stop is returned and its `featureAttributes`
- dictionary has the `maki` key set to `bus` (along with other attributes). The
- dictionary contains only the attributes provided by the tile source; it does
- not include computed attribute values or rules about how the feature is
- rendered by the current style.
-
- The returned array is sorted by z-order, starting with the topmost rendered
- feature and ending with the bottommost rendered feature. A feature that is
- rendered multiple times due to wrapping across the antimeridian at low zoom
- levels is included only once, subject to the caveat that follows.
-
- Features come from tiled vector data or GeoJSON data that is converted to tiles
- internally, so feature geometries are clipped at tile boundaries and features
- may appear duplicated across tiles. For example, suppose the specified
- rectangle intersects with a road that spans the screen. The resulting array
- includes those parts of the road that lie within the map tiles covering the
- specified rectangle, even if the road extends into other tiles. The portion of
- the road within each map tile is included individually.
-
- To find out the layer names in a particular style, view the style in
- <a href="https://www.mapbox.com/studio/">Mapbox Studio</a>.
-
- Only visible features are returned. To obtain features regardless of
- visibility, use the
- `-[MGLVectorTileSource featuresInSourceLayersWithIdentifiers:predicate:]` and
- `-[MGLShapeSource featuresMatchingPredicate:]` methods on the relevant sources.
-
- @note Layer identifiers are not guaranteed to exist across styles or different
- versions of the same style. Applications that use this API must first set the
- style URL to an explicitly versioned style using a convenience method like
- `+[MGLStyle outdoorsStyleURLWithVersion:]`, `MGLMapView`’s “Style URL”
- inspectable in Interface Builder, or a manually constructed `NSURL`. This
- approach also avoids layer identifer name changes that will occur in the
- default style’s layers over time.
-
- @note Layer identifiers are not guaranteed to exist across styles or different
- versions of the same style. Applications that use this API must first set
- the style URL to an explicitly versioned style using a convenience method
- like `+[MGLStyle outdoorsStyleURLWithVersion:]`, `MGLMapView`’s “Style URL”
- inspectable in Interface Builder, or a manually constructed `NSURL`. This
- approach also avoids layer identifer name changes that will occur in the
- default style’s layers over time.
-
- @param rect A rectangle expressed in the map view’s coordinate system.
- @param styleLayerIdentifiers A set of strings that correspond to the names of
- layers defined in the current style. Only the features contained in these
- layers are included in the returned array.
- @param predicate A predicate to filter the returned features.
- @return An array of objects conforming to the `MGLFeature` protocol that
- represent features in the sources used by the current style.
- */
-- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(CGRect)rect inStyleLayersWithIdentifiers:(nullable NSSet<NSString *> *)styleLayerIdentifiers predicate:(nullable NSPredicate *)predicate NS_SWIFT_NAME(visibleFeatures(in:styleLayerIdentifiers:predicate:));
-
-#pragma mark Debugging the Map
-
-/**
- The options that determine which debugging aids are shown on the map.
-
- These options are all disabled by default and should remain disabled in
- released software for performance and aesthetic reasons.
- */
-@property (nonatomic) MGLMapDebugMaskOptions debugMask;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
deleted file mode 100644
index 1f4e2ffa74..0000000000
--- a/platform/ios/src/MGLMapView.mm
+++ /dev/null
@@ -1,7010 +0,0 @@
-#import "MGLMapView_Private.h"
-#import "MGLMapView+Impl.h"
-
-#include <mbgl/map/map.hpp>
-#include <mbgl/map/map_options.hpp>
-#include <mbgl/annotation/annotation.hpp>
-#include <mbgl/gl/custom_layer.hpp>
-#include <mbgl/map/camera.hpp>
-#include <mbgl/map/mode.hpp>
-#include <mbgl/util/platform.hpp>
-#include <mbgl/storage/reachability.h>
-#include <mbgl/storage/resource_options.hpp>
-#include <mbgl/storage/network_status.hpp>
-#include <mbgl/style/style.hpp>
-#include <mbgl/style/image.hpp>
-#include <mbgl/style/transition_options.hpp>
-#include <mbgl/renderer/renderer.hpp>
-#include <mbgl/math/wrap.hpp>
-#include <mbgl/util/exception.hpp>
-#include <mbgl/util/geo.hpp>
-#include <mbgl/util/constants.hpp>
-#include <mbgl/util/image.hpp>
-#include <mbgl/util/projection.hpp>
-#include <mbgl/util/default_styles.hpp>
-#include <mbgl/util/chrono.hpp>
-#include <mbgl/util/run_loop.hpp>
-#include <mbgl/util/string.hpp>
-#include <mbgl/util/projection.hpp>
-
-#import "Mapbox.h"
-#import "MGLShape_Private.h"
-#import "MGLFeature_Private.h"
-#import "MGLGeometry_Private.h"
-#import "MGLMultiPoint_Private.h"
-#import "MGLOfflineStorage_Private.h"
-#import "MGLVectorTileSource_Private.h"
-#import "MGLFoundation_Private.h"
-#import "MGLRendererFrontend.h"
-#import "MGLRendererConfiguration.h"
-
-#import "NSBundle+MGLAdditions.h"
-#import "NSDate+MGLAdditions.h"
-#import "NSException+MGLAdditions.h"
-#import "NSPredicate+MGLPrivateAdditions.h"
-#import "NSString+MGLAdditions.h"
-#import "NSURL+MGLAdditions.h"
-#import "UIDevice+MGLAdditions.h"
-#import "UIImage+MGLAdditions.h"
-#import "UIViewController+MGLAdditions.h"
-#import "UIView+MGLAdditions.h"
-
-#import "MGLFaux3DUserLocationAnnotationView.h"
-#import "MGLUserLocationAnnotationView.h"
-#import "MGLUserLocationAnnotationView_Private.h"
-#import "MGLUserLocation_Private.h"
-#import "MGLAnnotationImage_Private.h"
-#import "MGLAnnotationView_Private.h"
-#import "MGLCompassButton_Private.h"
-#import "MGLScaleBar.h"
-#import "MGLStyle_Private.h"
-#import "MGLStyleLayer_Private.h"
-#import "MGLMapboxEvents.h"
-#import "MGLSDKUpdateChecker.h"
-#import "MGLCompactCalloutView.h"
-#import "MGLAnnotationContainerView.h"
-#import "MGLAnnotationContainerView_Private.h"
-#import "MGLAttributionInfo_Private.h"
-#import "MGLMapAccessibilityElement.h"
-#import "MGLLocationManager_Private.h"
-#import "MGLLoggingConfiguration_Private.h"
-#import "MGLNetworkIntegrationManager.h"
-#import "MMEConstants.h"
-
-#include <algorithm>
-#include <cstdlib>
-#include <map>
-#include <unordered_set>
-
-class MGLAnnotationContext;
-
-const MGLMapViewDecelerationRate MGLMapViewDecelerationRateNormal = UIScrollViewDecelerationRateNormal;
-const MGLMapViewDecelerationRate MGLMapViewDecelerationRateFast = UIScrollViewDecelerationRateFast;
-const MGLMapViewDecelerationRate MGLMapViewDecelerationRateImmediate = 0.0;
-
-const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondDefault = -1;
-const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondLowPower = 30;
-const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondMaximum = 0;
-
-const MGLExceptionName MGLMissingLocationServicesUsageDescriptionException = @"MGLMissingLocationServicesUsageDescriptionException";
-const MGLExceptionName MGLUserLocationAnnotationTypeException = @"MGLUserLocationAnnotationTypeException";
-const MGLExceptionName MGLUnderlyingMapUnavailableException = @"MGLUnderlyingMapUnavailableException";
-
-const CGPoint MGLOrnamentDefaultPositionOffset = CGPointMake(8, 8);
-
-/// Indicates the manner in which the map view is tracking the user location.
-typedef NS_ENUM(NSUInteger, MGLUserTrackingState) {
- /// The map view is not yet tracking the user location.
- MGLUserTrackingStatePossible = 0,
- /// The map view has begun to move to the first reported user location.
- MGLUserTrackingStateBegan,
- /// The map view begins a significant transition.
- MGLUserTrackingStateBeginSignificantTransition,
- /// The map view has finished moving to the first reported user location.
- MGLUserTrackingStateChanged,
-};
-
-const NSTimeInterval MGLAnimationDuration = 0.3;
-
-/// Duration of an animation due to a user location update, typically chosen to
-/// match a typical interval between user location updates.
-const NSTimeInterval MGLUserLocationAnimationDuration = 1.0;
-
-/// Distance between the map view’s edge and that of the user location
-/// annotation view.
-const UIEdgeInsets MGLUserLocationAnnotationViewInset = UIEdgeInsetsMake(50, 0, 50, 0);
-
-const CGSize MGLAnnotationUpdateViewportOutset = {150, 150};
-const CGFloat MGLMinimumZoom = 3;
-
-/// Minimum initial zoom level when entering user tracking mode.
-const double MGLMinimumZoomLevelForUserTracking = 10.5;
-
-/// Initial zoom level when entering user tracking mode from a low zoom level.
-const double MGLDefaultZoomLevelForUserTracking = 14.0;
-
-/// Tolerance for snapping to true north, measured in degrees in either direction.
-const CLLocationDirection MGLToleranceForSnappingToNorth = 7;
-
-/// Distance threshold to stop the camera while animating.
-const CLLocationDistance MGLDistanceThresholdForCameraPause = 500;
-
-/// Rotation threshold while a pinch gesture is occurring.
-static NSString * const MGLRotationThresholdWhileZoomingKey = @"MGLRotationThresholdWhileZooming";
-
-/// Reuse identifier and file name of the default point annotation image.
-static NSString * const MGLDefaultStyleMarkerSymbolName = @"default_marker";
-
-/// Reuse identifier and file name of the invisible point annotation image used
-/// by annotations that are visually backed by MGLAnnotationView objects
-static NSString * const MGLInvisibleStyleMarkerSymbolName = @"invisible_marker";
-
-/// Prefix that denotes a sprite installed by MGLMapView, to avoid collisions
-/// with style-defined sprites.
-NSString * const MGLAnnotationSpritePrefix = @"com.mapbox.sprites.";
-
-/// Slop area around the hit testing point, allowing for imprecise annotation selection.
-const CGFloat MGLAnnotationImagePaddingForHitTest = 5;
-
-/// Distance from the callout’s anchor point to the annotation it points to.
-const CGFloat MGLAnnotationImagePaddingForCallout = 1;
-
-const CGSize MGLAnnotationAccessibilityElementMinimumSize = CGSizeMake(10, 10);
-
-/// The number of view annotations (excluding the user location view) that must
-/// be descendents of `MGLMapView` before presentsWithTransaction is enabled.
-static const NSUInteger MGLPresentsWithTransactionAnnotationCount = 0;
-
-/// An indication that the requested annotation was not found or is nonexistent.
-enum { MGLAnnotationTagNotFound = UINT32_MAX };
-
-/// The threshold used to consider when a tilt gesture should start.
-const CLLocationDegrees MGLHorizontalTiltToleranceDegrees = 45.0;
-
-/// Mapping from an annotation tag to metadata about that annotation, including
-/// the annotation itself.
-typedef std::unordered_map<MGLAnnotationTag, MGLAnnotationContext> MGLAnnotationTagContextMap;
-
-/// Mapping from an annotation object to an annotation tag.
-typedef std::map<id<MGLAnnotation>, MGLAnnotationTag> MGLAnnotationObjectTagMap;
-
-mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function)
-{
- if ( ! function)
- {
- function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
- }
- float p1[2], p2[2];
- [function getControlPointAtIndex:0 values:p1];
- [function getControlPointAtIndex:1 values:p2];
- return { p1[0], p1[1], p2[0], p2[1] };
-}
-
-/// 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;
- MGLAnnotationView *annotationView;
- NSString *viewReuseIdentifier;
-};
-
-#pragma mark - Private -
-
-@interface MGLMapView () <UIGestureRecognizerDelegate,
- MGLLocationManagerDelegate,
- MGLSMCalloutViewDelegate,
- MGLCalloutViewDelegate,
- MGLMultiPointDelegate,
- MGLAnnotationImageDelegate>
-
-@property (nonatomic) UIImageView *glSnapshotView;
-
-@property (nonatomic) NSMutableArray<NSLayoutConstraint *> *scaleBarConstraints;
-@property (nonatomic, readwrite) MGLScaleBar *scaleBar;
-@property (nonatomic, readwrite) MGLCompassButton *compassView;
-@property (nonatomic) NSMutableArray<NSLayoutConstraint *> *compassViewConstraints;
-@property (nonatomic, readwrite) UIImageView *logoView;
-@property (nonatomic) NSMutableArray<NSLayoutConstraint *> *logoViewConstraints;
-@property (nonatomic, readwrite) UIButton *attributionButton;
-@property (nonatomic) NSMutableArray<NSLayoutConstraint *> *attributionButtonConstraints;
-@property (nonatomic, weak) UIAlertController *attributionController;
-
-@property (nonatomic, readwrite) MGLStyle *style;
-
-@property (nonatomic) UITapGestureRecognizer *singleTapGestureRecognizer;
-@property (nonatomic) UITapGestureRecognizer *doubleTap;
-@property (nonatomic) UITapGestureRecognizer *twoFingerTap;
-@property (nonatomic) UIPanGestureRecognizer *pan;
-@property (nonatomic) UIPinchGestureRecognizer *pinch;
-@property (nonatomic) UIRotationGestureRecognizer *rotate;
-@property (nonatomic) UILongPressGestureRecognizer *quickZoom;
-@property (nonatomic) UIPanGestureRecognizer *twoFingerDrag;
-
-@property (nonatomic) UIInterfaceOrientation currentOrientation;
-@property (nonatomic) UIInterfaceOrientationMask applicationSupportedInterfaceOrientations;
-
-@property (nonatomic) MGLCameraChangeReason cameraChangeReasonBitmask;
-
-/// Mapping from reusable identifiers to annotation images.
-@property (nonatomic) NSMutableDictionary<NSString *, MGLAnnotationImage *> *annotationImagesByIdentifier;
-
-/// Currently shown popover representing the selected annotation.
-@property (nonatomic) UIView<MGLCalloutView> *calloutViewForSelectedAnnotation;
-
-/// Anchor coordinate from which to present callout views (for example, for shapes this
-/// could be the touch point rather than its centroid)
-@property (nonatomic) CLLocationCoordinate2D anchorCoordinateForSelectedAnnotation;
-
-@property (nonatomic) MGLUserLocationAnnotationView *userLocationAnnotationView;
-
-/// Indicates how thoroughly the map view is tracking the user location.
-@property (nonatomic) MGLUserTrackingState userTrackingState;
-@property (nonatomic) CGFloat scale;
-@property (nonatomic) CGFloat angle;
-@property (nonatomic) CGFloat quickZoomStart;
-@property (nonatomic, getter=isDormant) BOOL dormant;
-@property (nonatomic, readonly, getter=isRotationAllowed) BOOL rotationAllowed;
-@property (nonatomic) CGFloat rotationThresholdWhileZooming;
-@property (nonatomic) CGFloat rotationBeforeThresholdMet;
-@property (nonatomic) BOOL isZooming;
-@property (nonatomic) BOOL isRotating;
-@property (nonatomic) BOOL shouldTriggerHapticFeedbackForCompass;
-@property (nonatomic) MGLMapViewProxyAccessibilityElement *mapViewProxyAccessibilityElement;
-@property (nonatomic) MGLAnnotationContainerView *annotationContainerView;
-@property (nonatomic) MGLUserLocation *userLocation;
-@property (nonatomic) NSMutableDictionary<NSString *, NSMutableArray<MGLAnnotationView *> *> *annotationViewReuseQueueByIdentifier;
-@property (nonatomic, readonly) BOOL enablePresentsWithTransaction;
-@property (nonatomic) UIImage *lastSnapshotImage;
-@property (nonatomic) NSMutableArray *pendingCompletionBlocks;
-
-/// Experimental rendering performance measurement.
-@property (nonatomic) BOOL experimental_enableFrameRateMeasurement;
-@property (nonatomic) CGFloat averageFrameRate;
-@property (nonatomic) CFTimeInterval frameTime;
-@property (nonatomic) CFTimeInterval averageFrameTime;
-
-/// Residual properties (saved on app termination)
-@property (nonatomic) BOOL terminated;
-@property (nonatomic, copy) MGLMapCamera *residualCamera;
-@property (nonatomic) MGLMapDebugMaskOptions residualDebugMask;
-@property (nonatomic, copy) NSURL *residualStyleURL;
-
-/// Tilt gesture recognizer helper
-@property (nonatomic, assign) CGPoint dragGestureMiddlePoint;
-
-/// This property is used to keep track of the view's safe edge insets
-/// and calculate the ornament's position
-@property (nonatomic, assign) UIEdgeInsets safeMapViewContentInsets;
-@property (nonatomic, strong) NSNumber *automaticallyAdjustContentInsetHolder;
-
-- (mbgl::Map &)mbglMap;
-
-@end
-
-@implementation MGLMapView
-{
- mbgl::Map *_mbglMap;
- std::unique_ptr<MGLMapViewImpl> _mbglView;
- std::unique_ptr<MGLRenderFrontend> _rendererFrontend;
-
- BOOL _opaque;
-
- MGLAnnotationTagContextMap _annotationContextsByAnnotationTag;
- MGLAnnotationObjectTagMap _annotationTagsByAnnotation;
-
- /// Tag of the selected annotation. If the user location annotation is selected, this ivar is set to `MGLAnnotationTagNotFound`.
- MGLAnnotationTag _selectedAnnotationTag;
-
- BOOL _userLocationAnnotationIsSelected;
- /// Size of the rectangle formed by unioning the maximum slop area around every annotation image and annotation image view.
- CGSize _unionedAnnotationRepresentationSize;
- CGSize _largestAnnotationViewSize;
- std::vector<MGLAnnotationTag> _annotationsNearbyLastTap;
- CGPoint _initialImplicitCalloutViewOffset;
- NSDate *_userLocationAnimationCompletionDate;
- /// True if a willChange notification has been issued for shape annotation layers and a didChange notification is pending.
- BOOL _isChangingAnnotationLayers;
- BOOL _isWaitingForRedundantReachableNotification;
-
- CLLocationDegrees _pendingLatitude;
- CLLocationDegrees _pendingLongitude;
-
- CADisplayLink *_displayLink;
- BOOL _needsDisplayRefresh;
-
- NSInteger _changeDelimiterSuppressionDepth;
-
- /// Center of the pinch gesture on the previous iteration of the gesture.
- CGPoint _previousPinchCenterPoint;
- NSUInteger _previousPinchNumberOfTouches;
-
- CLLocationDistance _distanceFromOldUserLocation;
-
- BOOL _delegateHasAlphasForShapeAnnotations;
- BOOL _delegateHasStrokeColorsForShapeAnnotations;
- BOOL _delegateHasFillColorsForShapeAnnotations;
- BOOL _delegateHasLineWidthsForShapeAnnotations;
-
- NSArray<id <MGLFeature>> *_visiblePlaceFeatures;
- NSArray<id <MGLFeature>> *_visibleRoadFeatures;
- NSMutableSet<MGLFeatureAccessibilityElement *> *_featureAccessibilityElements;
- BOOL _accessibilityValueAnnouncementIsPending;
-
- MGLReachability *_reachability;
-
- /// Experimental rendering performance measurement.
- CFTimeInterval _frameCounterStartTime;
- NSInteger _frameCount;
- CFTimeInterval _frameDurations;
-}
-
-#pragma mark - Setup & Teardown -
-
-- (instancetype)initWithFrame:(CGRect)frame
-{
- if (self = [super initWithFrame:frame])
- {
- MGLLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
- MGLLogDebug(@"Initializing frame: %@", NSStringFromCGRect(frame));
- [self commonInit];
- self.styleURL = nil;
- MGLLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
- }
- return self;
-}
-
-- (instancetype)initWithFrame:(CGRect)frame styleURL:(nullable NSURL *)styleURL
-{
- if (self = [super initWithFrame:frame])
- {
- MGLLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
- MGLLogDebug(@"Initializing frame: %@ styleURL: %@", NSStringFromCGRect(frame), styleURL);
- [self commonInit];
- self.styleURL = styleURL;
- MGLLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
- }
- return self;
-}
-
-- (instancetype)initWithCoder:(nonnull NSCoder *)decoder
-{
- if (self = [super initWithCoder:decoder])
- {
- MGLLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class]));
- [self commonInit];
- self.styleURL = nil;
- MGLLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class]));
- }
- return self;
-}
-
-+ (void)initialize
-{
- if (self == [MGLMapView class])
- {
- [MGLSDKUpdateChecker checkForUpdates];
- }
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingStyle
-{
- return [NSSet setWithObject:@"styleURL"];
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingStyleURL
-{
- return [NSSet setWithObjects:@"styleURL__", nil];
-}
-
-- (nonnull NSURL *)styleURL
-{
- if (!_mbglMap)
- {
- NSAssert(self.terminated, @"_mbglMap should only be unavailable during app termination");
- return self.residualStyleURL;
- }
-
- NSString *styleURLString = @(self.mbglMap.getStyle().getURL().c_str()).mgl_stringOrNilIfEmpty;
- MGLAssert(styleURLString, @"Invalid style URL string %@", styleURLString);
- return styleURLString ? [NSURL URLWithString:styleURLString] : nil;
-}
-
-- (void)setStyleURL:(nullable NSURL *)styleURL
-{
- if ( ! styleURL)
- {
- styleURL = [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion];
- }
- MGLLogDebug(@"Setting styleURL: %@", styleURL);
- styleURL = styleURL.mgl_URLByStandardizingScheme;
- self.style = nil;
- self.mbglMap.getStyle().loadURL([[styleURL absoluteString] UTF8String]);
-}
-
-- (IBAction)reloadStyle:(__unused id)sender {
- MGLLogInfo(@"Reloading style.");
- NSURL *styleURL = self.styleURL;
- self.mbglMap.getStyle().loadURL("");
- self.styleURL = styleURL;
-}
-
-- (mbgl::Map &)mbglMap
-{
- if (!_mbglMap)
- {
- [NSException raise:MGLUnderlyingMapUnavailableException
- format:@"The underlying map is not available - this happens during app termination"];
- }
- return *_mbglMap;
-}
-
-- (mbgl::Renderer *)renderer
-{
- return _rendererFrontend->getRenderer();
-}
-
-- (void)commonInit
-{
- _opaque = NO;
-
- // setup accessibility
- //
-// self.isAccessibilityElement = YES;
- MGLNativeNetworkManager.sharedManager.delegate = MGLNetworkIntegrationManager.sharedManager;
- self.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"MAP_A11Y_LABEL", nil, nil, @"Map", @"Accessibility label");
- self.accessibilityTraits = UIAccessibilityTraitAllowsDirectInteraction | UIAccessibilityTraitAdjustable;
- self.backgroundColor = [UIColor clearColor];
- self.clipsToBounds = YES;
- if (@available(iOS 11.0, *)) { self.accessibilityIgnoresInvertColors = YES; }
-
- self.preferredFramesPerSecond = MGLMapViewPreferredFramesPerSecondDefault;
-
- // setup mbgl view
- _mbglView = MGLMapViewImpl::Create(self);
-
- BOOL background = [UIApplication sharedApplication].applicationState == UIApplicationStateBackground;
- if (!background)
- {
- _mbglView->createView();
- }
- // Delete the pre-offline ambient cache at ~/Library/Caches/cache.db.
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
- NSString *fileCachePath = [paths.firstObject stringByAppendingPathComponent:@"cache.db"];
- [[NSFileManager defaultManager] removeItemAtPath:fileCachePath error:NULL];
-
- // setup mbgl map
- MGLRendererConfiguration *config = [MGLRendererConfiguration currentConfiguration];
-
- auto renderer = std::make_unique<mbgl::Renderer>(_mbglView->getRendererBackend(), config.scaleFactor, config.localFontFamilyName);
- BOOL enableCrossSourceCollisions = !config.perSourceCollisions;
- _rendererFrontend = std::make_unique<MGLRenderFrontend>(std::move(renderer), self, _mbglView->getRendererBackend());
-
- mbgl::MapOptions mapOptions;
- mapOptions.withMapMode(mbgl::MapMode::Continuous)
- .withSize(self.size)
- .withPixelRatio(config.scaleFactor)
- .withConstrainMode(mbgl::ConstrainMode::None)
- .withViewportMode(mbgl::ViewportMode::Default)
- .withCrossSourceCollisions(enableCrossSourceCollisions);
-
- mbgl::ResourceOptions resourceOptions;
- resourceOptions.withCachePath([[MGLOfflineStorage sharedOfflineStorage] mbglCachePath])
- .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String);
-
- NSAssert(!_mbglMap, @"_mbglMap should be NULL");
- _mbglMap = new mbgl::Map(*_rendererFrontend, *_mbglView, mapOptions, resourceOptions);
-
- // start paused if in IB
- if (background) {
- self.dormant = YES;
- }
-
- // Notify map object when network reachability status changes.
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(reachabilityChanged:)
- name:kMGLReachabilityChangedNotification
- object:nil];
-
- _reachability = [MGLReachability reachabilityForInternetConnection];
- if ([_reachability isReachable])
- {
- _isWaitingForRedundantReachableNotification = YES;
- }
- [_reachability startNotifier];
-
- // setup default location manager
- self.locationManager = nil;
-
- // Set up annotation management and selection state.
- _annotationImagesByIdentifier = [NSMutableDictionary dictionary];
- _annotationContextsByAnnotationTag = {};
- _annotationTagsByAnnotation = {};
- _annotationViewReuseQueueByIdentifier = [NSMutableDictionary dictionary];
- _selectedAnnotationTag = MGLAnnotationTagNotFound;
- _annotationsNearbyLastTap = {};
-
- // TODO: This warning should be removed when automaticallyAdjustsScrollViewInsets is removed from
- // the UIViewController api.
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSLog(@"%@ WARNING UIViewController.automaticallyAdjustsScrollViewInsets is deprecated use MGLMapView.automaticallyAdjustContentInset instead.",
- NSStringFromClass(self.class));
- });
-
- // setup logo
- //
- UIImage *logo = [UIImage mgl_resourceImageNamed:@"mapbox"];
- _logoView = [[UIImageView alloc] initWithImage:logo];
- _logoView.accessibilityTraits = UIAccessibilityTraitStaticText;
- _logoView.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"LOGO_A11Y_LABEL", nil, nil, @"Mapbox", @"Accessibility label");
- _logoView.translatesAutoresizingMaskIntoConstraints = NO;
- [self addSubview:_logoView];
- _logoViewConstraints = [NSMutableArray array];
- _logoViewPosition = MGLOrnamentPositionBottomLeft;
- _logoViewMargins = MGLOrnamentDefaultPositionOffset;
-
- // setup attribution
- //
- _attributionButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
- _attributionButton.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"INFO_A11Y_LABEL", nil, nil, @"About this map", @"Accessibility label");
- _attributionButton.accessibilityHint = NSLocalizedStringWithDefaultValue(@"INFO_A11Y_HINT", nil, nil, @"Shows credits, a feedback form, and more", @"Accessibility hint");
- [_attributionButton addTarget:self action:@selector(showAttribution:) forControlEvents:UIControlEventTouchUpInside];
- _attributionButton.translatesAutoresizingMaskIntoConstraints = NO;
- [self addSubview:_attributionButton];
- _attributionButtonConstraints = [NSMutableArray array];
- [_attributionButton addObserver:self forKeyPath:@"hidden" options:NSKeyValueObservingOptionNew context:NULL];
-
- UILongPressGestureRecognizer *attributionLongPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(showAttribution:)];
- [_attributionButton addGestureRecognizer:attributionLongPress];
- _attributionButtonPosition = MGLOrnamentPositionBottomRight;
- _attributionButtonMargins = MGLOrnamentDefaultPositionOffset;
-
- // setup compass
- //
- _compassView = [MGLCompassButton compassButtonWithMapView:self];
- [self addSubview:_compassView];
- _compassViewConstraints = [NSMutableArray array];
- _compassViewPosition = MGLOrnamentPositionTopRight;
- _compassViewMargins = MGLOrnamentDefaultPositionOffset;
-
- // setup scale control
- //
- _scaleBar = [[MGLScaleBar alloc] init];
- _scaleBar.translatesAutoresizingMaskIntoConstraints = NO;
- [self addSubview:_scaleBar];
- _scaleBarConstraints = [NSMutableArray array];
- _scaleBarPosition = MGLOrnamentPositionTopLeft;
- _scaleBarMargins = MGLOrnamentDefaultPositionOffset;
-
- [self installConstraints];
-
- // setup interaction
- //
- _pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
- _pan.delegate = self;
- _pan.maximumNumberOfTouches = 1;
- [self addGestureRecognizer:_pan];
- _scrollEnabled = YES;
-
- _pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
- _pinch.delegate = self;
- [self addGestureRecognizer:_pinch];
- _zoomEnabled = YES;
-
- _rotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotateGesture:)];
- _rotate.delegate = self;
- [self addGestureRecognizer:_rotate];
- _rotateEnabled = YES;
- _rotationThresholdWhileZooming = 3;
-
- _doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTapGesture:)];
- _doubleTap.numberOfTapsRequired = 2;
- [self addGestureRecognizer:_doubleTap];
-
- _twoFingerDrag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerDragGesture:)];
- _twoFingerDrag.minimumNumberOfTouches = 2;
- _twoFingerDrag.maximumNumberOfTouches = 2;
- _twoFingerDrag.delegate = self;
- [_twoFingerDrag requireGestureRecognizerToFail:_pan];
- [self addGestureRecognizer:_twoFingerDrag];
- _pitchEnabled = YES;
-
- _twoFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerTapGesture:)];
- _twoFingerTap.numberOfTouchesRequired = 2;
- [_twoFingerTap requireGestureRecognizerToFail:_pinch];
- [_twoFingerTap requireGestureRecognizerToFail:_rotate];
- [_twoFingerTap requireGestureRecognizerToFail:_twoFingerDrag];
- [self addGestureRecognizer:_twoFingerTap];
-
- _hapticFeedbackEnabled = YES;
-
- _decelerationRate = MGLMapViewDecelerationRateNormal;
-
- _quickZoom = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleQuickZoomGesture:)];
- _quickZoom.numberOfTapsRequired = 1;
- _quickZoom.minimumPressDuration = 0;
- [_quickZoom requireGestureRecognizerToFail:_doubleTap];
- [self addGestureRecognizer:_quickZoom];
-
- _singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)];
- [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTap];
- _singleTapGestureRecognizer.delegate = self;
- [_singleTapGestureRecognizer requireGestureRecognizerToFail:_quickZoom];
- [self addGestureRecognizer:_singleTapGestureRecognizer];
-
- // observe app activity
- //
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willTerminate) name:UIApplicationWillTerminateNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
-
- // Pending completion blocks are called *after* annotation views have been updated
- // in updateFromDisplayLink.
- _pendingCompletionBlocks = [NSMutableArray array];
-
-
- // As of 3.7.5, we intentionally do not listen for `UIApplicationWillResignActiveNotification` or call `pauseRendering:` in response to it, as doing
- // so causes a loop when asking for location permission. See: https://github.com/mapbox/mapbox-gl-native/issues/11225
-
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
-
- // Device orientation management
- self.currentOrientation = UIInterfaceOrientationUnknown;
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
- [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
-
- // set initial position
- //
- mbgl::CameraOptions options;
- options.center = mbgl::LatLng(0, 0);
- mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
- options.padding = padding;
- options.zoom = 0;
-
- _cameraChangeReasonBitmask = MGLCameraChangeReasonNone;
-
- _mbglMap->jumpTo(options);
- _pendingLatitude = NAN;
- _pendingLongitude = NAN;
- _targetCoordinate = kCLLocationCoordinate2DInvalid;
-
- if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) {
- [MGLMapboxEvents pushTurnstileEvent];
- [MGLMapboxEvents pushEvent:MMEEventTypeMapLoad withAttributes:@{}];
- }
-
-}
-
-- (mbgl::Size)size
-{
- // check for minimum texture size supported by OpenGL ES 2.0
- //
- CGSize size = CGSizeMake(MAX(self.bounds.size.width, 64), MAX(self.bounds.size.height, 64));
- return { static_cast<uint32_t>(size.width),
- static_cast<uint32_t>(size.height) };
-}
-
-- (void)reachabilityChanged:(NSNotification *)notification
-{
- MGLAssertIsMainThread();
-
- MGLReachability *reachability = [notification object];
- if ( ! _isWaitingForRedundantReachableNotification && [reachability isReachable])
- {
- mbgl::NetworkStatus::Reachable();
- }
- _isWaitingForRedundantReachableNotification = NO;
-}
-
-
-- (void)destroyCoreObjects {
- // Record the current state. Currently only saving a limited set of properties.
- self.terminated = YES;
- self.residualCamera = self.camera;
- self.residualDebugMask = self.debugMask;
- self.residualStyleURL = self.styleURL;
-
- // Tear down C++ objects, insuring worker threads correctly terminate.
- // Because of how _mbglMap is constructed, we need to destroy it first.
- delete _mbglMap;
- _mbglMap = nullptr;
-
- _mbglView.reset();
-
- _rendererFrontend.reset();
-}
-
-- (void)dealloc
-{
- MGLLogInfo(@"Deallocating MGLMapView.");
- [_reachability stopNotifier];
-
- [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- [_attributionButton removeObserver:self forKeyPath:@"hidden"];
-
- // Removing the annotations unregisters any outstanding KVO observers.
- NSArray *annotations = self.annotations;
- if (annotations)
- {
- [self removeAnnotations:annotations];
- }
-
- [self validateDisplayLink];
-
- [self destroyCoreObjects];
-
- [self.compassViewConstraints removeAllObjects];
- self.compassViewConstraints = nil;
-
- [self.scaleBarConstraints removeAllObjects];
- self.scaleBarConstraints = nil;
-
- [self.logoViewConstraints removeAllObjects];
- self.logoViewConstraints = nil;
-
- [self.attributionButtonConstraints removeAllObjects];
- self.attributionButtonConstraints = nil;
-
- [_locationManager stopUpdatingLocation];
- [_locationManager stopUpdatingHeading];
- _locationManager.delegate = nil;
-}
-
-- (void)setDelegate:(nullable id<MGLMapViewDelegate>)delegate
-{
- MGLLogDebug(@"Setting delegate: %@", delegate);
- if (_delegate == delegate) return;
-
- _delegate = delegate;
-
- _delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)];
- _delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)];
- _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)];
- _delegateHasLineWidthsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)];
-}
-
-- (void)didReceiveMemoryWarning
-{
- MGLAssertIsMainThread();
-
- if ( ! self.dormant && _rendererFrontend)
- {
- _rendererFrontend->reduceMemoryUse();
- }
-
- self.lastSnapshotImage = nil;
-}
-
-- (MGLMapViewImpl *)viewImpl
-{
- return _mbglView.get();
-}
-
-#pragma mark - Layout -
-
-+ (BOOL)requiresConstraintBasedLayout
-{
- return YES;
-}
-
-- (void)setScaleBarPosition:(MGLOrnamentPosition)scaleBarPosition {
- MGLLogDebug(@"Setting scaleBarPosition: %lu", scaleBarPosition);
- _scaleBarPosition = scaleBarPosition;
- [self installScaleBarConstraints];
-}
-
-- (void)setScaleBarMargins:(CGPoint)scaleBarMargins {
- MGLLogDebug(@"Setting scaleBarMargins: (x:%f, y:%f)", scaleBarMargins.x, scaleBarMargins.y);
- _scaleBarMargins = scaleBarMargins;
- [self installScaleBarConstraints];
-}
-
-- (void)setCompassViewPosition:(MGLOrnamentPosition)compassViewPosition {
- MGLLogDebug(@"Setting compassViewPosition: %lu", compassViewPosition);
- _compassViewPosition = compassViewPosition;
- [self installCompassViewConstraints];
-}
-
-- (void)setCompassViewMargins:(CGPoint)compassViewMargins {
- MGLLogDebug(@"Setting compassViewOffset: (x:%f, y:%f)", compassViewMargins.x, compassViewMargins.y);
- _compassViewMargins = compassViewMargins;
- [self installCompassViewConstraints];
-}
-
-- (void)setLogoViewPosition:(MGLOrnamentPosition)logoViewPosition {
- MGLLogDebug(@"Setting logoViewPosition: %lu", logoViewPosition);
- _logoViewPosition = logoViewPosition;
- [self installLogoViewConstraints];
-}
-
-- (void)setLogoViewMargins:(CGPoint)logoViewMargins {
- MGLLogDebug(@"Setting logoViewMargins: (x:%f, y:%f)", logoViewMargins.x, logoViewMargins.y);
- _logoViewMargins = logoViewMargins;
- [self installLogoViewConstraints];
-}
-
-- (void)setAttributionButtonPosition:(MGLOrnamentPosition)attributionButtonPosition {
- MGLLogDebug(@"Setting attributionButtonPosition: %lu", attributionButtonPosition);
- _attributionButtonPosition = attributionButtonPosition;
- [self installAttributionButtonConstraints];
-}
-
-- (void)setAttributionButtonMargins:(CGPoint)attributionButtonMargins {
- MGLLogDebug(@"Setting attributionButtonMargins: (x:%f, y:%f)", attributionButtonMargins.x, attributionButtonMargins.y);
- _attributionButtonMargins = attributionButtonMargins;
- [self installAttributionButtonConstraints];
-}
-
-- (void)updateConstraintsForOrnament:(UIView *)view
- constraints:(NSMutableArray *)constraints
- position:(MGLOrnamentPosition)position
- size:(CGSize)size
- margins:(CGPoint)margins {
- NSMutableArray *updatedConstraints = [NSMutableArray array];
- UIEdgeInsets inset = UIEdgeInsetsZero;
-
- BOOL automaticallyAdjustContentInset;
- if (_automaticallyAdjustContentInsetHolder) {
- automaticallyAdjustContentInset = _automaticallyAdjustContentInsetHolder.boolValue;
- } else {
- UIViewController *viewController = [self rootViewController];
- automaticallyAdjustContentInset = viewController.automaticallyAdjustsScrollViewInsets;
- }
-
- if (! automaticallyAdjustContentInset) {
- inset = UIEdgeInsetsMake(self.contentInset.top - self.safeMapViewContentInsets.top,
- self.contentInset.left - self.safeMapViewContentInsets.left,
- self.contentInset.bottom - self.safeMapViewContentInsets.bottom,
- self.contentInset.right - self.safeMapViewContentInsets.right);
-
- // makes sure the insets don't have negative values that could hide the ornaments
- // thus violating our ToS
- inset = UIEdgeInsetsMake(fmaxf(inset.top, 0),
- fmaxf(inset.left, 0),
- fmaxf(inset.bottom, 0),
- fmaxf(inset.right, 0));
- }
-
- switch (position) {
- case MGLOrnamentPositionTopLeft:
- [updatedConstraints addObject:[view.topAnchor constraintEqualToAnchor:self.mgl_safeTopAnchor constant:margins.y + inset.top]];
- [updatedConstraints addObject:[view.leadingAnchor constraintEqualToAnchor:self.mgl_safeLeadingAnchor constant:margins.x + inset.left]];
- break;
- case MGLOrnamentPositionTopRight:
- [updatedConstraints addObject:[view.topAnchor constraintEqualToAnchor:self.mgl_safeTopAnchor constant:margins.y + inset.top]];
- [updatedConstraints addObject:[self.mgl_safeTrailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margins.x + inset.right]];
- break;
- case MGLOrnamentPositionBottomLeft:
- [updatedConstraints addObject:[self.mgl_safeBottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:margins.y + inset.bottom]];
- [updatedConstraints addObject:[view.leadingAnchor constraintEqualToAnchor:self.mgl_safeLeadingAnchor constant:margins.x + inset.left]];
- break;
- case MGLOrnamentPositionBottomRight:
- [updatedConstraints addObject:[self.mgl_safeBottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:margins.y + inset.bottom]];
- [updatedConstraints addObject: [self.mgl_safeTrailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margins.x + inset.right]];
- break;
- }
-
- if (!CGSizeEqualToSize(size, CGSizeZero)) {
- [updatedConstraints addObject:[view.widthAnchor constraintEqualToConstant:size.width]];
- [updatedConstraints addObject:[view.heightAnchor constraintEqualToConstant:size.height]];
- }
-
- [NSLayoutConstraint deactivateConstraints:constraints];
- [constraints removeAllObjects];
- [NSLayoutConstraint activateConstraints:updatedConstraints];
- [constraints addObjectsFromArray:updatedConstraints];
-}
-
-- (void)installConstraints
-{
- [self installCompassViewConstraints];
- [self installScaleBarConstraints];
- [self installLogoViewConstraints];
- [self installAttributionButtonConstraints];
-}
-
-- (void)installCompassViewConstraints {
- // compass view
- [self updateConstraintsForOrnament:self.compassView
- constraints:self.compassViewConstraints
- position:self.compassViewPosition
- size:self.compassView.bounds.size
- margins:self.compassViewMargins];
-}
-
-- (void)installScaleBarConstraints {
- // scale bar view
- [self updateConstraintsForOrnament:self.scaleBar
- constraints:self.scaleBarConstraints
- position:self.scaleBarPosition
- size:CGSizeZero
- margins:self.scaleBarMargins];
-}
-
-- (void)installLogoViewConstraints {
- // logo view
- [self updateConstraintsForOrnament:self.logoView
- constraints:self.logoViewConstraints
- position:self.logoViewPosition
- size:self.logoView.bounds.size
- margins:self.logoViewMargins];
-}
-
-- (void)installAttributionButtonConstraints {
- // attribution button
- [self updateConstraintsForOrnament:self.attributionButton
- constraints:self.attributionButtonConstraints
- position:self.attributionButtonPosition
- size:self.attributionButton.bounds.size
- margins:self.attributionButtonMargins];
-}
-
-- (BOOL)isOpaque
-{
- return _opaque;
-}
-
-- (void)setOpaque:(BOOL)opaque
-{
- _opaque = opaque;
- if (_mbglView) {
- _mbglView->setOpaque(opaque);
- }
-}
-
-- (void)renderSync
-{
- if ( ! self.dormant && _rendererFrontend)
- {
- _rendererFrontend->render();
- }
-}
-
-// This gets called when the view dimension changes, e.g. because the device is being rotated.
-- (void)layoutSubviews
-{
- [super layoutSubviews];
-
- // Calling this here instead of in the scale bar itself because if this is done in the
- // scale bar instance, it triggers a call to this `layoutSubviews` method that calls
- // `_mbglMap->setSize()` just below that triggers rendering update which triggers
- // another scale bar update which causes a rendering update loop and a major performace
- // degradation.
- [self.scaleBar invalidateIntrinsicContentSize];
-
- [self adjustContentInset];
-
- if (_mbglView) {
- _mbglView->layoutChanged();
- }
-
- if (_mbglMap) {
- self.mbglMap.setSize([self size]);
- }
-
- if (self.compassView.alpha)
- {
- [self updateCompass];
- }
-
- if (self.compassView.alpha || self.showsUserHeadingIndicator)
- {
- [self updateHeadingForDeviceOrientation];
- }
-
- [self updateUserLocationAnnotationView];
-
- [self updateAttributionAlertView];
-}
-
-/// Updates `contentInset` to reflect the current window geometry.
-- (void)adjustContentInset
-{
- UIEdgeInsets adjustedContentInsets = UIEdgeInsetsZero;
- UIViewController *viewController = [self rootViewController];
- BOOL automaticallyAdjustContentInset;
- if (@available(iOS 11.0, *))
- {
- adjustedContentInsets = self.safeAreaInsets;
-
- } else {
- adjustedContentInsets.top = viewController.topLayoutGuide.length;
- CGFloat bottomPoint = CGRectGetMaxY(viewController.view.bounds) -
- (CGRectGetMaxY(viewController.view.bounds)
- - viewController.bottomLayoutGuide.length);
- adjustedContentInsets.bottom = bottomPoint;
-
- }
-
- if (_automaticallyAdjustContentInsetHolder) {
- automaticallyAdjustContentInset = _automaticallyAdjustContentInsetHolder.boolValue;
- } else {
- automaticallyAdjustContentInset = viewController.automaticallyAdjustsScrollViewInsets;
- }
-
- self.safeMapViewContentInsets = adjustedContentInsets;
- if ( ! automaticallyAdjustContentInset)
- {
- return;
- }
-
- self.contentInset = adjustedContentInsets;
-}
-
-- (UIViewController *)rootViewController {
- // We could crawl all the way up the responder chain using
- // -viewControllerForLayoutGuides, but an intervening view means that any
- // manual contentInset should not be overridden; something other than the
- // top and bottom bars may be influencing the manual inset.
- UIViewController *viewController;
- if ([self.nextResponder isKindOfClass:[UIViewController class]])
- {
- // This map view is the content view of a view controller.
- viewController = (UIViewController *)self.nextResponder;
- }
- else if ([self.superview.nextResponder isKindOfClass:[UIViewController class]])
- {
- // This map view is an immediate child of a view controller’s content view.
- viewController = (UIViewController *)self.superview.nextResponder;
- }
- return viewController;
-}
-
-- (void)setAutomaticallyAdjustsContentInset:(BOOL)automaticallyAdjustsContentInset {
- MGLLogDebug(@"Setting automaticallyAdjustsContentInset: %@", MGLStringFromBOOL(automaticallyAdjustsContentInset));
- _automaticallyAdjustContentInsetHolder = [NSNumber numberWithBool:automaticallyAdjustsContentInset];
-}
-
-- (BOOL)automaticallyAdjustsContentInset {
- return _automaticallyAdjustContentInsetHolder.boolValue;
-}
-
-- (void)setContentInset:(UIEdgeInsets)contentInset
-{
- [self setContentInset:contentInset animated:NO completionHandler:nil];
-}
-
-- (void)setContentInset:(UIEdgeInsets)contentInset animated:(BOOL)animated
-{
- [self setContentInset:contentInset animated:animated completionHandler:nil];
-}
-
-- (void)setContentInset:(UIEdgeInsets)contentInset animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Setting contentInset: %@ animated:", NSStringFromUIEdgeInsets(contentInset), MGLStringFromBOOL(animated));
- if (UIEdgeInsetsEqualToEdgeInsets(contentInset, self.contentInset))
- {
- if (completion) {
- completion();
- }
- return;
- }
-
- if (self.userTrackingMode == MGLUserTrackingModeNone)
- {
- // Don’t call -setCenterCoordinate:, which resets the user tracking mode.
- [self _setCenterCoordinate:self.centerCoordinate edgePadding:contentInset zoomLevel:self.zoomLevel direction:self.direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion];
- _contentInset = contentInset;
- }
- else
- {
- _contentInset = contentInset;
- [self didUpdateLocationWithUserTrackingAnimated:animated completionHandler:completion];
- }
-
- // Compass, logo and attribution button constraints needs to be updated.z
- [self installConstraints];
-}
-
-/// Returns the frame of inset content within the map view.
-- (CGRect)contentFrame
-{
- return UIEdgeInsetsInsetRect(self.bounds, self.contentInset);
-}
-
-/// Returns the center point of the inset content within the map view.
-- (CGPoint)contentCenter
-{
- CGRect contentFrame = self.contentFrame;
- return CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame));
-}
-
-#pragma mark - Pending completion blocks
-
-- (void)processPendingBlocks
-{
- NSArray *blocks = self.pendingCompletionBlocks;
- self.pendingCompletionBlocks = [NSMutableArray array];
-
- for (dispatch_block_t block in blocks)
- {
- block();
- }
-}
-
-- (BOOL)scheduleTransitionCompletion:(dispatch_block_t)block
-{
- // Only add a block if the display link (that calls processPendingBlocks) is
- // running, otherwise fall back to calling immediately.
- if (_displayLink && !_displayLink.isPaused)
- {
- [self willChangeValueForKey:@"pendingCompletionBlocks"];
- [self.pendingCompletionBlocks addObject:block];
- [self didChangeValueForKey:@"pendingCompletionBlocks"];
- return YES;
- }
-
- return NO;
-}
-
-#pragma mark - Life Cycle -
-
-- (void)updateFromDisplayLink:(CADisplayLink *)displayLink
-{
- MGLAssertIsMainThread();
-
- // Not "visible" - this isn't a full definition of visibility, but if
- // the map view doesn't have a window then it *cannot* be visible.
- if (!self.window) {
- return;
- }
-
- // Mismatched display link
- if (displayLink && displayLink != _displayLink) {
- return;
- }
-
- // Check to ensure rendering doesn't occur in the background
- if (([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) &&
- ![self supportsBackgroundRendering])
- {
- return;
- }
-
- if (_needsDisplayRefresh || (self.pendingCompletionBlocks.count > 0))
- {
- _needsDisplayRefresh = NO;
-
- // Update UIKit elements, prior to rendering
- [self updateUserLocationAnnotationView];
- [self updateAnnotationViews];
- [self updateCalloutView];
-
- // Call any pending completion blocks. This is primarily to ensure
- // that annotations are in the expected position after core rendering
- // and map update.
- //
- // TODO: Consider using this same mechanism for delegate callbacks.
- [self processPendingBlocks];
-
- _mbglView->display();
- }
-
- if (self.experimental_enableFrameRateMeasurement)
- {
- CFTimeInterval now = CACurrentMediaTime();
-
- self.frameTime = now - _displayLink.timestamp;
- _frameDurations += self.frameTime;
-
- _frameCount++;
-
- CFTimeInterval elapsed = now - _frameCounterStartTime;
-
- if (elapsed >= 1.0) {
- self.averageFrameRate = _frameCount / elapsed;
- self.averageFrameTime = (_frameDurations / _frameCount) * 1000;
-
- _frameCount = 0;
- _frameDurations = 0;
- _frameCounterStartTime = now;
- }
- }
-}
-
-- (void)setNeedsRerender
-{
- MGLAssertIsMainThread();
-
- _needsDisplayRefresh = YES;
-}
-
-- (void)willTerminate
-{
- MGLAssertIsMainThread();
-
- if ( ! self.dormant)
- {
- [self validateDisplayLink];
- self.dormant = YES;
- _mbglView->deleteView();
- }
-
- [self destroyCoreObjects];
-}
-
-- (void)validateDisplayLink
-{
- BOOL isVisible = self.superview && self.window;
- if (isVisible && ! _displayLink)
- {
- if (_mbglMap && self.mbglMap.getMapOptions().constrainMode() == mbgl::ConstrainMode::None)
- {
- self.mbglMap.setConstrainMode(mbgl::ConstrainMode::HeightOnly);
- }
-
- _displayLink = [self.window.screen displayLinkWithTarget:self selector:@selector(updateFromDisplayLink:)];
- [self updateDisplayLinkPreferredFramesPerSecond];
- [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
- _needsDisplayRefresh = YES;
- [self updateFromDisplayLink:_displayLink];
- }
- else if ( ! isVisible && _displayLink)
- {
- [_displayLink invalidate];
- _displayLink = nil;
- [self processPendingBlocks];
- }
-}
-
-- (void)updateDisplayLinkPreferredFramesPerSecond
-{
- if (!_displayLink)
- {
- return;
- }
-
- NSInteger newFrameRate;
- if (_preferredFramesPerSecond == MGLMapViewPreferredFramesPerSecondDefault)
- {
- // On legacy devices that cannot maintain a reasonable frame rate, set
- // a lower limit to avoid jank.
- newFrameRate = UIDevice.currentDevice.mgl_isLegacyDevice ? MGLMapViewPreferredFramesPerSecondLowPower : MGLMapViewPreferredFramesPerSecondMaximum;
- }
- else
- {
- newFrameRate = _preferredFramesPerSecond;
- }
-
- if (@available(iOS 10.0, *))
- {
- _displayLink.preferredFramesPerSecond = newFrameRate;
- }
- else
- {
- // CADisplayLink.frameInterval does not support more than 60 FPS (and
- // no device that supports >60 FPS ever supported iOS 9).
- NSInteger maximumFrameRate = 60;
-
- // `0` is an alias for maximum frame rate.
- newFrameRate = newFrameRate ?: maximumFrameRate;
-
- _displayLink.frameInterval = maximumFrameRate / MIN(newFrameRate, maximumFrameRate);
- }
-}
-
-- (void)setPreferredFramesPerSecond:(MGLMapViewPreferredFramesPerSecond)preferredFramesPerSecond
-{
- MGLLogDebug(@"Setting preferredFramesPerSecond: %ld", preferredFramesPerSecond);
- if (_preferredFramesPerSecond == preferredFramesPerSecond)
- {
- return;
- }
-
- _preferredFramesPerSecond = preferredFramesPerSecond;
- [self updateDisplayLinkPreferredFramesPerSecond];
-}
-
-- (void)updatePresentsWithTransaction
-{
- BOOL hasEnoughViewAnnotations = (self.annotationContainerView.annotationViews.count > MGLPresentsWithTransactionAnnotationCount);
- BOOL hasAnAnchoredCallout = [self hasAnAnchoredAnnotationCalloutView];
-
- _enablePresentsWithTransaction = (hasEnoughViewAnnotations || hasAnAnchoredCallout);
-
- // If the map is visible, change the layer property too
- if (self.window) {
- _mbglView->setPresentsWithTransaction(_enablePresentsWithTransaction);
- }
-}
-
-- (void)willMoveToWindow:(UIWindow *)newWindow {
- [super willMoveToWindow:newWindow];
- [self refreshSupportedInterfaceOrientationsWithWindow:newWindow];
-
- if (!newWindow)
- {
- // See https://github.com/mapbox/mapbox-gl-native/issues/14232
- // In iOS 12.2, CAEAGLLayer.presentsWithTransaction can cause dramatic
- // slow down. The exact cause of this is unknown, but this work around
- // appears to lessen the effects.
- _mbglView->setPresentsWithTransaction(NO);
-
- // Moved from didMoveToWindow
- [self validateDisplayLink];
- }
-}
-
-- (void)didMoveToWindow
-{
- [super didMoveToWindow];
-
- if (self.window)
- {
- // See above comment
- _mbglView->setPresentsWithTransaction(self.enablePresentsWithTransaction);
-
- [self validateDisplayLink];
- }
-}
-
-- (void)didMoveToSuperview
-{
- [self validateDisplayLink];
- if (self.superview)
- {
- [self installConstraints];
- }
- [super didMoveToSuperview];
-}
-
-- (void)refreshSupportedInterfaceOrientationsWithWindow:(UIWindow *)window {
-
- // "The system intersects the view controller'€™s supported orientations with
- // the app's supported orientations (as determined by the Info.plist file or
- // the app delegate's application:supportedInterfaceOrientationsForWindow:
- // method) and the device's supported orientations to determine whether to rotate.
-
- UIApplication *application = [UIApplication sharedApplication];
-
- if (window && [application.delegate respondsToSelector:@selector(application:supportedInterfaceOrientationsForWindow:)]) {
- self.applicationSupportedInterfaceOrientations = [application.delegate application:application supportedInterfaceOrientationsForWindow:window];
- return;
- }
-
- // If no delegate method, check the application's plist.
- static UIInterfaceOrientationMask orientationMask = UIInterfaceOrientationMaskAll;
-
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- // No application delegate
- NSArray *orientations = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UISupportedInterfaceOrientations"];
-
- // Application's info plist provided supported orientations.
- if (orientations.count > 0) {
- orientationMask = 0;
-
- NSDictionary *lookup =
- @{
- @"UIInterfaceOrientationPortrait" : @(UIInterfaceOrientationMaskPortrait),
- @"UIInterfaceOrientationPortraitUpsideDown" : @(UIInterfaceOrientationMaskPortraitUpsideDown),
- @"UIInterfaceOrientationLandscapeLeft" : @(UIInterfaceOrientationMaskLandscapeLeft),
- @"UIInterfaceOrientationLandscapeRight" : @(UIInterfaceOrientationMaskLandscapeRight)
- };
-
- for (NSString *orientation in orientations) {
- UIInterfaceOrientationMask mask = ((NSNumber*)lookup[orientation]).unsignedIntegerValue;
- orientationMask |= mask;
- }
- }
- });
-
- self.applicationSupportedInterfaceOrientations = orientationMask;
-}
-
-- (void)deviceOrientationDidChange:(__unused NSNotification *)notification
-{
- UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
-
- // The docs for `UIViewController.supportedInterfaceOrientations` states:
- //
- // When the user changes the device orientation, the system calls this method
- // on the root view controller or the topmost presented view controller that
- // fills the window. If the view controller supports the new orientation, the
- // window and view controller are rotated to the new orientation. This method
- // is only called if the view controller'€™s shouldAutorotate method returns YES.
- //
- // We want to match similar behaviour. However, it may be preferable to look
- // at the owning view controller (in cases where the map view may be covered
- // by another view.
-
- UIViewController *viewController = [self.window.rootViewController mgl_topMostViewController];
-
- if (![viewController shouldAutorotate]) {
- return;
- }
-
- if ((self.currentOrientation == (UIInterfaceOrientation)deviceOrientation) &&
- (self.currentOrientation != UIInterfaceOrientationUnknown)) {
- return;
- }
-
- // "The system intersects the view controller'€™s supported orientations with
- // the app's supported orientations (as determined by the Info.plist file or
- // the app delegate's application:supportedInterfaceOrientationsForWindow:
- // method) and the device's supported orientations to determine whether to rotate.
-
- UIInterfaceOrientationMask supportedOrientations = viewController.supportedInterfaceOrientations;
- supportedOrientations &= self.applicationSupportedInterfaceOrientations;
-
- // Interface orientations are defined by device orientations
- UIInterfaceOrientationMask interfaceOrientation = 1 << deviceOrientation;
- UIInterfaceOrientationMask validOrientation = interfaceOrientation & UIInterfaceOrientationMaskAll;
-
- if (!(validOrientation & supportedOrientations)) {
- return;
- }
-
- self.currentOrientation = (UIInterfaceOrientation)deviceOrientation;
-
- // Q. Do we need to re-layout if we're just going from Portrait -> Portrait
- // Upside Down (or from Left to Right)?
- [self setNeedsLayout];
-}
-
-#pragma mark - Application lifecycle
-- (void)willResignActive:(NSNotification *)notification
-{
- if ([self supportsBackgroundRendering])
- {
- return;
- }
-
- self.lastSnapshotImage = _mbglView->snapshot();
-
- // For OpenGL this calls glFinish as recommended in
- // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/ImplementingaMultitasking-awareOpenGLESApplication/ImplementingaMultitasking-awareOpenGLESApplication.html#//apple_ref/doc/uid/TP40008793-CH5-SW1
- // reduceMemoryUse(), calls performCleanup(), which calls glFinish
- if (_rendererFrontend)
- {
- _rendererFrontend->reduceMemoryUse();
- }
-}
-
-- (void)didEnterBackground:(NSNotification *)notification
-{
- [self pauseRendering:notification];
-}
-
-- (void)willEnterForeground:(NSNotification *)notification
-{
- // Do nothing, currently if resumeRendering is called here it's a no-op.
-}
-
-- (void)didBecomeActive:(NSNotification *)notification
-{
- [self resumeRendering:notification];
- self.lastSnapshotImage = nil;
-}
-
-#pragma mark - GL / display link wake/sleep
-
-- (EAGLContext *)context {
- return _mbglView->getEAGLContext();
-}
-
-- (BOOL)supportsBackgroundRendering
-{
- // If this view targets an external display, such as AirPlay or CarPlay, we
- // can safely continue to render OpenGL content without tripping
- // gpus_ReturnNotPermittedKillClient in libGPUSupportMercury, because the
- // external connection keeps the application from truly receding to the
- // background.
- return (self.window.screen != [UIScreen mainScreen]);
-}
-
-- (void)pauseRendering:(__unused NSNotification *)notification
-{
- // If this view targets an external display, such as AirPlay or CarPlay, we
- // can safely continue to render OpenGL content without tripping
- // gpus_ReturnNotPermittedKillClient in libGPUSupportMercury, because the
- // external connection keeps the application from truly receding to the
- // background.
- if ([self supportsBackgroundRendering])
- {
- return;
- }
-
- MGLLogInfo(@"Entering background.");
- MGLAssertIsMainThread();
-
- // Ideally we would wait until we actually received a memory warning but the bulk of the memory
- // we have to release is tied up in GL buffers that we can't touch once we're in the background.
- // Compromise position: release everything but currently rendering tiles
- // A possible improvement would be to store a copy of the GL buffers that we could use to rapidly
- // restart, but that we could also discard in response to a memory warning.
- if (_rendererFrontend)
- {
- _rendererFrontend->reduceMemoryUse();
- }
-
- if ( ! self.dormant)
- {
- self.dormant = YES;
-
- [self validateLocationServices];
-
- [MGLMapboxEvents flush];
-
- _displayLink.paused = YES;
- [self processPendingBlocks];
-
- if ( ! self.glSnapshotView)
- {
- self.glSnapshotView = [[UIImageView alloc] initWithFrame: _mbglView->getView().frame];
- self.glSnapshotView.autoresizingMask = _mbglView->getView().autoresizingMask;
- self.glSnapshotView.contentMode = UIViewContentModeCenter;
- [self insertSubview:self.glSnapshotView aboveSubview:_mbglView->getView()];
- }
-
- self.glSnapshotView.image = self.lastSnapshotImage;
- self.glSnapshotView.hidden = NO;
-
- if (self.debugMask && [self.glSnapshotView.subviews count] == 0)
- {
- UIView *snapshotTint = [[UIView alloc] initWithFrame:self.glSnapshotView.bounds];
- snapshotTint.autoresizingMask = self.glSnapshotView.autoresizingMask;
- snapshotTint.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.25];
- [self.glSnapshotView addSubview:snapshotTint];
- }
-
- _mbglView->deleteView();
- }
-}
-
-- (void)resumeRendering:(__unused NSNotification *)notification
-{
- MGLLogInfo(@"Entering foreground.");
- MGLAssertIsMainThread();
-
- if (self.dormant && [UIApplication sharedApplication].applicationState != UIApplicationStateBackground)
- {
- self.dormant = NO;
-
- _mbglView->createView();
-
- self.glSnapshotView.hidden = YES;
-
- [self.glSnapshotView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
-
- _displayLink.paused = NO;
-
- [self validateLocationServices];
-
- [MGLMapboxEvents pushTurnstileEvent];
- [MGLMapboxEvents pushEvent:MMEEventTypeMapLoad withAttributes:@{}];
- }
-}
-
-- (void)setHidden:(BOOL)hidden
-{
- super.hidden = hidden;
- _displayLink.paused = hidden;
-
- if (hidden)
- {
- [self processPendingBlocks];
- }
-}
-
-- (void)tintColorDidChange
-{
- for (UIView *subview in self.subviews) [self updateTintColorForView:subview];
-}
-
-- (void)updateTintColorForView:(UIView *)view
-{
- // Don't update:
- // - annotation views
- // - attribution button (handled automatically)
- if ([view isEqual:self.annotationContainerView] || [view isEqual:self.attributionButton]) return;
-
- if ([view respondsToSelector:@selector(setTintColor:)]) view.tintColor = self.tintColor;
-
- for (UIView *subview in view.subviews) [self updateTintColorForView:subview];
-}
-
-- (BOOL)canBecomeFirstResponder {
- return YES;
-}
-
-#pragma mark - Gestures -
-
-- (void)touchesBegan:(__unused NSSet<UITouch *> *)touches withEvent:(__unused UIEvent *)event
-{
- if (!self.zoomEnabled && !self.pitchEnabled && !self.rotateEnabled && !self.scrollEnabled)
- {
- return;
- };
-
- self.mbglMap.setGestureInProgress(false);
- if (self.userTrackingState == MGLUserTrackingStateBegan)
- {
- [self setUserTrackingMode:MGLUserTrackingModeNone animated:NO completionHandler:nil];
- }
-
- [self cancelTransitions];
-}
-
-- (void)notifyGestureDidBegin {
- BOOL animated = NO;
-
- [self cameraWillChangeAnimated:animated];
- self.mbglMap.setGestureInProgress(true);
- _changeDelimiterSuppressionDepth++;
-}
-
-- (void)notifyGestureDidEndWithDrift:(BOOL)drift {
- _changeDelimiterSuppressionDepth--;
- MGLAssert(_changeDelimiterSuppressionDepth >= 0,
- @"Unbalanced change delimiter suppression/unsuppression");
- if (_changeDelimiterSuppressionDepth == 0) {
- self.mbglMap.setGestureInProgress(false);
- }
- if ( ! drift)
- {
- BOOL animated = NO;
- [self cameraDidChangeAnimated:animated];
- }
-}
-
-- (BOOL)isSuppressingChangeDelimiters {
- return _changeDelimiterSuppressionDepth > 0;
-}
-
-- (BOOL)_shouldChangeFromCamera:(nonnull MGLMapCamera *)oldCamera toCamera:(nonnull MGLMapCamera *)newCamera
-{
- // Check delegates first
- if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:reason:)])
- {
- return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera reason:self.cameraChangeReasonBitmask];
- }
- else if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)])
- {
- return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera];
- }
- else
- {
- return YES;
- }
-}
-
-- (void)handlePanGesture:(UIPanGestureRecognizer *)pan
-{
- if ( ! self.isScrollEnabled) return;
-
- [self cancelTransitions];
-
- MGLMapCamera *oldCamera = self.camera;
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGesturePan;
-
- if (pan.state == UIGestureRecognizerStateBegan)
- {
- self.userTrackingMode = MGLUserTrackingModeNone;
-
- [self notifyGestureDidBegin];
- }
- else if (pan.state == UIGestureRecognizerStateChanged)
- {
- CGPoint delta = [pan translationInView:pan.view];
-
- MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:delta panGesture:pan];
-
- if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
- {
- self.mbglMap.moveBy({ delta.x, delta.y });
- [pan setTranslation:CGPointZero inView:pan.view];
- }
-
- [self cameraIsChanging];
- }
- else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled)
- {
- CGPoint velocity = [pan velocityInView:pan.view];
- if (self.decelerationRate == MGLMapViewDecelerationRateImmediate || sqrtf(velocity.x * velocity.x + velocity.y * velocity.y) < 100)
- {
- // Not enough velocity to overcome friction
- velocity = CGPointZero;
- }
-
- BOOL drift = ! CGPointEqualToPoint(velocity, CGPointZero);
- if (drift)
- {
- CGPoint offset = CGPointMake(velocity.x * self.decelerationRate / 4, velocity.y * self.decelerationRate / 4);
- MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:offset panGesture:pan];
-
- if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
- {
- self.mbglMap.moveBy({ offset.x, offset.y }, MGLDurationFromTimeInterval(self.decelerationRate));
- }
- }
-
- [self notifyGestureDidEndWithDrift:drift];
- }
-
-}
-
-- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch
-{
- if ( ! self.isZoomEnabled) return;
-
- [self cancelTransitions];
-
- CGPoint centerPoint = [self anchorPointForGesture:pinch];
- MGLMapCamera *oldCamera = self.camera;
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGesturePinch;
-
- if (pinch.state == UIGestureRecognizerStateBegan)
- {
- self.scale = powf(2, [self zoomLevel]);
-
- if (abs(pinch.velocity) > abs(self.rotate.velocity)) {
- self.isZooming = YES;
- }
- [self notifyGestureDidBegin];
- }
- else if (pinch.state == UIGestureRecognizerStateChanged)
- {
- // Zoom limiting happens at the core level.
- CGFloat newScale = self.scale * pinch.scale;
- double newZoom = log2(newScale);
-
- // Calculates the final camera zoom, has no effect within current map camera.
- MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint];
-
- if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
- {
- self.mbglMap.jumpTo(mbgl::CameraOptions()
- .withZoom(newZoom)
- .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
- .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)));
-
- // The gesture recognizer only reports the gesture’s current center
- // point, so use the previous center point to anchor the transition.
- // If the number of touches has changed, the remembered center point is
- // meaningless.
- if (self.userTrackingMode == MGLUserTrackingModeNone && pinch.numberOfTouches == _previousPinchNumberOfTouches)
- {
- self.mbglMap.moveBy({centerPoint.x - _previousPinchCenterPoint.x, centerPoint.y - _previousPinchCenterPoint.y});
- }
- }
- [self cameraIsChanging];
- }
- else if (pinch.state == UIGestureRecognizerStateEnded || pinch.state == UIGestureRecognizerStateCancelled)
- {
- CGFloat velocity = pinch.velocity;
- if (isnan(velocity))
- {
- // UIPinchGestureRecognizer sometimes returns NaN for the velocity
- velocity = 0;
- }
- if (velocity > -0.5 && velocity < 3)
- {
- velocity = 0;
- }
-
- NSTimeInterval duration = (velocity > 0 ? 1 : 0.25) * self.decelerationRate;
-
- CGFloat scale = self.scale * pinch.scale;
- CGFloat newScale = scale;
- if (velocity >= 0)
- {
- newScale += scale * velocity * duration * 0.1;
- }
- else
- {
- newScale += scale / (velocity * duration) * 0.1;
- }
-
- if (newScale <= 0 || log2(newScale) < *self.mbglMap.getBounds().minZoom)
- {
- velocity = 0;
- }
-
- BOOL drift = velocity && duration;
-
- // Calculates the final camera zoom, this has no effect within current map camera.
- double zoom = log2(newScale);
- MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:centerPoint];
-
- if ( ! [self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
- {
- drift = NO;
- }
- else
- {
- if (drift)
- {
- self.mbglMap.easeTo(mbgl::CameraOptions()
- .withZoom(zoom)
- .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
- .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)), MGLDurationFromTimeInterval(duration));
- }
- }
-
- self.isZooming = NO;
- [self notifyGestureDidEndWithDrift:drift];
- [self unrotateIfNeededForGesture];
- }
-
- _previousPinchCenterPoint = centerPoint;
- _previousPinchNumberOfTouches = pinch.numberOfTouches;
-}
-
-- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate
-{
- if ( ! self.isRotateEnabled) return;
-
- [self cancelTransitions];
-
- CGPoint centerPoint = [self anchorPointForGesture:rotate];
- MGLMapCamera *oldCamera = self.camera;
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureRotate;
-
- if ([[NSUserDefaults standardUserDefaults] objectForKey:MGLRotationThresholdWhileZoomingKey]) {
- self.rotationThresholdWhileZooming = [[[NSUserDefaults standardUserDefaults] objectForKey:MGLRotationThresholdWhileZoomingKey] floatValue];
- }
- // Check whether a zoom triggered by a pinch gesture is occurring and if the rotation threshold has been met.
- if (MGLDegreesFromRadians(self.rotationBeforeThresholdMet) < self.rotationThresholdWhileZooming && self.isZooming && !self.isRotating) {
- self.rotationBeforeThresholdMet += fabs(rotate.rotation);
- rotate.rotation = 0;
- return;
- }
-
- if (rotate.state == UIGestureRecognizerStateBegan || ! self.isRotating)
- {
- self.angle = MGLRadiansFromDegrees(*self.mbglMap.getCameraOptions().bearing) * -1;
-
- self.isRotating = YES;
- if (self.userTrackingMode != MGLUserTrackingModeNone)
- {
- self.userTrackingMode = MGLUserTrackingModeFollow;
- }
-
- self.shouldTriggerHapticFeedbackForCompass = NO;
- [self notifyGestureDidBegin];
- }
- if (rotate.state == UIGestureRecognizerStateChanged)
- {
- CGFloat newDegrees = MGLDegreesFromRadians(self.angle + rotate.rotation) * -1;
-
- // constrain to +/-30 degrees when merely rotating like Apple does
- //
- if ( ! self.isRotationAllowed && std::abs(self.pinch.scale) < 10)
- {
- newDegrees = fminf(newDegrees, 30);
- newDegrees = fmaxf(newDegrees, -30);
- }
-
- MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint];
-
- if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
- {
- self.mbglMap.jumpTo(mbgl::CameraOptions()
- .withBearing(newDegrees)
- .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y})
- .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)));
- }
-
- [self cameraIsChanging];
-
- // Trigger a light haptic feedback event when the user rotates to due north.
- if (@available(iOS 10.0, *))
- {
- if (self.isHapticFeedbackEnabled && fabs(newDegrees) <= 1 && self.shouldTriggerHapticFeedbackForCompass)
- {
- UIImpactFeedbackGenerator *hapticFeedback = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
- [hapticFeedback impactOccurred];
-
- self.shouldTriggerHapticFeedbackForCompass = NO;
- }
- else if (fabs(newDegrees) > 1)
- {
- self.shouldTriggerHapticFeedbackForCompass = YES;
- }
- }
- }
- else if ((rotate.state == UIGestureRecognizerStateEnded || rotate.state == UIGestureRecognizerStateCancelled))
- {
- self.rotationBeforeThresholdMet = 0;
- if (! self.isRotating) { return; }
- self.isRotating = NO;
-
- CGFloat velocity = rotate.velocity;
- CGFloat decelerationRate = self.decelerationRate;
- if (decelerationRate != MGLMapViewDecelerationRateImmediate && fabs(velocity) > 3)
- {
- CGFloat radians = self.angle + rotate.rotation;
- CGFloat newRadians = radians + velocity * decelerationRate * 0.1;
- CGFloat newDegrees = MGLDegreesFromRadians(newRadians) * -1;
-
- MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint];
-
- if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
- {
- self.mbglMap.easeTo(mbgl::CameraOptions()
- .withBearing(newDegrees)
- .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
- .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)),
- MGLDurationFromTimeInterval(decelerationRate));
-
- [self notifyGestureDidEndWithDrift:YES];
- __weak MGLMapView *weakSelf = self;
-
- [self animateWithDelay:decelerationRate animations:^
- {
- [weakSelf unrotateIfNeededForGesture];
- }];
- }
- }
- else
- {
- [self notifyGestureDidEndWithDrift:NO];
- [self unrotateIfNeededForGesture];
- }
- }
-}
-
-- (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap
-{
- if (singleTap.state != UIGestureRecognizerStateRecognized) return;
-
- if (self.mapViewProxyAccessibilityElement.accessibilityElementIsFocused)
- {
- id nextElement;
- if (_userLocationAnnotationIsSelected)
- {
- nextElement = self.userLocationAnnotationView;
- }
- else
- {
- if (_selectedAnnotationTag != MGLAnnotationTagNotFound) {
- nextElement = _annotationContextsByAnnotationTag.at(_selectedAnnotationTag).accessibilityElement;
- }
- }
- [self deselectAnnotation:self.selectedAnnotation animated:YES];
- UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nextElement);
-
- return;
- }
-
- id<MGLAnnotation> annotation = [self annotationForGestureRecognizer:singleTap persistingResults:YES];
- if (annotation)
- {
- CGPoint calloutPoint = [singleTap locationInView:self];
- CGRect positionRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:calloutPoint];
- [self selectAnnotation:annotation moveIntoView:YES animateSelection:YES calloutPositioningRect:positionRect completionHandler:nil];
- }
- else if (self.selectedAnnotation)
- {
- [self deselectAnnotation:self.selectedAnnotation animated:YES];
- }
-}
-
-/**
- Returns the annotation that would be selected by a tap gesture recognizer.
-
- This is used when a gesture is recognized, and to check if the gesture should be recognized.
-
- @param singleTap An in progress tap gesture recognizer.
- @param persist True to remember the cycleable set of annotations. @see annotationTagAtPoint:persistingResults
- */
-- (nullable id <MGLAnnotation>)annotationForGestureRecognizer:(UITapGestureRecognizer*)singleTap persistingResults:(BOOL)persist
-{
- CGPoint tapPoint = [singleTap locationInView:self];
-
- if (self.userLocationVisible)
- {
- CGPoint tapPointForUserLocation;
- if (self.userLocationAnnotationView.hitTestLayer == self.userLocationAnnotationView.layer.presentationLayer)
- {
- tapPointForUserLocation = tapPoint;
- }
- else
- {
- // Get the tap point within the custom hit test layer.
- tapPointForUserLocation = [singleTap locationInView:self.userLocationAnnotationView];
- }
-
- CALayer *hitLayer = [self.userLocationAnnotationView.hitTestLayer hitTest:tapPointForUserLocation];
-
- if (hitLayer)
- {
- if ( ! _userLocationAnnotationIsSelected)
- {
- return self.userLocation;
- }
- return nil;
- }
- }
-
- MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:tapPoint persistingResults:persist];
- if (hitAnnotationTag != MGLAnnotationTagNotFound)
- {
- if (hitAnnotationTag != _selectedAnnotationTag)
- {
- id <MGLAnnotation> annotation = [self annotationWithTag:hitAnnotationTag];
- MGLAssert(annotation, @"Cannot select nonexistent annotation with tag %llu", hitAnnotationTag);
- return annotation;
- }
- }
-
- return nil;
-}
-
-- (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap
-{
- if (doubleTap.state != UIGestureRecognizerStateRecognized) return;
-
- if ( ! self.isZoomEnabled) return;
-
- [self cancelTransitions];
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureZoomIn;
-
- MGLMapCamera *oldCamera = self.camera;
-
- double newZoom = round(self.zoomLevel) + 1.0;
-
- CGPoint gesturePoint = [self anchorPointForGesture:doubleTap];
-
- MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint];
-
- if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
- {
- mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y);
- self.mbglMap.easeTo(mbgl::CameraOptions()
- .withZoom(newZoom)
- .withAnchor(center)
- .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)), MGLDurationFromTimeInterval(MGLAnimationDuration));
-
- __weak MGLMapView *weakSelf = self;
-
- [self animateWithDelay:MGLAnimationDuration animations:^
- {
- [weakSelf unrotateIfNeededForGesture];
- }];
- }
- else
- {
- [self unrotateIfNeededForGesture];
- }
-}
-
-- (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap
-{
- if (twoFingerTap.state != UIGestureRecognizerStateRecognized) return;
-
- if ( ! self.isZoomEnabled) return;
-
- if ([self zoomLevel] == *self.mbglMap.getBounds().minZoom) return;
-
- [self cancelTransitions];
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureZoomOut;
-
- MGLMapCamera *oldCamera = self.camera;
-
- double newZoom = round(self.zoomLevel) - 1.0;
-
- CGPoint gesturePoint = [self anchorPointForGesture:twoFingerTap];
-
- MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint];
-
- if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
- {
- mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y);
- self.mbglMap.easeTo(mbgl::CameraOptions()
- .withZoom(newZoom)
- .withAnchor(center)
- .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)), MGLDurationFromTimeInterval(MGLAnimationDuration));
-
- __weak MGLMapView *weakSelf = self;
-
- [self animateWithDelay:MGLAnimationDuration animations:^
- {
- [weakSelf unrotateIfNeededForGesture];
- }];
- }
-}
-
-- (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom
-{
- if ( ! self.isZoomEnabled) return;
-
- [self cancelTransitions];
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureOneFingerZoom;
-
- if (quickZoom.state == UIGestureRecognizerStateBegan)
- {
- self.scale = powf(2, [self zoomLevel]);
-
- self.quickZoomStart = [quickZoom locationInView:quickZoom.view].y;
-
- [self notifyGestureDidBegin];
- }
- else if (quickZoom.state == UIGestureRecognizerStateChanged)
- {
- CGFloat distance = [quickZoom locationInView:quickZoom.view].y - self.quickZoomStart;
-
- CGFloat newZoom = MAX(log2f(self.scale) + (distance / 75), *self.mbglMap.getBounds().minZoom);
-
- if ([self zoomLevel] == newZoom) return;
-
- CGPoint centerPoint = [self anchorPointForGesture:quickZoom];
-
- MGLMapCamera *oldCamera = self.camera;
- MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint];
-
- if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
- {
- self.mbglMap.jumpTo(mbgl::CameraOptions()
- .withZoom(newZoom)
- .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
- .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)));
- }
-
- [self cameraIsChanging];
- }
- else if (quickZoom.state == UIGestureRecognizerStateEnded || quickZoom.state == UIGestureRecognizerStateCancelled)
- {
- [self notifyGestureDidEndWithDrift:NO];
- [self unrotateIfNeededForGesture];
- }
-}
-
-- (void)handleTwoFingerDragGesture:(UIPanGestureRecognizer *)twoFingerDrag
-{
- if ( ! self.isPitchEnabled) return;
-
- [self cancelTransitions];
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureTilt;
- static CGFloat initialPitch;
-
- if (twoFingerDrag.state == UIGestureRecognizerStateBegan)
- {
- CGPoint midPoint = [twoFingerDrag translationInView:twoFingerDrag.view];
- // In the following if and for the first execution middlePoint
- // will be equal to dragGestureMiddlePoint and the resulting
- // gestureSlopeAngle will be 0º causing a small delay,
- // initializing dragGestureMiddlePoint with the current midPoint
- // but substracting one point from 'y' forces an initial 90º angle
- // making the gesture avoid the delay
- self.dragGestureMiddlePoint = CGPointMake(midPoint.x, midPoint.y-1);
- initialPitch = *self.mbglMap.getCameraOptions().pitch;
- [self notifyGestureDidBegin];
- }
-
- if (twoFingerDrag.state == UIGestureRecognizerStateBegan || twoFingerDrag.state == UIGestureRecognizerStateChanged)
- {
- if (twoFingerDrag.numberOfTouches != 2)
- {
- twoFingerDrag.state = UIGestureRecognizerStateEnded;
- return;
- }
-
- CGPoint leftTouchPoint = [twoFingerDrag locationOfTouch:0 inView:twoFingerDrag.view];
- CGPoint rightTouchPoint = [twoFingerDrag locationOfTouch:1 inView:twoFingerDrag.view];
- CLLocationDegrees fingerSlopeAngle = [self angleBetweenPoints:leftTouchPoint endPoint:rightTouchPoint];
-
- CGPoint middlePoint = [twoFingerDrag translationInView:twoFingerDrag.view];
-
- CLLocationDegrees gestureSlopeAngle = [self angleBetweenPoints:self.dragGestureMiddlePoint endPoint:middlePoint];
- self.dragGestureMiddlePoint = middlePoint;
- if (fabs(fingerSlopeAngle) < MGLHorizontalTiltToleranceDegrees && fabs(gestureSlopeAngle) > 60.0 ) {
-
- CGFloat gestureDistance = middlePoint.y;
- CGFloat slowdown = 2.0;
-
- CGFloat pitchNew = initialPitch - (gestureDistance / slowdown);
-
- CGPoint centerPoint = [self anchorPointForGesture:twoFingerDrag];
-
- MGLMapCamera *oldCamera = self.camera;
- MGLMapCamera *toCamera = [self cameraByTiltingToPitch:pitchNew];
-
- if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
- {
- self.mbglMap.jumpTo(mbgl::CameraOptions()
- .withPitch(pitchNew)
- .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
- .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)));
- }
-
- [self cameraIsChanging];
-
- }
-
-
- }
- else if (twoFingerDrag.state == UIGestureRecognizerStateEnded || twoFingerDrag.state == UIGestureRecognizerStateCancelled)
- {
- [self notifyGestureDidEndWithDrift:NO];
- [self unrotateIfNeededForGesture];
- self.dragGestureMiddlePoint = CGPointZero;
- }
-
-}
-
-- (MGLMapCamera *)cameraByPanningWithTranslation:(CGPoint)endPoint panGesture:(UIPanGestureRecognizer *)pan
-{
- MGLMapCamera *panCamera = [self.camera copy];
-
- CGPoint centerPoint = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
- CGPoint endCameraPoint = CGPointMake(centerPoint.x - endPoint.x, centerPoint.y - endPoint.y);
- CLLocationCoordinate2D panCoordinate = [self convertPoint:endCameraPoint toCoordinateFromView:pan.view];
-
- panCamera.centerCoordinate = panCoordinate;
-
- return panCamera;
-}
-
-- (MGLMapCamera *)cameraByZoomingToZoomLevel:(double)zoom aroundAnchorPoint:(CGPoint)anchorPoint
-{
- mbgl::ScreenCoordinate anchor = mbgl::ScreenCoordinate { anchorPoint.x, anchorPoint.y };
- mbgl::EdgeInsets padding = mbgl::EdgeInsets(anchor.y, anchor.x, self.size.height - anchor.y, self.size.width - anchor.x);
- mbgl::CameraOptions currentCameraOptions = self.mbglMap.getCameraOptions(padding);
-
- currentCameraOptions.zoom = mbgl::util::clamp(zoom, self.minimumZoomLevel, self.maximumZoomLevel);
- currentCameraOptions.anchor = anchor;
- MGLCoordinateBounds bounds = MGLCoordinateBoundsFromLatLngBounds(self.mbglMap.latLngBoundsForCamera(currentCameraOptions));
-
- return [self cameraThatFitsCoordinateBounds:bounds];
-}
-
-- (MGLMapCamera *)cameraByRotatingToDirection:(CLLocationDirection)degrees aroundAnchorPoint:(CGPoint)anchorPoint
-{
- mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
- mbgl::CameraOptions currentCameraOptions = self.mbglMap.getCameraOptions(padding);
-
- MGLMapCamera *camera;
-
- mbgl::ScreenCoordinate anchor = mbgl::ScreenCoordinate { anchorPoint.x, anchorPoint.y };
- currentCameraOptions.bearing = degrees;
- currentCameraOptions.anchor = anchor;
- camera = [self cameraForCameraOptions:currentCameraOptions];
-
- return camera;
-}
-
-- (MGLMapCamera *)cameraByTiltingToPitch:(CGFloat)pitch
-{
- mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
- mbgl::CameraOptions currentCameraOptions = self.mbglMap.getCameraOptions(padding);
-
- MGLMapCamera *camera;
-
- currentCameraOptions.pitch = pitch;
- camera = [self cameraForCameraOptions:currentCameraOptions];
-
- return camera;
-}
-
-- (CGPoint)anchorPointForGesture:(UIGestureRecognizer *)gesture {
- if (self.userTrackingMode != MGLUserTrackingModeNone)
- {
- return self.userLocationAnnotationViewCenter;
- }
-
- // Special case for two-finger drag and quickzoom
- if ([gesture isKindOfClass:[UIPanGestureRecognizer class]] || [gesture isKindOfClass:[UILongPressGestureRecognizer class]])
- {
- return self.contentCenter;
- }
-
- return [gesture locationInView:gesture.view];
-}
-
-- (void)handleCalloutAccessoryTapGesture:(UITapGestureRecognizer *)tap
-{
- if ([self.delegate respondsToSelector:@selector(mapView:annotation:calloutAccessoryControlTapped:)])
- {
- MGLAssert([tap.view isKindOfClass:[UIControl class]], @"Tapped view %@ is not a UIControl", tap.view);
- id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation;
- MGLAssert(selectedAnnotation, @"Selected annotation should not be nil.");
- [self.delegate mapView:self annotation:selectedAnnotation
- calloutAccessoryControlTapped:(UIControl *)tap.view];
- }
-}
-
-- (BOOL)calloutViewShouldHighlight:(__unused MGLCompactCalloutView *)calloutView
-{
- return [self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)];
-}
-
-- (void)calloutViewClicked:(__unused MGLSMCalloutView *)calloutView
-{
- if ([self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)])
- {
- id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation;
- MGLAssert(selectedAnnotation, @"Selected annotation should not be nil.");
- [self.delegate mapView:self tapOnCalloutForAnnotation:selectedAnnotation];
- }
-}
-
-- (void)calloutViewTapped:(__unused MGLCompactCalloutView *)calloutView
-{
- if ([self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)])
- {
- id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation;
- MGLAssert(selectedAnnotation, @"Selected annotation should not be nil.");
- [self.delegate mapView:self tapOnCalloutForAnnotation:selectedAnnotation];
- }
-}
-
-- (void)calloutViewDidAppear:(UIView<MGLCalloutView> *)calloutView
-{
- UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
- UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, calloutView);
-
- [self updatePresentsWithTransaction];
-
- // TODO: Add sibling disappear method
-}
-
-- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
-{
- if (gestureRecognizer == _twoFingerDrag)
- {
- UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer;
-
- if (panGesture.minimumNumberOfTouches == 2)
- {
- CGPoint leftTouchPoint = [panGesture locationOfTouch:0 inView:panGesture.view];
- CGPoint rightTouchPoint = [panGesture locationOfTouch:1 inView:panGesture.view];
-
- CLLocationDegrees degrees = [self angleBetweenPoints:leftTouchPoint endPoint:rightTouchPoint];
- if (fabs(degrees) > MGLHorizontalTiltToleranceDegrees) {
- return NO;
- }
- }
- }
- else if (gestureRecognizer == _singleTapGestureRecognizer)
- {
- // Gesture will be recognized if it could deselect an annotation
- if(!self.selectedAnnotation)
- {
- id<MGLAnnotation> annotation = [self annotationForGestureRecognizer:(UITapGestureRecognizer*)gestureRecognizer persistingResults:NO];
- if (!annotation) {
- return NO;
- }
- }
- }
- return YES;
-}
-
-- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
-{
- NSArray *validSimultaneousGestures = @[ self.pan, self.pinch, self.rotate ];
- return ([validSimultaneousGestures containsObject:gestureRecognizer] && [validSimultaneousGestures containsObject:otherGestureRecognizer]);
-}
-
-- (CLLocationDegrees)angleBetweenPoints:(CGPoint)originPoint endPoint:(CGPoint)endPoint
-{
- if (originPoint.x > endPoint.x) {
- CGPoint swap = originPoint;
- originPoint = endPoint;
- endPoint = swap;
- }
-
- CGFloat x = (endPoint.x - originPoint.x);
- CGFloat y = (endPoint.y - originPoint.y);
-
- CGFloat angleInRadians = atan2(y, x);
- CLLocationDegrees angleInDegrees = MGLDegreesFromRadians(angleInRadians);
-
- return angleInDegrees;
-}
-
-#pragma mark - Attribution -
-
-- (void)showAttribution:(id)sender
-{
- BOOL shouldShowVersion = [sender isKindOfClass:[UILongPressGestureRecognizer class]];
- if (shouldShowVersion)
- {
- UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
- if (longPress.state != UIGestureRecognizerStateBegan)
- {
- return;
- }
- }
-
- NSString *actionSheetTitle = NSLocalizedStringWithDefaultValue(@"SDK_NAME", nil, nil, @"Mapbox Maps SDK for iOS", @"Action sheet title");
- UIAlertController *attributionController = [UIAlertController alertControllerWithTitle:actionSheetTitle
- message:nil
- preferredStyle:UIAlertControllerStyleActionSheet];
-
- if (shouldShowVersion)
- {
- attributionController.title = [actionSheetTitle stringByAppendingFormat:@" %@", [NSBundle mgl_frameworkInfoDictionary][@"MGLSemanticVersionString"]];
- }
-
- NSArray *attributionInfos = [self.style attributionInfosWithFontSize:[UIFont buttonFontSize]
- linkColor:nil];
- for (MGLAttributionInfo *info in attributionInfos)
- {
- UIAlertAction *action = [UIAlertAction actionWithTitle:[info.title.string mgl_titleCasedStringWithLocale:[NSLocale currentLocale]]
- style:UIAlertActionStyleDefault
- handler:^(UIAlertAction * _Nonnull actionBlock) {
- NSURL *url = info.URL;
- if (url)
- {
- if (info.feedbackLink)
- {
- MGLMapCamera *camera = self.camera;
- url = [info feedbackURLForStyleURL:self.styleURL
- atCenterCoordinate:camera.centerCoordinate
- zoomLevel:self.zoomLevel
- direction:camera.heading
- pitch:camera.pitch];
- }
- [[UIApplication sharedApplication] openURL:url];
- }
- }];
- [attributionController addAction:action];
- }
-
- NSString *telemetryTitle = NSLocalizedStringWithDefaultValue(@"TELEMETRY_NAME", nil, nil, @"Mapbox Telemetry", @"Action in attribution sheet");
- UIAlertAction *telemetryAction = [UIAlertAction actionWithTitle:telemetryTitle
- style:UIAlertActionStyleDefault
- handler:^(UIAlertAction * _Nonnull action) {
- [self presentTelemetryAlertController];
- }];
- [attributionController addAction:telemetryAction];
-
- NSString *cancelTitle = NSLocalizedStringWithDefaultValue(@"CANCEL", nil, nil, @"Cancel", @"Title of button for dismissing attribution action sheet");
- UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelTitle
- style:UIAlertActionStyleCancel
- handler:NULL];
- [attributionController addAction:cancelAction];
-
- attributionController.popoverPresentationController.sourceView = self;
- attributionController.popoverPresentationController.sourceRect = self.attributionButton.frame;
-
- UIViewController *viewController = [self.window.rootViewController mgl_topMostViewController];
- [viewController presentViewController:attributionController animated:YES completion:NULL];
- self.attributionController = attributionController;
-}
-
-- (void)presentTelemetryAlertController
-{
- NSString *title = NSLocalizedStringWithDefaultValue(@"TELEMETRY_TITLE", nil, nil, @"Make Mapbox Maps Better", @"Telemetry prompt title");
- NSString *message;
- NSString *participateTitle;
- NSString *declineTitle;
- if ([[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsEnabledKey])
- {
- message = NSLocalizedStringWithDefaultValue(@"TELEMETRY_ENABLED_MSG", nil, nil, @"You are helping to make OpenStreetMap and Mapbox maps better by contributing anonymous usage data.", @"Telemetry prompt message");
- participateTitle = NSLocalizedStringWithDefaultValue(@"TELEMETRY_ENABLED_ON", nil, nil, @"Keep Participating", @"Telemetry prompt button");
- declineTitle = NSLocalizedStringWithDefaultValue(@"TELEMETRY_ENABLED_OFF", nil, nil, @"Stop Participating", @"Telemetry prompt button");
- }
- else
- {
- message = NSLocalizedStringWithDefaultValue(@"TELEMETRY_DISABLED_MSG", nil, nil, @"You can help make OpenStreetMap and Mapbox maps better by contributing anonymous usage data.", @"Telemetry prompt message");
- participateTitle = NSLocalizedStringWithDefaultValue(@"TELEMETRY_DISABLED_ON", nil, nil, @"Participate", @"Telemetry prompt button");
- declineTitle = NSLocalizedStringWithDefaultValue(@"TELEMETRY_DISABLED_OFF", nil, nil, @"Don’t Participate", @"Telemetry prompt button");
- }
-
- UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
- message:message
- preferredStyle:UIAlertControllerStyleAlert];
-
- NSString *moreTitle = NSLocalizedStringWithDefaultValue(@"TELEMETRY_MORE", nil, nil, @"Tell Me More", @"Telemetry prompt button");
- UIAlertAction *moreAction = [UIAlertAction actionWithTitle:moreTitle
- style:UIAlertActionStyleDefault
- handler:^(UIAlertAction * _Nonnull action) {
- [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://www.mapbox.com/telemetry/"]];
- }];
- [alertController addAction:moreAction];
-
- UIAlertAction *declineAction = [UIAlertAction actionWithTitle:declineTitle
- style:UIAlertActionStyleDefault
- handler:^(UIAlertAction * _Nonnull action) {
- [[NSUserDefaults standardUserDefaults] setBool:NO forKey:MGLMapboxMetricsEnabledKey];
- }];
- [alertController addAction:declineAction];
-
- UIAlertAction *participateAction = [UIAlertAction actionWithTitle:participateTitle
- style:UIAlertActionStyleCancel
- handler:^(UIAlertAction * _Nonnull action) {
- [[NSUserDefaults standardUserDefaults] setBool:YES forKey:MGLMapboxMetricsEnabledKey];
- }];
- [alertController addAction:participateAction];
-
- UIViewController *viewController = [self.window.rootViewController mgl_topMostViewController];
- [viewController presentViewController:alertController animated:YES completion:NULL];
-}
-
-#pragma mark - Properties -
-
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
-{
- if ([keyPath isEqualToString:@"hidden"] && object == _attributionButton)
- {
- NSNumber *hiddenNumber = change[NSKeyValueChangeNewKey];
- BOOL attributionButtonWasHidden = [hiddenNumber boolValue];
- if (attributionButtonWasHidden)
- {
- [MGLMapboxEvents ensureMetricsOptoutExists];
- }
- }
- else if ([keyPath isEqualToString:@"coordinate"] && [object conformsToProtocol:@protocol(MGLAnnotation)] && ![object isKindOfClass:[MGLMultiPoint class]])
- {
- id <MGLAnnotation> annotation = object;
- MGLAnnotationTag annotationTag = (MGLAnnotationTag)(NSUInteger)context;
- // We can get here because a subclass registered itself as an observer
- // of the coordinate key path of a non-multipoint annotation but failed
- // to handle the change. This check deters us from treating the
- // subclass’s context as an annotation tag. If the context happens to
- // match a valid annotation tag, the annotation will be unnecessarily
- // but safely updated.
- if (annotation == [self annotationWithTag:annotationTag])
- {
- const mbgl::Point<double> point = MGLPointFromLocationCoordinate2D(annotation.coordinate);
-
- if (annotationTag != MGLAnnotationTagNotFound) {
- MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
- if (annotationContext.annotationView)
- {
- // Redundantly move the associated annotation view outside the scope of the animation-less transaction block in -updateAnnotationViews.
- annotationContext.annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self];
- }
-
- MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
- NSString *symbolName = annotationImage.styleIconIdentifier;
-
- // Update the annotation’s backing geometry to match the annotation model object. Any associated annotation view is also moved by side effect. However, -updateAnnotationViews disables the view’s animation actions, because it can’t distinguish between moves due to the viewport changing and moves due to the annotation’s coordinate changing.
- self.mbglMap.updateAnnotation(annotationTag, mbgl::SymbolAnnotation { point, symbolName.UTF8String });
- [self updateCalloutView];
- }
- }
- }
- else if ([keyPath isEqualToString:@"coordinates"] && [object isKindOfClass:[MGLMultiPoint class]])
- {
- MGLMultiPoint *annotation = object;
- MGLAnnotationTag annotationTag = (MGLAnnotationTag)(NSUInteger)context;
- // We can get here because a subclass registered itself as an observer
- // of the coordinates key path of a multipoint annotation but failed
- // to handle the change. This check deters us from treating the
- // subclass’s context as an annotation tag. If the context happens to
- // match a valid annotation tag, the annotation will be unnecessarily
- // but safely updated.
- if (annotation == [self annotationWithTag:annotationTag])
- {
- // Update the annotation’s backing geometry to match the annotation model object.
- self.mbglMap.updateAnnotation(annotationTag, [annotation annotationObjectWithDelegate:self]);
- [self updateCalloutView];
- }
- }
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingZoomEnabled
-{
- return [NSSet setWithObject:@"allowsZooming"];
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingScrollEnabled
-{
- return [NSSet setWithObject:@"allowsScrolling"];
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingRotateEnabled
-{
- return [NSSet setWithObject:@"allowsRotating"];
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingPitchEnabled
-{
- return [NSSet setWithObject:@"allowsTilting"];
-}
-
-- (MGLMapDebugMaskOptions)debugMask
-{
- if (!_mbglMap)
- {
- NSAssert(self.terminated, @"_mbglMap should only be unavailable during app termination");
- return self.residualDebugMask;
- }
-
- mbgl::MapDebugOptions options = self.mbglMap.getDebug();
- MGLMapDebugMaskOptions mask = 0;
- if (options & mbgl::MapDebugOptions::TileBorders)
- {
- mask |= MGLMapDebugTileBoundariesMask;
- }
- if (options & mbgl::MapDebugOptions::ParseStatus)
- {
- mask |= MGLMapDebugTileInfoMask;
- }
- if (options & mbgl::MapDebugOptions::Timestamps)
- {
- mask |= MGLMapDebugTimestampsMask;
- }
- if (options & mbgl::MapDebugOptions::Collision)
- {
- mask |= MGLMapDebugCollisionBoxesMask;
- }
- if (options & mbgl::MapDebugOptions::Overdraw)
- {
- mask |= MGLMapDebugOverdrawVisualizationMask;
- }
- return mask;
-}
-
-- (void)setDebugMask:(MGLMapDebugMaskOptions)debugMask
-{
- if (!_mbglMap)
- {
- return;
- }
-
- mbgl::MapDebugOptions options = mbgl::MapDebugOptions::NoDebug;
- if (debugMask & MGLMapDebugTileBoundariesMask)
- {
- options |= mbgl::MapDebugOptions::TileBorders;
- }
- if (debugMask & MGLMapDebugTileInfoMask)
- {
- options |= mbgl::MapDebugOptions::ParseStatus;
- }
- if (debugMask & MGLMapDebugTimestampsMask)
- {
- options |= mbgl::MapDebugOptions::Timestamps;
- }
- if (debugMask & MGLMapDebugCollisionBoxesMask)
- {
- options |= mbgl::MapDebugOptions::Collision;
- }
- if (debugMask & MGLMapDebugOverdrawVisualizationMask)
- {
- options |= mbgl::MapDebugOptions::Overdraw;
- }
- self.mbglMap.setDebug(options);
-}
-
-- (void)resetNorth
-{
- MGLLogInfo(@"Resetting the map rotation to a northern heading — a direction of 0 degrees.");
- [self resetNorthAnimated:YES];
-}
-
-- (void)resetNorthAnimated:(BOOL)animated
-{
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonResetNorth;
-
- [self setDirection:0 animated:animated];
-}
-
-- (void)resetPosition
-{
- MGLLogInfo(@"Resetting the map to the current style’s default viewport.");
- auto camera = self.mbglMap.getStyle().getDefaultCamera();
-
- double pitch = camera.pitch ? *camera.pitch : 0.0;
- double bearing = camera.bearing ? *camera.bearing : 0.0;
- double zoom = camera.zoom ? *camera.zoom : 0.0;
- mbgl::LatLng center = camera.center ? *camera.center : mbgl::LatLng();
-
- CLLocationDirection heading = mbgl::util::wrap(bearing, 0., 360.);
- CLLocationDistance altitude = MGLAltitudeForZoomLevel(zoom, pitch, 0, self.frame.size);
- self.camera = [MGLMapCamera cameraLookingAtCenterCoordinate:MGLLocationCoordinate2DFromLatLng(center)
- altitude:altitude
- pitch:pitch
- heading:heading];
-}
-
-- (void)setZoomEnabled:(BOOL)zoomEnabled
-{
- MGLLogDebug(@"Setting zoomEnabled: %@", MGLStringFromBOOL(zoomEnabled));
- _zoomEnabled = zoomEnabled;
- self.pinch.enabled = zoomEnabled;
- self.doubleTap.enabled = zoomEnabled;
- self.quickZoom.enabled = zoomEnabled;
- self.twoFingerTap.enabled = zoomEnabled;
-}
-
-- (void)setScrollEnabled:(BOOL)scrollEnabled
-{
- MGLLogDebug(@"Setting scrollEnabled: %@", MGLStringFromBOOL(scrollEnabled));
- _scrollEnabled = scrollEnabled;
- self.pan.enabled = scrollEnabled;
-}
-
-- (void)setRotateEnabled:(BOOL)rotateEnabled
-{
- MGLLogDebug(@"Setting rotateEnabled: %@", MGLStringFromBOOL(rotateEnabled));
- _rotateEnabled = rotateEnabled;
- self.rotate.enabled = rotateEnabled;
-}
-
-- (void)setPitchEnabled:(BOOL)pitchEnabled
-{
- MGLLogDebug(@"Setting pitchEnabled: %@", MGLStringFromBOOL(pitchEnabled));
- _pitchEnabled = pitchEnabled;
- self.twoFingerDrag.enabled = pitchEnabled;
-}
-
-- (void)setShowsScale:(BOOL)showsScale
-{
- MGLLogDebug(@"Setting showsScale: %@", MGLStringFromBOOL(showsScale));
- _showsScale = showsScale;
- self.scaleBar.hidden = !showsScale;
-
- if (showsScale)
- {
- [self updateScaleBar];
- }
-}
-
-- (void)setPrefetchesTiles:(BOOL)prefetchesTiles
-{
- _mbglMap->setPrefetchZoomDelta(prefetchesTiles ? mbgl::util::DEFAULT_PREFETCH_ZOOM_DELTA : 0);
-}
-
-- (BOOL)prefetchesTiles
-{
- return _mbglMap->getPrefetchZoomDelta() > 0 ? YES : NO;
-}
-
-#pragma mark - Accessibility -
-
-- (NSString *)accessibilityValue
-{
- NSMutableArray *facts = [NSMutableArray array];
-
- double zoomLevel = round(self.zoomLevel + 1);
- [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;
-}
-
-- (NSArray<id <MGLFeature>> *)visiblePlaceFeatures
-{
- if (!_visiblePlaceFeatures)
- {
- NSArray *placeStyleLayerIdentifiers = [self.style.placeStyleLayers valueForKey:@"identifier"];
- _visiblePlaceFeatures = [self visibleFeaturesInRect:self.bounds inStyleLayersWithIdentifiers:[NSSet setWithArray:placeStyleLayerIdentifiers]];
- }
- return _visiblePlaceFeatures;
-}
-
-- (NSArray<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
-{
- CGRect frame = [super accessibilityFrame];
- UIViewController *viewController = self.mgl_viewControllerForLayoutGuides;
- if (viewController)
- {
- CGFloat topInset = viewController.topLayoutGuide.length;
- frame.origin.y += topInset;
- frame.size.height -= topInset + viewController.bottomLayoutGuide.length;
- }
- return frame;
-}
-
-- (UIBezierPath *)accessibilityPath
-{
- UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.accessibilityFrame];
-
- // Exclude any visible annotation callout view.
- if (self.calloutViewForSelectedAnnotation)
- {
- UIBezierPath *calloutViewPath = [UIBezierPath bezierPathWithRect:self.calloutViewForSelectedAnnotation.frame];
- [path appendPath:calloutViewPath];
- }
-
- return path;
-}
-
-- (NSInteger)accessibilityElementCount
-{
- if (self.calloutViewForSelectedAnnotation)
- {
- return 2 /* calloutViewForSelectedAnnotation, mapViewProxyAccessibilityElement */;
- }
- return !!self.userLocationAnnotationView + self.accessibilityAnnotationCount + self.visiblePlaceFeatures.count + self.visibleRoadFeatures.count + 2 /* compass, attributionButton */;
-}
-
-- (NSInteger)accessibilityAnnotationCount
-{
- std::vector<MGLAnnotationTag> visibleAnnotations = [self annotationTagsInRect:self.bounds];
- return visibleAnnotations.size();
-}
-
-- (id)accessibilityElementAtIndex:(NSInteger)index
-{
- if (self.calloutViewForSelectedAnnotation)
- {
- if (index == 0)
- {
- return self.calloutViewForSelectedAnnotation;
- }
- if (index == 1)
- {
- self.mapViewProxyAccessibilityElement.accessibilityFrame = self.accessibilityFrame;
- self.mapViewProxyAccessibilityElement.accessibilityPath = self.accessibilityPath;
- return self.mapViewProxyAccessibilityElement;
- }
- return nil;
- }
-
- // Compass
- NSInteger compassIndex = 0;
- if (index == compassIndex)
- {
- return self.compassView;
- }
-
- // User location annotation
- NSRange userLocationAnnotationRange = NSMakeRange(compassIndex + 1, !!self.userLocationAnnotationView);
- if (NSLocationInRange(index, userLocationAnnotationRange))
- {
- return self.userLocationAnnotationView;
- }
-
- 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());
- 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];
- MGLAssert(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
- NSInteger attributionButtonIndex = NSMaxRange(visibleRoadFeatureRange);
- if (index == attributionButtonIndex)
- {
- return self.attributionButton;
- }
-
- MGLAssert(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
-{
- MGLAssert(_annotationContextsByAnnotationTag.count(annotationTag), @"Missing annotation for tag %llu.", 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];
- CGPoint annotationFrameCenter = CGPointMake(CGRectGetMidX(annotationFrame), CGRectGetMidY(annotationFrame));
- CGRect minimumFrame = CGRectInset({ annotationFrameCenter, CGSizeZero },
- -MGLAnnotationAccessibilityElementMinimumSize.width / 2,
- -MGLAnnotationAccessibilityElementMinimumSize.height / 2);
- annotationFrame = CGRectUnion(annotationFrame, minimumFrame);
- CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self);
- annotationContext.accessibilityElement.accessibilityFrame = screenRect;
-
- if ([annotation respondsToSelector:@selector(title)])
- {
- annotationContext.accessibilityElement.accessibilityLabel = annotation.title;
- }
- if ([annotation respondsToSelector:@selector(subtitle)])
- {
- 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 testElement, BOOL * _Nonnull stop) {
- return testElement.feature.identifier && ![testElement.feature.identifier isEqual:@0] && [testElement.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 testElement, BOOL * _Nonnull stop) {
- return testElement.feature.identifier && ![testElement.feature.identifier isEqual:@0] && [testElement.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)
- {
- return [@[self.calloutViewForSelectedAnnotation, self.mapViewProxyAccessibilityElement]
- indexOfObject:element];
- }
-
- // Compass
- NSUInteger compassIndex = 0;
- if (element == self.compassView)
- {
- return compassIndex;
- }
-
- // User location annotation
- NSRange userLocationAnnotationRange = NSMakeRange(compassIndex + 1, !!self.userLocationAnnotationView);
- if (element == self.userLocationAnnotationView)
- {
- 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]])
- {
- id <MGLAnnotation> annotation = [(MGLAnnotationView *)element annotation];
- tag = [self annotationTagForAnnotation:annotation];
- }
- else if ([element isKindOfClass:[MGLAnnotationAccessibilityElement class]])
- {
- tag = [(MGLAnnotationAccessibilityElement *)element tag];
- }
-
- 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);
- }
-
- // 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;
- }
-
- // 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 attributionButtonIndex;
- }
-
- return NSNotFound;
-}
-
-- (MGLMapViewProxyAccessibilityElement *)mapViewProxyAccessibilityElement
-{
- if ( ! _mapViewProxyAccessibilityElement)
- {
- _mapViewProxyAccessibilityElement = [[MGLMapViewProxyAccessibilityElement alloc] initWithAccessibilityContainer:self];
- }
- return _mapViewProxyAccessibilityElement;
-}
-
-- (void)accessibilityIncrement
-{
- // Swipe up to zoom out.
- [self accessibilityScaleBy:0.5];
-}
-
-- (void)accessibilityDecrement
-{
- // Swipe down to zoom in.
- [self accessibilityScaleBy:2];
-}
-
-- (void)accessibilityScaleBy:(double)scaleFactor
-{
- CGPoint centerPoint = self.contentCenter;
- if (self.userTrackingMode != MGLUserTrackingModeNone)
- {
- centerPoint = self.userLocationAnnotationViewCenter;
- }
- double newZoom = round(self.zoomLevel) + log2(scaleFactor);
- self.mbglMap.jumpTo(mbgl::CameraOptions()
- .withZoom(newZoom)
- .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
- .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)));
- [self unrotateIfNeededForGesture];
-
- _accessibilityValueAnnouncementIsPending = YES;
-}
-
-#pragma mark - Geography -
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingCenterCoordinate
-{
- return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil];
-}
-
-- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated
-{
- MGLLogDebug(@"Setting centerCoordinate: %@ animated: %@", MGLStringFromCLLocationCoordinate2D(coordinate), MGLStringFromBOOL(animated));
- [self setCenterCoordinate:coordinate zoomLevel:self.zoomLevel animated:animated];
-}
-
-- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
-{
- MGLLogDebug(@"Setting centerCoordinate: %@", MGLStringFromCLLocationCoordinate2D(centerCoordinate));
- [self setCenterCoordinate:centerCoordinate animated:NO];
-}
-
-- (CLLocationCoordinate2D)centerCoordinate
-{
- mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
- return MGLLocationCoordinate2DFromLatLng(*self.mbglMap.getCameraOptions(padding).center);
-}
-
-- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated
-{
- MGLLogDebug(@"Setting centerCoordinate: %@ zoomLevel: %f animated: %@",
- MGLStringFromCLLocationCoordinate2D(centerCoordinate),
- zoomLevel,
- MGLStringFromBOOL(animated));
- [self setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:self.direction animated:animated];
-}
-
-- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated
-{
- MGLLogDebug(@"Setting centerCoordinate: %@ zoomLevel: %f direction: %f animated: %@",
- MGLStringFromCLLocationCoordinate2D(centerCoordinate),
- zoomLevel,
- direction,
- MGLStringFromBOOL(animated));
- [self setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:direction animated:animated completionHandler:nil];
-}
-
-- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Setting centerCoordinate: %@ zoomLevel: %f direction: %f animated: %@ completionHandler: %@",
- MGLStringFromCLLocationCoordinate2D(centerCoordinate),
- zoomLevel,
- direction,
- MGLStringFromBOOL(animated),
- completion);
- self.userTrackingMode = MGLUserTrackingModeNone;
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
-
- [self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:zoomLevel direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion];
-}
-
-- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate edgePadding:(UIEdgeInsets)insets zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
-{
- if (!_mbglMap)
- {
- if (completion)
- {
- completion();
- }
- return;
- }
-
- mbgl::CameraOptions cameraOptions;
- cameraOptions.center = MGLLatLngFromLocationCoordinate2D(centerCoordinate);
- cameraOptions.padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
- cameraOptions.zoom = zoomLevel;
- if (direction >= 0)
- {
- cameraOptions.bearing = direction;
- }
-
- mbgl::AnimationOptions animationOptions;
- if (duration)
- {
- animationOptions.duration.emplace(MGLDurationFromTimeInterval(duration));
- animationOptions.easing.emplace(MGLUnitBezierForMediaTimingFunction(function));
- }
-
- dispatch_block_t pendingCompletion;
-
- if (completion)
- {
- __weak __typeof__(self) weakSelf = self;
-
- pendingCompletion = ^{
- if (![weakSelf scheduleTransitionCompletion:completion])
- {
- completion();
- }
- };
-
- animationOptions.transitionFinishFn = [pendingCompletion]() {
- // Must run asynchronously after the transition is completely over.
- // Otherwise, a call to -setCenterCoordinate: within the completion
- // handler would reenter the completion handler’s caller.
-
- dispatch_async(dispatch_get_main_queue(), pendingCompletion);
- };
- }
-
- MGLMapCamera *camera = [self cameraForCameraOptions:cameraOptions];
- if ([self.camera isEqualToMapCamera:camera] && UIEdgeInsetsEqualToEdgeInsets(_contentInset, insets))
- {
- if (pendingCompletion)
- {
- [self animateWithDelay:duration animations:pendingCompletion];
- }
- return;
- }
-
- [self cancelTransitions];
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
-
- self.mbglMap.easeTo(cameraOptions, animationOptions);
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingZoomLevel
-{
- return [NSSet setWithObject:@"camera"];
-}
-
-- (double)zoomLevel
-{
- mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
- return *self.mbglMap.getCameraOptions(padding).zoom;
-}
-
-- (void)setZoomLevel:(double)zoomLevel
-{
- MGLLogDebug(@"Setting zoomLevel: %f", zoomLevel);
- [self setZoomLevel:zoomLevel animated:NO];
-}
-
-- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated
-{
- MGLLogDebug(@"Setting zoomLevel: %f animated: %@", zoomLevel, MGLStringFromBOOL(animated));
- if (zoomLevel == self.zoomLevel) return;
- [self cancelTransitions];
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
-
- CGFloat duration = animated ? MGLAnimationDuration : 0;
-
- self.mbglMap.easeTo(mbgl::CameraOptions()
- .withZoom(zoomLevel)
- .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)),
- MGLDurationFromTimeInterval(duration));
-}
-
-- (void)setMinimumZoomLevel:(double)minimumZoomLevel
-{
- MGLLogDebug(@"Setting minimumZoomLevel: %f", minimumZoomLevel);
- self.mbglMap.setBounds(mbgl::BoundOptions().withMinZoom(minimumZoomLevel));
-}
-
-- (double)minimumZoomLevel
-{
- return *self.mbglMap.getBounds().minZoom;
-}
-
-- (void)setMaximumZoomLevel:(double)maximumZoomLevel
-{
- MGLLogDebug(@"Setting maximumZoomLevel: %f", maximumZoomLevel);
- self.mbglMap.setBounds(mbgl::BoundOptions().withMaxZoom(maximumZoomLevel));
-}
-
-- (double)maximumZoomLevel
-{
- return *self.mbglMap.getBounds().maxZoom;
-}
-
-- (MGLCoordinateBounds)visibleCoordinateBounds
-{
- return [self convertRect:self.bounds toCoordinateBoundsFromView:self];
-}
-
-- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds
-{
- [self setVisibleCoordinateBounds:bounds animated:NO];
-}
-
-- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds animated:(BOOL)animated
-{
- [self setVisibleCoordinateBounds:bounds edgePadding:UIEdgeInsetsZero animated:animated completionHandler:nil];
-}
-
-- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated
-{
- [self setVisibleCoordinateBounds:bounds edgePadding:insets animated:animated completionHandler:nil];
-}
-
-- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Setting visibleCoordinateBounds: %@ edgePadding: %@ animated: %@",
- MGLStringFromCoordinateBounds(bounds),
- NSStringFromUIEdgeInsets(insets),
- MGLStringFromBOOL(animated));
- CLLocationCoordinate2D coordinates[] = {
- {bounds.ne.latitude, bounds.sw.longitude},
- bounds.sw,
- {bounds.sw.latitude, bounds.ne.longitude},
- bounds.ne,
- };
- [self setVisibleCoordinates:coordinates
- count:sizeof(coordinates) / sizeof(coordinates[0])
- edgePadding:insets
- direction:self.direction
- duration:animated ? MGLAnimationDuration : 0
- animationTimingFunction:nil
- completionHandler:completion];
-}
-
-- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated
-{
- MGLLogDebug(@"Setting: %lu coordinates edgePadding: %@ animated: %@",
- count,
- NSStringFromUIEdgeInsets(insets),
- MGLStringFromBOOL(animated));
- [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:self.direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil];
-}
-
-- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function {
- MGLLogDebug(@"Setting: %lu coordinates edgePadding: %@ direction: %f duration: %f animationTimingFunction: %@",
- count,
- NSStringFromUIEdgeInsets(insets),
- direction,
- duration,
- function);
- [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:duration animationTimingFunction:function completionHandler:NULL];
-}
-
-- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Setting: %lu coordinates edgePadding: %@ direction: %f duration: %f animationTimingFunction: %@ completionHandler: %@", count, NSStringFromUIEdgeInsets(insets), direction, duration, function, completion);
- self.userTrackingMode = MGLUserTrackingModeNone;
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
-
- [self _setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:duration animationTimingFunction:function completionHandler:completion];
-}
-
-- (void)_setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
-{
- if (!_mbglMap)
- {
- if (completion)
- {
- completion();
- }
- return;
- }
-
- mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
- padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
- std::vector<mbgl::LatLng> latLngs;
- latLngs.reserve(count);
- for (NSUInteger i = 0; i < count; i++)
- {
- latLngs.push_back({coordinates[i].latitude, coordinates[i].longitude});
- }
-
- CLLocationDirection cameraDirection = direction >= 0 ? direction : self.direction;
-
- mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForLatLngs(latLngs, padding, cameraDirection);
-
- mbgl::AnimationOptions animationOptions;
- if (duration > 0)
- {
- animationOptions.duration.emplace(MGLDurationFromTimeInterval(duration));
- animationOptions.easing.emplace(MGLUnitBezierForMediaTimingFunction(function));
- }
-
- dispatch_block_t pendingCompletion;
-
- if (completion)
- {
- __weak __typeof__(self) weakSelf = self;
-
- pendingCompletion = ^{
- if (![weakSelf scheduleTransitionCompletion:completion])
- {
- completion();
- }
- };
-
- animationOptions.transitionFinishFn = [pendingCompletion]() {
- dispatch_async(dispatch_get_main_queue(), pendingCompletion);
- };
- }
-
- // Padding is baked in adjusted camera center (in cameraForLatLngs) and
- // cameraOptions.padding at this point is (0, 0, 0, 0) and we don't need to
- // check if cameraOptions.contentInsets are equal to contentInsets.
- MGLMapCamera *camera = [self cameraForCameraOptions:cameraOptions];
- if ([self.camera isEqualToMapCamera:camera])
- {
- if (pendingCompletion)
- {
- [self animateWithDelay:duration animations:pendingCompletion];
- }
- return;
- }
-
- [self willChangeValueForKey:@"visibleCoordinateBounds"];
- [self cancelTransitions];
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
-
- self.mbglMap.easeTo(cameraOptions, animationOptions);
- [self didChangeValueForKey:@"visibleCoordinateBounds"];
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingDirection
-{
- return [NSSet setWithObject:@"camera"];
-}
-
-- (CLLocationDirection)direction
-{
- return mbgl::util::wrap(*self.mbglMap.getCameraOptions().bearing, 0., 360.);
-}
-
-- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated
-{
- MGLLogDebug(@"Setting direction: %f animated: %@", direction, MGLStringFromBOOL(animated));
- if ( ! animated && ! self.rotationAllowed) return;
-
- if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading ||
- self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
- {
- self.userTrackingMode = MGLUserTrackingModeFollow;
- }
-
- [self _setDirection:direction animated:animated];
-}
-
-- (void)_setDirection:(CLLocationDirection)direction animated:(BOOL)animated
-{
- if (!_mbglMap)
- {
- return;
- }
-
- if (direction == self.direction) return;
- [self cancelTransitions];
-
- CGFloat duration = animated ? MGLAnimationDuration : 0;
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
-
- if (self.userTrackingMode == MGLUserTrackingModeNone)
- {
- self.mbglMap.easeTo(mbgl::CameraOptions()
- .withBearing(direction)
- .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)),
- MGLDurationFromTimeInterval(duration));
- }
- else
- {
- CGPoint anchor = self.userLocationAnnotationViewCenter;
- self.mbglMap.easeTo(mbgl::CameraOptions()
- .withBearing(direction)
- .withAnchor(mbgl::ScreenCoordinate { anchor.x, anchor.y }),
- MGLDurationFromTimeInterval(duration));
- }
-}
-
-- (void)setDirection:(CLLocationDirection)direction
-{
- MGLLogDebug(@"Setting direction: %f", direction);
- [self setDirection:direction animated:NO];
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingPitch
-{
- return [NSSet setWithObject:@"camera"];
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingCamera
-{
- return [NSSet setWithObjects:@"longitude", @"latitude", @"centerCoordinate", @"zoomLevel", @"direction", nil];
-}
-
-- (MGLMapCamera *)camera
-{
- if (!_mbglMap)
- {
- NSAssert(self.terminated, @"_mbglMap should only be unavailable during app termination");
- return self.residualCamera;
- }
-
- mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
- return [self cameraForCameraOptions:self.mbglMap.getCameraOptions(padding)];
-}
-
-- (void)setCamera:(MGLMapCamera *)camera
-{
- MGLLogDebug(@"Setting camera: %@", camera);
- [self setCamera:camera animated:NO];
-}
-
-- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated
-{
- MGLLogDebug(@"Setting camera: %@ animated: %@", camera, MGLStringFromBOOL(animated));
- [self setCamera:camera withDuration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil];
-}
-
-- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function
-{
- MGLLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@", camera, duration, function);
- [self setCamera:camera withDuration:duration animationTimingFunction:function completionHandler:nil];
-}
-
-- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@ completionHandler: %@", camera, duration, function, completion);
- [self setCamera:camera withDuration:duration animationTimingFunction:function edgePadding:UIEdgeInsetsZero completionHandler:completion];
-}
-
-- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(UIEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion {
- if (!_mbglMap)
- {
- if (completion)
- {
- completion();
- }
- return;
- }
-
- MGLLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@ edgePadding: %@ completionHandler: %@", camera, duration, function, NSStringFromUIEdgeInsets(edgePadding), completion);
-
- edgePadding = MGLEdgeInsetsInsetEdgeInset(edgePadding, self.contentInset);
-
- mbgl::AnimationOptions animationOptions;
- if (duration > 0)
- {
- animationOptions.duration.emplace(MGLDurationFromTimeInterval(duration));
- animationOptions.easing.emplace(MGLUnitBezierForMediaTimingFunction(function));
- }
-
- dispatch_block_t pendingCompletion;
-
- if (completion)
- {
- __weak __typeof__(self) weakSelf = self;
-
- pendingCompletion = ^{
- if (![weakSelf scheduleTransitionCompletion:completion])
- {
- completion();
- }
- };
-
- animationOptions.transitionFinishFn = [pendingCompletion]() {
- dispatch_async(dispatch_get_main_queue(), pendingCompletion);
- };
- }
-
- if ([self.camera isEqualToMapCamera:camera] && UIEdgeInsetsEqualToEdgeInsets(_contentInset, edgePadding))
- {
- if (pendingCompletion)
- {
- [self animateWithDelay:duration animations:pendingCompletion];
- }
- return;
- }
-
- [self willChangeValueForKey:@"camera"];
- [self cancelTransitions];
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
-
- mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding];
- self.mbglMap.easeTo(cameraOptions, animationOptions);
- [self didChangeValueForKey:@"camera"];
-}
-
-- (void)flyToCamera:(MGLMapCamera *)camera completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Setting flyToCamera: %@ completionHandler: %@", camera, completion);
- [self flyToCamera:camera withDuration:-1 completionHandler:completion];
-}
-
-- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Setting flyToCamera: %@ withDuration: %f completionHandler: %@", camera, duration, completion);
- [self flyToCamera:camera withDuration:duration peakAltitude:-1 completionHandler:completion];
-}
-
-- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Setting flyToCamera: %@ withDuration: %f peakAltitude: %f completionHandler: %@", camera, duration, peakAltitude, completion);
- [self _flyToCamera:camera edgePadding:self.contentInset withDuration:duration peakAltitude:peakAltitude completionHandler:completion];
-}
-
-- (void)_flyToCamera:(MGLMapCamera *)camera edgePadding:(UIEdgeInsets)insets withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion
-{
- if (!_mbglMap)
- {
- if (completion)
- {
- completion();
- }
- return;
- }
-
- mbgl::AnimationOptions animationOptions;
- if (duration >= 0)
- {
- animationOptions.duration = MGLDurationFromTimeInterval(duration);
- }
- if (peakAltitude >= 0)
- {
- CLLocationDegrees peakLatitude = (self.centerCoordinate.latitude + camera.centerCoordinate.latitude) / 2;
- CLLocationDegrees peakPitch = (self.camera.pitch + camera.pitch) / 2;
- animationOptions.minZoom = MGLZoomLevelForAltitude(peakAltitude, peakPitch,
- peakLatitude, self.frame.size);
- }
-
- dispatch_block_t pendingCompletion;
-
- if (completion)
- {
- __weak __typeof__(self) weakSelf = self;
-
- pendingCompletion = ^{
- if (![weakSelf scheduleTransitionCompletion:completion])
- {
- completion();
- }
- };
-
- animationOptions.transitionFinishFn = [pendingCompletion]() {
- dispatch_async(dispatch_get_main_queue(), pendingCompletion);
- };
- }
-
- if ([self.camera isEqualToMapCamera:camera] && UIEdgeInsetsEqualToEdgeInsets(_contentInset, insets))
- {
- if (pendingCompletion)
- {
- [self animateWithDelay:duration animations:pendingCompletion];
- }
- return;
- }
-
- [self willChangeValueForKey:@"camera"];
- [self cancelTransitions];
-
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
-
- mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets];
- self.mbglMap.flyTo(cameraOptions, animationOptions);
- [self didChangeValueForKey:@"camera"];
-}
-
-- (void)cancelTransitions {
- if (!_mbglMap)
- {
- return;
- }
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonTransitionCancelled;
- self.mbglMap.cancelTransitions();
- self.cameraChangeReasonBitmask &= ~MGLCameraChangeReasonTransitionCancelled;
-}
-
-- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds
-{
- return [self cameraThatFitsCoordinateBounds:bounds edgePadding:UIEdgeInsetsZero];
-}
-
-- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets
-{
- if (!_mbglMap)
- {
- return self.residualCamera;
- }
-
- mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
- padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
- mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding);
- return [self cameraForCameraOptions:cameraOptions];
-}
-
-- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets
-{
- if (!_mbglMap)
- {
- return self.residualCamera;
- }
-
- mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
- padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
-
- MGLMapCamera *currentCamera = self.camera;
- CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch;
- CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading;
-
- mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding, direction, pitch);
- return [self cameraForCameraOptions:cameraOptions];
-}
-
-- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingShape:(MGLShape *)shape edgePadding:(UIEdgeInsets)insets {
- if (!_mbglMap)
- {
- return self.residualCamera;
- }
-
- mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
- padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
-
- MGLMapCamera *currentCamera = self.camera;
- CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch;
- CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading;
-
- mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForGeometry([shape geometryObject], padding, direction, pitch);
-
- return [self cameraForCameraOptions: cameraOptions];
-}
-
-- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(UIEdgeInsets)insets {
- if (!_mbglMap)
- {
- return self.residualCamera;
- }
-
- mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
- padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
-
- mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForGeometry([shape geometryObject], padding, direction);
-
- return [self cameraForCameraOptions:cameraOptions];
-}
-
-- (MGLMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions
-{
- if (!_mbglMap)
- {
- return self.residualCamera;
- }
-
- mbgl::CameraOptions mapCamera = self.mbglMap.getCameraOptions();
- CLLocationCoordinate2D centerCoordinate = MGLLocationCoordinate2DFromLatLng(cameraOptions.center ? *cameraOptions.center : *mapCamera.center);
- double zoomLevel = cameraOptions.zoom ? *cameraOptions.zoom : self.zoomLevel;
- CLLocationDirection direction = cameraOptions.bearing ? mbgl::util::wrap(*cameraOptions.bearing, 0., 360.) : self.direction;
- CGFloat pitch = cameraOptions.pitch ? *cameraOptions.pitch : *mapCamera.pitch;
- CLLocationDistance altitude = MGLAltitudeForZoomLevel(zoomLevel, pitch, centerCoordinate.latitude, self.frame.size);
- return [MGLMapCamera cameraLookingAtCenterCoordinate:centerCoordinate altitude:altitude pitch:pitch heading:direction];
-}
-
-/// Returns a CameraOptions object that specifies parameters for animating to
-/// the given camera.
-- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MGLMapCamera *)camera edgePadding:(UIEdgeInsets)insets
-{
- mbgl::CameraOptions options;
- if (CLLocationCoordinate2DIsValid(camera.centerCoordinate))
- {
- options.center = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate);
- }
- options.padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
- options.zoom = MGLZoomLevelForAltitude(camera.altitude, camera.pitch,
- camera.centerCoordinate.latitude,
- self.frame.size);
- if (camera.heading >= 0)
- {
- options.bearing = camera.heading;
- }
- if (camera.pitch >= 0)
- {
- options.pitch = camera.pitch;
- }
- return options;
-}
-
-- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view
-{
- return MGLLocationCoordinate2DFromLatLng([self convertPoint:point toLatLngFromView:view]);
-}
-
-/// Converts a point in the view’s coordinate system to a geographic coordinate.
-- (mbgl::LatLng)convertPoint:(CGPoint)point toLatLngFromView:(nullable UIView *)view
-{
- CGPoint convertedPoint = [self convertPoint:point fromView:view];
- return self.mbglMap.latLngForPixel(mbgl::ScreenCoordinate(convertedPoint.x, convertedPoint.y)).wrapped();
-}
-
-- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable UIView *)view
-{
- if ( ! CLLocationCoordinate2DIsValid(coordinate))
- {
- return CGPointMake(NAN, NAN);
- }
- return [self convertLatLng:MGLLatLngFromLocationCoordinate2D(coordinate) toPointToView:view];
-}
-
-/// Converts a geographic coordinate to a point in the view’s coordinate system.
-- (CGPoint)convertLatLng:(mbgl::LatLng)latLng toPointToView:(nullable UIView *)view
-{
- mbgl::ScreenCoordinate pixel = self.mbglMap.pixelForLatLng(latLng);
- return [self convertPoint:CGPointMake(pixel.x, pixel.y) toView:view];
-}
-
-- (MGLCoordinateBounds)convertRect:(CGRect)rect toCoordinateBoundsFromView:(nullable UIView *)view
-{
- return MGLCoordinateBoundsFromLatLngBounds([self convertRect:rect toLatLngBoundsFromView:view]);
-}
-
-- (CGRect)convertCoordinateBounds:(MGLCoordinateBounds)bounds toRectToView:(nullable UIView *)view
-{
- return [self convertLatLngBounds:MGLLatLngBoundsFromCoordinateBounds(bounds) toRectToView:view];
-}
-
-/// Converts a geographic bounding box to a rectangle in the view’s coordinate
-/// system.
-- (CGRect)convertLatLngBounds:(mbgl::LatLngBounds)bounds toRectToView:(nullable UIView *)view {
- auto northwest = bounds.northwest();
- auto northeast = bounds.northeast();
- auto southwest = bounds.southwest();
- auto southeast = bounds.southeast();
-
- auto center = [self convertPoint:{ CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds) } toLatLngFromView:view];
-
- // Extend bounds to account for the antimeridian
- northwest.unwrapForShortestPath(center);
- northeast.unwrapForShortestPath(center);
- southwest.unwrapForShortestPath(center);
- southeast.unwrapForShortestPath(center);
-
- auto correctedLatLngBounds = mbgl::LatLngBounds::empty();
- correctedLatLngBounds.extend(northwest);
- correctedLatLngBounds.extend(northeast);
- correctedLatLngBounds.extend(southwest);
- correctedLatLngBounds.extend(southeast);
-
- CGRect rect = { [self convertLatLng:correctedLatLngBounds.southwest() toPointToView:view], CGSizeZero };
- rect = MGLExtendRect(rect, [self convertLatLng:correctedLatLngBounds.northeast() toPointToView:view]);
- return rect;
-}
-
-/// Converts a rectangle in the given view’s coordinate system to a geographic
-/// bounding box.
-- (mbgl::LatLngBounds)convertRect:(CGRect)rect toLatLngBoundsFromView:(nullable UIView *)view
-{
- auto bounds = mbgl::LatLngBounds::empty();
- auto topLeft = [self convertPoint:{ CGRectGetMinX(rect), CGRectGetMinY(rect) } toLatLngFromView:view];
- auto topRight = [self convertPoint:{ CGRectGetMaxX(rect), CGRectGetMinY(rect) } toLatLngFromView:view];
- auto bottomRight = [self convertPoint:{ CGRectGetMaxX(rect), CGRectGetMaxY(rect) } toLatLngFromView:view];
- auto bottomLeft = [self convertPoint:{ CGRectGetMinX(rect), CGRectGetMaxY(rect) } toLatLngFromView:view];
-
- // If the bounds straddles the antimeridian, unwrap it so that one side
- // extends beyond ±180° longitude.
- auto center = [self convertPoint:{ CGRectGetMidX(rect), CGRectGetMidY(rect) } toLatLngFromView:view];
- topLeft.unwrapForShortestPath(center);
- topRight.unwrapForShortestPath(center);
- bottomRight.unwrapForShortestPath(center);
- bottomLeft.unwrapForShortestPath(center);
-
- bounds.extend(topLeft);
- bounds.extend(topRight);
- bounds.extend(bottomRight);
- bounds.extend(bottomLeft);
-
- return bounds;
-}
-
-- (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude
-{
- return mbgl::Projection::getMetersPerPixelAtLatitude(latitude, self.zoomLevel);
-}
-
-#pragma mark - Camera Change Reason -
-
-- (void)resetCameraChangeReason
-{
- self.cameraChangeReasonBitmask = MGLCameraChangeReasonNone;
-}
-
-#pragma mark - Annotations -
-
-- (nullable NSArray<id <MGLAnnotation>> *)annotations
-{
- if (_annotationContextsByAnnotationTag.empty())
- {
- return nil;
- }
-
- // Map all the annotation tags to the annotations themselves.
- std::vector<id <MGLAnnotation>> annotations;
- std::transform(_annotationContextsByAnnotationTag.begin(),
- _annotationContextsByAnnotationTag.end(),
- std::back_inserter(annotations),
- ^ id <MGLAnnotation> (const std::pair<MGLAnnotationTag, MGLAnnotationContext> &pair)
- {
- return pair.second.annotation;
- });
-
- annotations.erase(std::remove_if(annotations.begin(), annotations.end(),
- [](const id <MGLAnnotation> annotation) { return annotation == nullptr; }),
- annotations.end());
-
- return [NSArray arrayWithObjects:&annotations[0] count:annotations.size()];
-}
-
-- (nullable NSArray<id <MGLAnnotation>> *)visibleAnnotations
-{
- return [self visibleAnnotationsInRect:self.bounds];
-}
-
-- (nullable NSArray<id <MGLAnnotation>> *)visibleAnnotationsInRect:(CGRect)rect
-{
- if (_annotationContextsByAnnotationTag.empty())
- {
- return nil;
- }
-
- 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()];
-
- for (auto const& annotationTag: annotationTags)
- {
- if (!_annotationContextsByAnnotationTag.count(annotationTag) ||
- annotationTag == MGLAnnotationTagNotFound)
- {
- continue;
- }
-
- MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
- MGLAssert(annotationContext.annotation, @"Missing annotation for tag %llu.", annotationTag);
- if (annotationContext.annotation)
- {
- [annotations addObject:annotationContext.annotation];
- }
- }
-
- return [annotations copy];
- }
-
- return nil;
-}
-
-/// Returns the annotation assigned the given tag. Cheap.
-- (id <MGLAnnotation>)annotationWithTag:(MGLAnnotationTag)tag
-{
- if ( ! _annotationContextsByAnnotationTag.count(tag) ||
- tag == MGLAnnotationTagNotFound) {
- return nil;
- }
-
- MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(tag);
- return annotationContext.annotation;
-}
-
-/// Returns the annotation tag assigned to the given annotation.
-- (MGLAnnotationTag)annotationTagForAnnotation:(id <MGLAnnotation>)annotation
-{
- if ( ! annotation || annotation == self.userLocation
- || _annotationTagsByAnnotation.count(annotation) == 0)
- {
- return MGLAnnotationTagNotFound;
- }
-
- return _annotationTagsByAnnotation.at(annotation);
-}
-
-- (void)addAnnotation:(id <MGLAnnotation>)annotation
-{
- MGLLogDebug(@"Adding annotation: %@", annotation);
- if ( ! annotation) return;
-
- // The core bulk add API is efficient with respect to indexing and
- // screen refreshes, thus we should defer to it even for individual adds.
- //
- [self addAnnotations:@[ annotation ]];
-}
-
-- (void)addAnnotations:(NSArray<id <MGLAnnotation>> *)annotations
-{
- MGLLogDebug(@"Adding: %lu annotations", annotations.count);
- if ( ! annotations) return;
- [self willChangeValueForKey:@"annotations"];
-
- NSMutableDictionary *annotationImagesForAnnotation = [NSMutableDictionary dictionary];
- NSMutableDictionary *annotationViewsForAnnotation = [NSMutableDictionary dictionary];
-
- BOOL delegateImplementsViewForAnnotation = [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)];
- BOOL delegateImplementsImageForPoint = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)];
-
- NSMutableArray *newAnnotationViews = [[NSMutableArray alloc] initWithCapacity:annotations.count];
-
- for (id <MGLAnnotation> annotation in annotations)
- {
- MGLAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"annotation should conform to MGLAnnotation");
-
- // adding the same annotation object twice is a no-op
- if (_annotationTagsByAnnotation.count(annotation) != 0)
- {
- continue;
- }
-
- if ([annotation isKindOfClass:[MGLMultiPoint class]])
- {
- // The polyline or polygon knows how to style itself (with the map view’s help).
- MGLMultiPoint *multiPoint = (MGLMultiPoint *)annotation;
- if (!multiPoint.pointCount) {
- continue;
- }
-
- _isChangingAnnotationLayers = YES;
- MGLAnnotationTag annotationTag = self.mbglMap.addAnnotation([multiPoint annotationObjectWithDelegate:self]);
- MGLAnnotationContext context;
- context.annotation = annotation;
- _annotationContextsByAnnotationTag[annotationTag] = context;
- _annotationTagsByAnnotation[annotation] = annotationTag;
-
- [(NSObject *)annotation addObserver:self forKeyPath:@"coordinates" options:0 context:(void *)(NSUInteger)annotationTag];
- }
- else if ( ! [annotation isKindOfClass:[MGLMultiPolyline class]]
- && ![annotation isKindOfClass:[MGLMultiPolygon class]]
- && ![annotation isKindOfClass:[MGLShapeCollection class]]
- && ![annotation isKindOfClass:[MGLPointCollection class]])
- {
- MGLAnnotationView *annotationView;
- NSString *symbolName;
- NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation];
-
- if (delegateImplementsViewForAnnotation)
- {
- annotationView = [self annotationViewForAnnotation:annotation];
- if (annotationView)
- {
- annotationViewsForAnnotation[annotationValue] = annotationView;
- annotationView.annotation = annotation;
- annotationView.center = MGLPointRounded([self convertCoordinate:annotation.coordinate toPointToView:self]);
- [newAnnotationViews addObject:annotationView];
-
- MGLAnnotationImage *annotationImage = self.invisibleAnnotationImage;
- symbolName = annotationImage.styleIconIdentifier;
- annotationImagesForAnnotation[annotationValue] = annotationImage;
- if ( ! self.annotationImagesByIdentifier[annotationImage.reuseIdentifier])
- {
- [self installAnnotationImage:annotationImage];
- }
- }
- }
-
- if ( ! annotationView) {
- MGLAnnotationImage *annotationImage;
-
- if (delegateImplementsImageForPoint)
- {
- annotationImage = [self.delegate mapView:self imageForAnnotation:annotation];
- }
- if ( ! annotationImage)
- {
- annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName];
- }
- if ( ! annotationImage)
- {
- annotationImage = self.defaultAnnotationImage;
- }
-
- symbolName = annotationImage.styleIconIdentifier;
-
- if ( ! symbolName)
- {
- symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
- annotationImage.styleIconIdentifier = symbolName;
- }
- if ( ! self.annotationImagesByIdentifier[annotationImage.reuseIdentifier])
- {
- [self installAnnotationImage:annotationImage];
- }
-
- annotationImagesForAnnotation[annotationValue] = annotationImage;
- }
-
- MGLAnnotationTag annotationTag = self.mbglMap.addAnnotation(mbgl::SymbolAnnotation {
- MGLPointFromLocationCoordinate2D(annotation.coordinate),
- symbolName.UTF8String
- });
-
- MGLAnnotationContext context;
- context.annotation = annotation;
- MGLAnnotationImage *annotationImage = annotationImagesForAnnotation[annotationValue];
- context.imageReuseIdentifier = annotationImage.reuseIdentifier;
-
- if (annotationView) {
- context.annotationView = annotationView;
- context.viewReuseIdentifier = annotationView.reuseIdentifier;
- }
-
- _annotationTagsByAnnotation[annotation] = annotationTag;
- _annotationContextsByAnnotationTag[annotationTag] = context;
-
- if ([annotation isKindOfClass:[NSObject class]]) {
- MGLAssert(![annotation isKindOfClass:[MGLMultiPoint class]], @"Point annotation should not be MGLMultiPoint.");
- [(NSObject *)annotation addObserver:self forKeyPath:@"coordinate" options:0 context:(void *)(NSUInteger)annotationTag];
- }
- }
- }
-
- [self updateAnnotationContainerViewWithAnnotationViews:newAnnotationViews];
-
- [self didChangeValueForKey:@"annotations"];
- if (_isChangingAnnotationLayers)
- {
- [self.style willChangeValueForKey:@"layers"];
- }
-
- if ([self.delegate respondsToSelector:@selector(mapView:didAddAnnotationViews:)])
- {
- [self.delegate mapView:self didAddAnnotationViews:newAnnotationViews];
- }
-
- UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
-}
-
-- (void)updateAnnotationContainerViewWithAnnotationViews:(NSArray<MGLAnnotationView *> *)annotationViews
-{
- if (annotationViews.count == 0) return;
-
- MGLAnnotationContainerView *newAnnotationContainerView;
- if (self.annotationContainerView)
- {
- // reload any previously added views
- newAnnotationContainerView = [MGLAnnotationContainerView annotationContainerViewWithAnnotationContainerView:self.annotationContainerView];
- [self.annotationContainerView removeFromSuperview];
- }
- else
- {
- newAnnotationContainerView = [[MGLAnnotationContainerView alloc] initWithFrame:self.bounds];
- }
- newAnnotationContainerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- newAnnotationContainerView.contentMode = UIViewContentModeCenter;
- [newAnnotationContainerView addSubviews:annotationViews];
- [_mbglView->getView() insertSubview:newAnnotationContainerView atIndex:0];
- self.annotationContainerView = newAnnotationContainerView;
-
- [self updatePresentsWithTransaction];
-}
-
-/// Initialize and return a default annotation image that depicts a round pin
-/// rising from the center, with a shadow slightly below center. The alignment
-/// rect therefore excludes the bottom half.
-- (MGLAnnotationImage *)defaultAnnotationImage
-{
- UIImage *image = [UIImage mgl_resourceImageNamed:MGLDefaultStyleMarkerSymbolName];
- image = [image imageWithAlignmentRectInsets:
- UIEdgeInsetsMake(0, 0, image.size.height / 2, 0)];
- MGLAnnotationImage *annotationImage = [MGLAnnotationImage annotationImageWithImage:image
- reuseIdentifier:MGLDefaultStyleMarkerSymbolName];
- annotationImage.styleIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
- return annotationImage;
-}
-
-- (MGLAnnotationImage *)invisibleAnnotationImage
-{
- MGLAnnotationImage *annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLInvisibleStyleMarkerSymbolName];
-
- if (!annotationImage)
- {
- UIGraphicsBeginImageContext(CGSizeMake(1, 1));
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- annotationImage = [MGLAnnotationImage annotationImageWithImage:image
- reuseIdentifier:MGLInvisibleStyleMarkerSymbolName];
- annotationImage.styleIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
- }
-
- return annotationImage;
-}
-
-- (MGLAnnotationView *)annotationViewForAnnotation:(id<MGLAnnotation>)annotation
-{
- MGLAnnotationView *annotationView = [self.delegate mapView:self viewForAnnotation:annotation];
-
- if (annotationView)
- {
- // Make sure that the annotation views are selected/deselected correctly because
- // annotations are not dismissed when they move out of the visible bounds
- BOOL isViewForSelectedAnnotation = self.selectedAnnotation == annotation;
- [annotationView setSelected:isViewForSelectedAnnotation];
-
- annotationView.annotation = annotation;
- annotationView.mapView = self;
- CGRect bounds = UIEdgeInsetsInsetRect({ CGPointZero, annotationView.frame.size }, annotationView.alignmentRectInsets);
-
- // Take any offset into consideration
- CGFloat adjustedAnnotationWidth = CGRectGetWidth(bounds) + fabs(annotationView.centerOffset.dx);
- CGFloat adjustedAnnotationHeight = CGRectGetHeight(bounds) + fabs(annotationView.centerOffset.dx);
-
- _largestAnnotationViewSize = CGSizeMake(MAX(_largestAnnotationViewSize.width, adjustedAnnotationWidth),
- MAX(_largestAnnotationViewSize.height, adjustedAnnotationHeight));
-
- _unionedAnnotationRepresentationSize = CGSizeMake(MAX(_unionedAnnotationRepresentationSize.width, _largestAnnotationViewSize.width),
- MAX(_unionedAnnotationRepresentationSize.height, _largestAnnotationViewSize.height));
- }
-
- return annotationView;
-}
-
-- (nullable MGLAnnotationView *)viewForAnnotation:(id<MGLAnnotation>)annotation
-{
- MGLLogDebug(@"Retrieving the annotation view for: %@", annotation);
- if (annotation == self.userLocation)
- {
- return self.userLocationAnnotationView;
- }
- MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
- if (annotationTag == MGLAnnotationTagNotFound) {
- return nil;
- }
- MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
- return annotationContext.annotationView;
-}
-
-- (double)alphaForShapeAnnotation:(MGLShape *)annotation
-{
- if (_delegateHasAlphasForShapeAnnotations)
- {
- return [self.delegate mapView:self alphaForShapeAnnotation:annotation];
- }
- return 1.0;
-}
-
-- (mbgl::Color)strokeColorForShapeAnnotation:(MGLShape *)annotation
-{
- UIColor *color = (_delegateHasStrokeColorsForShapeAnnotations
- ? [self.delegate mapView:self strokeColorForShapeAnnotation:annotation]
- : self.tintColor);
- return color.mgl_color;
-}
-
-- (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation
-{
- UIColor *color = (_delegateHasFillColorsForShapeAnnotations
- ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation]
- : self.tintColor);
- return color.mgl_color;
-}
-
-- (CGFloat)lineWidthForPolylineAnnotation:(MGLPolyline *)annotation
-{
- if (_delegateHasLineWidthsForShapeAnnotations)
- {
- return [self.delegate mapView:self lineWidthForPolylineAnnotation:(MGLPolyline *)annotation];
- }
- return 3.0;
-}
-
-- (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage
-{
- NSString *iconIdentifier = annotationImage.styleIconIdentifier;
- self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage;
- annotationImage.delegate = self;
-
- // add sprite
- self.mbglMap.addAnnotationImage([annotationImage.image mgl_styleImageWithIdentifier:iconIdentifier]);
-
- // Create a slop area with a “radius” equal in size to the annotation
- // image’s alignment rect, allowing the eventual tap to be on any point
- // within this image. Union this slop area with any existing slop areas.
- CGRect bounds = UIEdgeInsetsInsetRect({ CGPointZero, annotationImage.image.size },
- annotationImage.image.alignmentRectInsets);
- _unionedAnnotationRepresentationSize = CGSizeMake(MAX(_unionedAnnotationRepresentationSize.width, bounds.size.width),
- MAX(_unionedAnnotationRepresentationSize.height, bounds.size.height));
-}
-
-- (void)removeAnnotation:(id <MGLAnnotation>)annotation
-{
- MGLLogDebug(@"Removing annotation: %@", annotation);
- if ( ! annotation) return;
-
- // The core bulk deletion API is efficient with respect to indexing
- // and screen refreshes, thus we should defer to it even for
- // individual deletes.
- //
- [self removeAnnotations:@[ annotation ]];
-}
-
-- (void)removeAnnotations:(NSArray<id <MGLAnnotation>> *)annotations
-{
- MGLLogDebug(@"Removing: %lu annotations", annotations.count);
- if ( ! annotations) return;
-
- [self willChangeValueForKey:@"annotations"];
-
- for (id <MGLAnnotation> annotation in annotations)
- {
- MGLAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"annotation should conform to MGLAnnotation");
-
- MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
- if (annotationTag == MGLAnnotationTagNotFound)
- {
- continue;
- }
-
- MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
- MGLAnnotationView *annotationView = annotationContext.annotationView;
-
- if (annotationContext.viewReuseIdentifier)
- {
- NSMutableArray *annotationViewReuseQueue = [self annotationViewReuseQueueForIdentifier:annotationContext.viewReuseIdentifier];
- if (![annotationViewReuseQueue containsObject:annotationView])
- {
- [annotationViewReuseQueue removeObject:annotationView];
- }
- }
-
- annotationView.annotation = nil;
- [annotationView removeFromSuperview];
- [self.annotationContainerView.annotationViews removeObject:annotationView];
-
- if (annotationTag == _selectedAnnotationTag)
- {
- [self deselectAnnotation:annotation animated:NO];
- }
-
- _annotationContextsByAnnotationTag.erase(annotationTag);
- _annotationTagsByAnnotation.erase(annotation);
-
- if ([annotation isKindOfClass:[NSObject class]] && ![annotation isKindOfClass:[MGLMultiPoint class]])
- {
- [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinate" context:(void *)(NSUInteger)annotationTag];
- }
- else if ([annotation isKindOfClass:[MGLMultiPoint class]])
- {
- [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinates" context:(void *)(NSUInteger)annotationTag];
- }
-
- _isChangingAnnotationLayers = YES;
- self.mbglMap.removeAnnotation(annotationTag);
- }
-
- [self updatePresentsWithTransaction];
-
- [self didChangeValueForKey:@"annotations"];
- UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
- if (_isChangingAnnotationLayers)
- {
- [self.style willChangeValueForKey:@"layers"];
- }
-}
-
-- (nonnull NSArray<id <MGLOverlay>> *)overlays
-{
- if (self.annotations == nil) { return @[]; }
-
- NSMutableArray<id <MGLOverlay>> *mutableOverlays = [NSMutableArray array];
-
- [self.annotations enumerateObjectsUsingBlock:^(id<MGLAnnotation> _Nonnull annotation, NSUInteger idx, BOOL * _Nonnull stop) {
- if ([annotation conformsToProtocol:@protocol(MGLOverlay)])
- {
- [mutableOverlays addObject:(id<MGLOverlay>)annotation];
- }
- }];
-
- return [NSArray arrayWithArray:mutableOverlays];
-}
-
-- (void)addOverlay:(id <MGLOverlay>)overlay
-{
- MGLLogDebug(@"Adding overlay: %@", overlay);
- [self addOverlays:@[ overlay ]];
-}
-
-- (void)addOverlays:(NSArray<id <MGLOverlay>> *)overlays
-{
- MGLLogDebug(@"Adding: %lu overlays", overlays.count);
-#if DEBUG
- for (id <MGLOverlay> overlay in overlays)
- {
- MGLAssert([overlay conformsToProtocol:@protocol(MGLOverlay)], @"overlay should conform to MGLOverlay");
- }
-#endif
-
- [self addAnnotations:overlays];
-}
-
-- (void)removeOverlay:(id <MGLOverlay>)overlay
-{
- MGLLogDebug(@"Removing overlay: %@", overlay);
- [self removeOverlays:@[ overlay ]];
-}
-
-- (void)removeOverlays:(NSArray<id <MGLOverlay>> *)overlays
-{
- MGLLogDebug(@"Removing: %lu overlays", overlays.count);
-#if DEBUG
- for (id <MGLOverlay> overlay in overlays)
- {
- MGLAssert([overlay conformsToProtocol:@protocol(MGLOverlay)], @"overlay should conform to MGLOverlay");
- }
-#endif
-
- [self removeAnnotations:overlays];
-}
-
-- (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier
-{
- return self.annotationImagesByIdentifier[identifier];
-}
-
-- (nullable MGLAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier
-{
- NSMutableArray *annotationViewReuseQueue = [self annotationViewReuseQueueForIdentifier:identifier];
- MGLAnnotationView *reusableView = annotationViewReuseQueue.firstObject;
- [reusableView prepareForReuse];
- [annotationViewReuseQueue removeObject:reusableView];
-
- return reusableView;
-}
-
-/**
- Returns the tag of the annotation at the given point in the view.
-
- This is more involved than it sounds: if multiple point annotations overlap
- near the point, this method cycles through them so that each of them is
- accessible to the user at some point.
-
- @param persist True to remember the cycleable set of annotations, so that a
- different annotation is returned the next time this method is called
- with the same point. Setting this parameter to false is useful for
- asking “what if?”
- */
-- (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)persist
-{
- // Look for any annotation near the tap. An annotation is “near” if the
- // distance between its center and the tap is less than the maximum height
- // or width of an installed annotation image or annotation view.
- CGRect queryRect = CGRectInset({ point, CGSizeZero },
- -_unionedAnnotationRepresentationSize.width,
- -_unionedAnnotationRepresentationSize.height);
- 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())
- {
- // Assume that the user is fat-fingering an annotation.
- 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) {
- id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
- MGLAssert(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 = MGLPointRounded([self convertCoordinate:annotation.coordinate toPointToView:self]);
- CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2);
-
- // We need to take any offset into consideration. Note that a large offset will result in a
- // large value for `_unionedAnnotationRepresentationSize` (and thus a larger feature query rect).
- // Aim to keep the offset as small as possible.
- frame = CGRectOffset(frame, annotationView.centerOffset.dx, annotationView.centerOffset.dy);
-
- annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets);
- }
- else
- {
- if ([annotation isKindOfClass:[MGLMultiPoint class]])
- {
- if ([self.delegate respondsToSelector:@selector(mapView:shapeAnnotationIsEnabled:)]) {
- return !!(![self.delegate mapView:self shapeAnnotationIsEnabled:(MGLMultiPoint *)annotation]);
- } else {
- 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;
- if (nearbyAnnotations.size())
- {
- // The first selection in the cycle should be the one nearest to the
- // tap. Also the annotation tags need to be stable in order to compare them with
- // the remembered tags _annotationsNearbyLastTap.
- CLLocationCoordinate2D currentCoordinate = [self convertPoint:point toCoordinateFromView:self];
- std::sort(nearbyAnnotations.begin(), nearbyAnnotations.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;
- });
-
- if (nearbyAnnotations == _annotationsNearbyLastTap)
- {
- // The last time we persisted a set of annotations, we had the same
- // set of annotations as we do now. Cycle through them.
- if (_selectedAnnotationTag == MGLAnnotationTagNotFound
- || _selectedAnnotationTag == nearbyAnnotations.back())
- {
- // Either no annotation is selected or the last annotation in
- // the set was selected. Wrap around to the first annotation in
- // the set.
- hitAnnotationTag = nearbyAnnotations.front();
- }
- else
- {
- auto result = std::find(nearbyAnnotations.begin(),
- nearbyAnnotations.end(),
- _selectedAnnotationTag);
- if (result == nearbyAnnotations.end())
- {
- // An annotation from this set hasn’t been selected before.
- // Select the first (nearest) one.
- hitAnnotationTag = nearbyAnnotations.front();
- }
- else
- {
- // Step to the next annotation in the set.
- auto distance = std::distance(nearbyAnnotations.begin(), result);
- hitAnnotationTag = nearbyAnnotations[distance + 1];
- }
- }
- }
- else
- {
- // Remember the nearby annotations for the next time this method is
- // called.
- if (persist)
- {
- _annotationsNearbyLastTap = nearbyAnnotations;
- }
-
- // Choose the first nearby annotation.
- if (nearbyAnnotations.size())
- {
- hitAnnotationTag = nearbyAnnotations.front();
- }
- }
- }
-
- return hitAnnotationTag;
-}
-
-/// Returns the tags of the annotations coincident with the given rectangle.
-- (std::vector<MGLAnnotationTag>)annotationTagsInRect:(CGRect)rect
-{
- return _rendererFrontend->getRenderer()->queryPointAnnotations({
- { CGRectGetMinX(rect), CGRectGetMinY(rect) },
- { CGRectGetMaxX(rect), CGRectGetMaxY(rect) },
- });
-}
-
-- (std::vector<MGLAnnotationTag>)shapeAnnotationTagsInRect:(CGRect)rect
-{
- return _rendererFrontend->getRenderer()->queryShapeAnnotations({
- { CGRectGetMinX(rect), CGRectGetMinY(rect) },
- { CGRectGetMaxX(rect), CGRectGetMaxY(rect) },
- });
-}
-
-
-- (BOOL)isMovingAnnotationIntoViewSupportedForAnnotation:(id<MGLAnnotation>)annotation animated:(BOOL)animated {
- // Consider delegating
- return [annotation isKindOfClass:[MGLPointAnnotation class]];
-}
-
-- (id <MGLAnnotation>)selectedAnnotation
-{
- if (_userLocationAnnotationIsSelected)
- {
- return self.userLocation;
- }
-
- if ( ! _annotationContextsByAnnotationTag.count(_selectedAnnotationTag) ||
- _selectedAnnotationTag == MGLAnnotationTagNotFound) {
- return nil;
- }
-
- MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(_selectedAnnotationTag);
- return annotationContext.annotation;
-}
-
-- (void)setSelectedAnnotation:(id <MGLAnnotation>)annotation
-{
- MGLLogDebug(@"Selecting annotation: %@", annotation);
- [self willChangeValueForKey:@"selectedAnnotations"];
- _selectedAnnotationTag = [self annotationTagForAnnotation:annotation];
- _userLocationAnnotationIsSelected = annotation && annotation == self.userLocation;
- [self didChangeValueForKey:@"selectedAnnotations"];
-}
-
-- (NSArray<id <MGLAnnotation>> *)selectedAnnotations
-{
- id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation;
- return (selectedAnnotation ? @[ selectedAnnotation ] : @[]);
-}
-
-- (void)setSelectedAnnotations:(NSArray<id <MGLAnnotation>> *)selectedAnnotations
-{
- if ( ! selectedAnnotations.count) return;
-
- id <MGLAnnotation> firstAnnotation = selectedAnnotations[0];
-
- MGLAssert([firstAnnotation conformsToProtocol:@protocol(MGLAnnotation)], @"annotation should conform to MGLAnnotation");
-
- if ([firstAnnotation isKindOfClass:[MGLMultiPoint class]]) return;
-
- [self selectAnnotation:firstAnnotation animated:YES completionHandler:nil];
-}
-
-- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated
-{
- [self selectAnnotation:annotation animated:animated completionHandler:nil];
-}
-
-- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
- [self selectAnnotation:annotation moveIntoView:animated animateSelection:animated completionHandler:completion];
-}
-
-- (void)selectAnnotation:(id <MGLAnnotation>)annotation moveIntoView:(BOOL)moveIntoView animateSelection:(BOOL)animateSelection completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Selecting annotation: %@ moveIntoView: %@ animateSelection: %@", annotation, MGLStringFromBOOL(moveIntoView), MGLStringFromBOOL(animateSelection));
- CGRect positioningRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:CGPointZero];
- [self selectAnnotation:annotation moveIntoView:moveIntoView animateSelection:animateSelection calloutPositioningRect:positioningRect completionHandler:completion];
-}
-
-- (void)selectAnnotation:(id <MGLAnnotation>)annotation moveIntoView:(BOOL)moveIntoView animateSelection:(BOOL)animateSelection calloutPositioningRect:(CGRect)calloutPositioningRect completionHandler:(nullable void (^)(void))completion
-{
- if ( ! annotation) return;
-
- if (annotation == self.selectedAnnotation) return;
-
- [self deselectAnnotation:self.selectedAnnotation animated:NO];
-
- // Add the annotation to the map if it hasn’t been added yet.
- MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
- if (annotationTag == MGLAnnotationTagNotFound && annotation != self.userLocation)
- {
- [self addAnnotation:annotation];
- annotationTag = [self annotationTagForAnnotation:annotation];
- if (annotationTag == MGLAnnotationTagNotFound) return;
- }
-
- MGLAnnotationView *annotationView = nil;
-
- if (annotation != self.userLocation)
- if (annotationTag != MGLAnnotationTagNotFound) {
- MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
- annotationView = annotationContext.annotationView;
- if (annotationView && annotationView.enabled) {
- // Annotations represented by views use the view frame as the positioning rect.
- calloutPositioningRect = annotationView.frame;
- [annotationView.superview bringSubviewToFront:annotationView];
-
- [annotationView setSelected:YES animated:animateSelection];
- }
- }
-
- self.selectedAnnotation = annotation;
-
- // Determine if we're allowed to move this offscreen annotation on screen, even though we've asked it to
- if (moveIntoView) {
- moveIntoView = [self isMovingAnnotationIntoViewSupportedForAnnotation:annotation animated:animateSelection];
- }
-
- // If we have an invalid positioning rect, we need to provide a suitable default.
- // This (currently) happens if you select an annotation that has NOT yet been
- // added. See https://github.com/mapbox/mapbox-gl-native/issues/11476
- if (CGRectIsNull(calloutPositioningRect)) {
- CLLocationCoordinate2D origin = annotation.coordinate;
- CGPoint originPoint = [self convertCoordinate:origin toPointToView:self];
- calloutPositioningRect = { .origin = originPoint, .size = CGSizeZero };
- }
-
- CGRect expandedPositioningRect = calloutPositioningRect;
-
- // Used for callout positioning, and moving offscreen annotations onscreen.
- CGRect constrainedRect = self.contentFrame;
- CGRect bounds = constrainedRect;
-
- BOOL expandedPositioningRectToMoveCalloutIntoViewWithMargins = NO;
-
- UIView <MGLCalloutView> *calloutView = nil;
-
- if ([annotation respondsToSelector:@selector(title)] &&
- annotation.title &&
- [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] &&
- [self.delegate mapView:self annotationCanShowCallout:annotation])
- {
- // build the callout
- if ([self.delegate respondsToSelector:@selector(mapView:calloutViewForAnnotation:)])
- {
- id providedCalloutView = [self.delegate mapView:self calloutViewForAnnotation:annotation];
- if (providedCalloutView) {
- if (![providedCalloutView isKindOfClass:[UIView class]]) {
- [NSException raise:NSInvalidArgumentException format:@"Callout view must be a kind of UIView"];
- }
- MGLAssert([providedCalloutView conformsToProtocol:@protocol(MGLCalloutView)], @"callout view must conform to MGLCalloutView");
- calloutView = providedCalloutView;
- }
- }
- if (!calloutView)
- {
- calloutView = [self calloutViewForAnnotation:annotation];
- }
- self.calloutViewForSelectedAnnotation = calloutView;
-
- if (_userLocationAnnotationIsSelected)
- {
- calloutPositioningRect = [self.userLocationAnnotationView.layer.presentationLayer frame];
-
- CGRect implicitAnnotationFrame = [self.userLocationAnnotationView.layer.presentationLayer frame];
- CGRect explicitAnnotationFrame = self.userLocationAnnotationView.frame;
- _initialImplicitCalloutViewOffset = CGPointMake(CGRectGetMinX(explicitAnnotationFrame) - CGRectGetMinX(implicitAnnotationFrame),
- CGRectGetMinY(explicitAnnotationFrame) - CGRectGetMinY(implicitAnnotationFrame));
- }
-
- // consult delegate for left and/or right accessory views
- if ([self.delegate respondsToSelector:@selector(mapView:leftCalloutAccessoryViewForAnnotation:)])
- {
- calloutView.leftAccessoryView = [self.delegate mapView:self leftCalloutAccessoryViewForAnnotation:annotation];
-
- if ([calloutView.leftAccessoryView isKindOfClass:[UIControl class]])
- {
- UITapGestureRecognizer *calloutAccessoryTap = [[UITapGestureRecognizer alloc] initWithTarget:self
- action:@selector(handleCalloutAccessoryTapGesture:)];
-
- [calloutView.leftAccessoryView addGestureRecognizer:calloutAccessoryTap];
- }
- }
-
- if ([self.delegate respondsToSelector:@selector(mapView:rightCalloutAccessoryViewForAnnotation:)])
- {
- calloutView.rightAccessoryView = [self.delegate mapView:self rightCalloutAccessoryViewForAnnotation:annotation];
-
- if ([calloutView.rightAccessoryView isKindOfClass:[UIControl class]])
- {
- UITapGestureRecognizer *calloutAccessoryTap = [[UITapGestureRecognizer alloc] initWithTarget:self
- action:@selector(handleCalloutAccessoryTapGesture:)];
-
- [calloutView.rightAccessoryView addGestureRecognizer:calloutAccessoryTap];
- }
- }
-
- // set annotation delegate to handle taps on the callout view
- calloutView.delegate = self;
-
- // If the callout view provides inset (outset) information, we can use it to expand our positioning
- // rect, which we then use to help move the annotation on-screen if want need to.
- if (moveIntoView && [calloutView respondsToSelector:@selector(marginInsetsHintForPresentationFromRect:)]) {
- UIEdgeInsets margins = [calloutView marginInsetsHintForPresentationFromRect:calloutPositioningRect];
- expandedPositioningRect = UIEdgeInsetsInsetRect(expandedPositioningRect, margins);
- expandedPositioningRectToMoveCalloutIntoViewWithMargins = YES;
- }
- }
-
- if (!expandedPositioningRectToMoveCalloutIntoViewWithMargins)
- {
- // We don't have a callout (OR our callout didn't implement
- // marginInsetsHintForPresentationFromRect: - in this case we need to
- // ensure that partially off-screen annotations are NOT moved into view.
- //
- // We may want to create (and fallback to) an `MGLMapViewDelegate` version
- // of the `-[MGLCalloutView marginInsetsHintForPresentationFromRect:]
- // protocol method.
- bounds = CGRectInset(bounds, -calloutPositioningRect.size.width, -calloutPositioningRect.size.height);
- }
-
- if (moveIntoView)
- {
- moveIntoView = NO;
-
- // Any one of these cases should trigger a move onscreen
- CGFloat minX = CGRectGetMinX(expandedPositioningRect);
-
- if (minX < CGRectGetMinX(bounds)) {
- constrainedRect.origin.x = minX;
- moveIntoView = YES;
- }
- else {
- CGFloat maxX = CGRectGetMaxX(expandedPositioningRect);
-
- if (maxX > CGRectGetMaxX(bounds)) {
- constrainedRect.origin.x = maxX - CGRectGetWidth(constrainedRect);
- moveIntoView = YES;
- }
- }
-
- CGFloat minY = CGRectGetMinY(expandedPositioningRect);
-
- if (minY < CGRectGetMinY(bounds)) {
- constrainedRect.origin.y = minY;
- moveIntoView = YES;
- }
- else {
- CGFloat maxY = CGRectGetMaxY(expandedPositioningRect);
-
- if (maxY > CGRectGetMaxY(bounds)) {
- constrainedRect.origin.y = maxY - CGRectGetHeight(constrainedRect);
- moveIntoView = YES;
- }
- }
- }
-
- // Remember, calloutView can be nil here.
- [calloutView presentCalloutFromRect:calloutPositioningRect
- inView:_mbglView->getView()
- constrainedToRect:constrainedRect
- animated:animateSelection];
-
- // Save the anchor coordinate
- if ([annotation isKindOfClass:[MGLPointAnnotation class]]) {
- self.anchorCoordinateForSelectedAnnotation = annotation.coordinate;
- }
- else {
- // This is used for features like polygons, so that if the map is dragged
- // the callout doesn't ping to its coordinate.
- CGPoint anchorPoint = CGPointMake(CGRectGetMidX(calloutPositioningRect), CGRectGetMidY(calloutPositioningRect));
- self.anchorCoordinateForSelectedAnnotation = [self convertPoint:anchorPoint toCoordinateFromView:self];
- }
-
- // notify delegate
- if ([self.delegate respondsToSelector:@selector(mapView:didSelectAnnotation:)])
- {
- [self.delegate mapView:self didSelectAnnotation:annotation];
- }
-
- if (annotationView && [self.delegate respondsToSelector:@selector(mapView:didSelectAnnotationView:)])
- {
- [self.delegate mapView:self didSelectAnnotationView:annotationView];
- }
-
- if (moveIntoView)
- {
- CGPoint center = CGPointMake(CGRectGetMidX(constrainedRect), CGRectGetMidY(constrainedRect));
- CLLocationCoordinate2D centerCoord = [self convertPoint:center toCoordinateFromView:self];
- [self setCenterCoordinate:centerCoord zoomLevel:self.zoomLevel direction:self.direction animated:animateSelection completionHandler:completion];
- }
- else if (completion)
- {
- completion();
- }
-}
-
-- (MGLCompactCalloutView *)calloutViewForAnnotation:(id <MGLAnnotation>)annotation
-{
- MGLCompactCalloutView *calloutView = [MGLCompactCalloutView platformCalloutView];
- calloutView.representedObject = annotation;
- calloutView.tintColor = self.tintColor;
-
- return calloutView;
-}
-
-/// 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];
-
- if (CGRectIsNull(positioningRect)) {
- return positioningRect;
- }
-
- // 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];
- if ( ! annotation)
- {
- return CGRectNull;
- }
-
- CLLocationCoordinate2D coordinate;
-
- if ((annotation == self.selectedAnnotation) &&
- CLLocationCoordinate2DIsValid(self.anchorCoordinateForSelectedAnnotation)) {
- coordinate = self.anchorCoordinateForSelectedAnnotation;
- }
- else {
- coordinate = annotation.coordinate;
- }
-
- if ([annotation isKindOfClass:[MGLMultiPoint class]]) {
- CLLocationCoordinate2D origin = coordinate;
- CGPoint originPoint = [self convertCoordinate:origin toPointToView:self];
- return CGRectMake(originPoint.x, originPoint.y, MGLAnnotationImagePaddingForHitTest, MGLAnnotationImagePaddingForHitTest);
- }
-
- UIImage *image = [self imageOfAnnotationWithTag:annotationTag].image;
- if ( ! image)
- {
- image = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName].image;
- }
- if ( ! image)
- {
- return CGRectZero;
- }
-
- CGRect positioningRect = [self frameOfImage:image centeredAtCoordinate:coordinate];
- positioningRect.origin.x -= 0.5;
-
- return CGRectInset(positioningRect, -MGLAnnotationImagePaddingForCallout,
- -MGLAnnotationImagePaddingForCallout);
-}
-
-/// Returns the rectangle relative to the viewport that represents the given
-/// image centered at the given coordinate.
-- (CGRect)frameOfImage:(UIImage *)image centeredAtCoordinate:(CLLocationCoordinate2D)coordinate
-{
- 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);
-}
-
-/// Returns the annotation image assigned to the annotation with the given tag.
-- (MGLAnnotationImage *)imageOfAnnotationWithTag:(MGLAnnotationTag)annotationTag
-{
- if (annotationTag == MGLAnnotationTagNotFound
- || _annotationContextsByAnnotationTag.count(annotationTag) == 0)
- {
- return nil;
- }
-
- NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier;
- NSString *symbolName = customSymbol.length ? customSymbol : MGLDefaultStyleMarkerSymbolName;
-
- return [self dequeueReusableAnnotationImageWithIdentifier:symbolName];
-}
-
-- (void)deselectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated
-{
- if ( ! annotation) return;
-
- if (self.selectedAnnotation == annotation)
- {
- MGLLogDebug(@"Deselecting annotation: %@ animated: %@", annotation, MGLStringFromBOOL(animated));
- // dismiss popup
- [self.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated];
-
- // deselect annotation view
- MGLAnnotationView *annotationView = nil;
- MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
-
- if (annotationTag != MGLAnnotationTagNotFound)
- {
- MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
- annotationView = annotationContext.annotationView;
- [annotationView setSelected:NO animated:animated];
- }
-
- // clean up
- self.calloutViewForSelectedAnnotation = nil;
- self.selectedAnnotation = nil;
- self.anchorCoordinateForSelectedAnnotation = kCLLocationCoordinate2DInvalid;
-
- // notify delegate
- if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)])
- {
- [self.delegate mapView:self didDeselectAnnotation:annotation];
- }
-
- if (annotationView && [self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotationView:)])
- {
- [self.delegate mapView:self didDeselectAnnotationView:annotationView];
- }
-
- [self updatePresentsWithTransaction];
- }
-}
-
-- (void)calloutViewWillAppear:(UIView <MGLCalloutView> *)calloutView
-{
- if (_userLocationAnnotationIsSelected ||
- CGPointEqualToPoint(_initialImplicitCalloutViewOffset, CGPointZero))
- {
- return;
- }
-
- __weak __typeof__(self) weakSelf = self;
-
- // The user location callout view initially points to the user location
- // annotation’s implicit (visual) frame, which is offset from the
- // annotation’s explicit frame. Now the callout view needs to rendezvous
- // with the explicit frame. Then,
- // -updateUserLocationAnnotationViewAnimatedWithDuration: will take over the
- // next time an updated location arrives.
- [UIView animateWithDuration:_userLocationAnimationCompletionDate.timeIntervalSinceNow
- delay:0
- options:(UIViewAnimationOptionCurveLinear |
- UIViewAnimationOptionAllowUserInteraction |
- UIViewAnimationOptionBeginFromCurrentState)
- animations:^
- {
- __typeof__(self) strongSelf = weakSelf;
- if ( ! strongSelf)
- {
- return;
- }
-
- calloutView.frame = CGRectOffset(calloutView.frame,
- strongSelf->_initialImplicitCalloutViewOffset.x,
- strongSelf->_initialImplicitCalloutViewOffset.y);
- strongSelf->_initialImplicitCalloutViewOffset = CGPointZero;
- }
- completion:NULL];
-}
-
-- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations animated:(BOOL)animated
-{
- CGFloat maximumPadding = 100;
- CGFloat yPadding = (self.frame.size.height / 5 <= maximumPadding) ? (self.frame.size.height / 5) : maximumPadding;
- CGFloat xPadding = (self.frame.size.width / 5 <= maximumPadding) ? (self.frame.size.width / 5) : maximumPadding;
-
- UIEdgeInsets edgeInsets = UIEdgeInsetsMake(yPadding, xPadding, yPadding, xPadding);
-
- [self showAnnotations:annotations edgePadding:edgeInsets animated:animated completionHandler:nil];
-}
-
-- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated
-{
- [self showAnnotations:annotations edgePadding:insets animated:animated completionHandler:nil];
-}
-
-- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Showing: %lu annotations edgePadding: %@ animated: %@", annotations.count, NSStringFromUIEdgeInsets(insets), MGLStringFromBOOL(animated));
- if ( ! annotations.count)
- {
- if (completion) {
- completion();
- }
- return;
- }
-
- mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
-
- for (id <MGLAnnotation> annotation in annotations)
- {
- if ([annotation conformsToProtocol:@protocol(MGLOverlay)])
- {
- bounds.extend(MGLLatLngBoundsFromCoordinateBounds(((id <MGLOverlay>)annotation).overlayBounds));
- }
- else
- {
- bounds.extend(MGLLatLngFromLocationCoordinate2D(annotation.coordinate));
- }
- }
-
- [self setVisibleCoordinateBounds:MGLCoordinateBoundsFromLatLngBounds(bounds)
- edgePadding:insets
- animated:animated
- completionHandler:completion];
-}
-
-
-#pragma mark Annotation Image Delegate
-
-- (void)annotationImageNeedsRedisplay:(MGLAnnotationImage *)annotationImage
-{
- NSString *reuseIdentifier = annotationImage.reuseIdentifier;
- NSString *iconIdentifier = annotationImage.styleIconIdentifier;
- NSString *fallbackReuseIdentifier = MGLDefaultStyleMarkerSymbolName;
- NSString *fallbackIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:fallbackReuseIdentifier];
-
- if (annotationImage.image)
- {
- // Add the new icon to the style.
- NSString *updatedIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
- annotationImage.styleIconIdentifier = updatedIconIdentifier;
- [self installAnnotationImage:annotationImage];
-
- if ([iconIdentifier isEqualToString:fallbackIconIdentifier])
- {
- // Update any annotations associated with the annotation image.
- [self applyIconIdentifier:updatedIconIdentifier toAnnotationsWithImageReuseIdentifier:reuseIdentifier];
- }
- }
- else
- {
- // Add the default icon to the style if necessary.
- annotationImage.styleIconIdentifier = fallbackIconIdentifier;
- if ( ! [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName])
- {
- [self installAnnotationImage:self.defaultAnnotationImage];
- }
-
- // Update any annotations associated with the annotation image.
- [self applyIconIdentifier:fallbackIconIdentifier toAnnotationsWithImageReuseIdentifier:reuseIdentifier];
- }
-}
-
-- (void)applyIconIdentifier:(NSString *)iconIdentifier toAnnotationsWithImageReuseIdentifier:(NSString *)reuseIdentifier
-{
- for (auto &pair : _annotationContextsByAnnotationTag)
- {
- if ([pair.second.imageReuseIdentifier isEqualToString:reuseIdentifier])
- {
- const mbgl::Point<double> point = MGLPointFromLocationCoordinate2D(pair.second.annotation.coordinate);
- self.mbglMap.updateAnnotation(pair.first, mbgl::SymbolAnnotation { point, iconIdentifier.UTF8String ?: "" });
- }
- }
-}
-
-#pragma mark - User Location -
-
-- (void)setLocationManager:(nullable id<MGLLocationManager>)locationManager
-{
- MGLLogDebug(@"Setting locationManager: %@", locationManager);
- if (!locationManager) {
- locationManager = [[MGLCLLocationManager alloc] init];
- }
- [_locationManager stopUpdatingLocation];
- [_locationManager stopUpdatingHeading];
- _locationManager.delegate = nil;
-
- _locationManager = locationManager;
- _locationManager.delegate = self;
-}
-
-- (void)validateLocationServices
-{
- BOOL shouldEnableLocationServices = self.showsUserLocation && !self.dormant;
-
- if (shouldEnableLocationServices)
- {
- if (self.locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined) {
- BOOL hasWhenInUseUsageDescription = !![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"];
-
- if (@available(iOS 11.0, *)) {
- // A WhenInUse string is required in iOS 11+ and the map never has any need for Always, so it's enough to just ask for WhenInUse.
- if (hasWhenInUseUsageDescription) {
- [self.locationManager requestWhenInUseAuthorization];
- } else {
- [NSException raise:MGLMissingLocationServicesUsageDescriptionException
- format:@"To use location services this app must have a NSLocationWhenInUseUsageDescription string in its Info.plist."];
- }
- } else {
- // We might have to ask for Always if the app does not provide a WhenInUse string.
- BOOL hasAlwaysUsageDescription = !![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"];
-
- if (hasWhenInUseUsageDescription) {
- [self.locationManager requestWhenInUseAuthorization];
- } else if (hasAlwaysUsageDescription) {
- [self.locationManager requestAlwaysAuthorization];
- } else {
- [NSException raise:MGLMissingLocationServicesUsageDescriptionException
- format:@"To use location services this app must have a NSLocationWhenInUseUsageDescription and/or NSLocationAlwaysUsageDescription string in its Info.plist."];
- }
- }
- }
-
- [self.locationManager startUpdatingLocation];
-
- [self validateUserHeadingUpdating];
- }
- else if ( ! shouldEnableLocationServices && self.locationManager)
- {
- [self.locationManager stopUpdatingLocation];
- [self.locationManager stopUpdatingHeading];
- }
-}
-
-- (void)setShowsUserLocation:(BOOL)showsUserLocation
-{
- MGLLogDebug(@"Setting showsUserLocation: %@", MGLStringFromBOOL(showsUserLocation));
- if (showsUserLocation == _showsUserLocation) return;
-
- _showsUserLocation = showsUserLocation;
-
- if (showsUserLocation)
- {
- if ([self.delegate respondsToSelector:@selector(mapViewWillStartLocatingUser:)])
- {
- [self.delegate mapViewWillStartLocatingUser:self];
- }
-
- self.userLocation = [[MGLUserLocation alloc] initWithMapView:self];
-
- MGLUserLocationAnnotationView *userLocationAnnotationView;
-
- if ([self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)])
- {
- userLocationAnnotationView = (MGLUserLocationAnnotationView *)[self.delegate mapView:self viewForAnnotation:self.userLocation];
- if (userLocationAnnotationView && ! [userLocationAnnotationView isKindOfClass:MGLUserLocationAnnotationView.class])
- {
- [NSException raise:MGLUserLocationAnnotationTypeException
- format:@"User location annotation view must be a kind of MGLUserLocationAnnotationView. %@", userLocationAnnotationView.debugDescription];
- }
- }
-
- self.userLocationAnnotationView = userLocationAnnotationView ?: [[MGLFaux3DUserLocationAnnotationView alloc] init];
- self.userLocationAnnotationView.mapView = self;
- self.userLocationAnnotationView.userLocation = self.userLocation;
-
- self.userLocationAnnotationView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin |
- UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
-
- [self validateLocationServices];
- }
- else
- {
- [self validateLocationServices];
-
- if ([self.delegate respondsToSelector:@selector(mapViewDidStopLocatingUser:)])
- {
- [self.delegate mapViewDidStopLocatingUser:self];
- }
-
- [self setUserTrackingMode:MGLUserTrackingModeNone animated:YES completionHandler:nil];
-
- [self.userLocationAnnotationView removeFromSuperview];
- self.userLocationAnnotationView = nil;
- }
-}
-
-- (void)setUserLocationAnnotationView:(MGLUserLocationAnnotationView *)newAnnotationView
-{
- if ( ! [newAnnotationView isEqual:_userLocationAnnotationView])
- {
- _userLocationAnnotationView = newAnnotationView;
- [self updateUserLocationAnnotationView];
- }
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingUserLocation
-{
- return [NSSet setWithObject:@"userLocationAnnotationView"];
-}
-
-- (BOOL)isUserLocationVisible
-{
- if (self.userLocationAnnotationView)
- {
- CGPoint locationPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self];
-
- CGRect locationRect = CGRectMake(locationPoint.x - self.userLocation.location.horizontalAccuracy,
- locationPoint.y - self.userLocation.location.horizontalAccuracy,
- self.userLocation.location.horizontalAccuracy * 2,
- self.userLocation.location.horizontalAccuracy * 2);
-
- return CGRectIntersectsRect([self bounds], locationRect);
- }
-
- return NO;
-}
-
-- (void)setUserTrackingMode:(MGLUserTrackingMode)mode
-{
- [self setUserTrackingMode:mode animated:YES completionHandler:nil];
-}
-
-- (void)setUserTrackingMode:(MGLUserTrackingMode)mode animated:(BOOL)animated
-{
- [self setUserTrackingMode:mode animated:animated completionHandler:nil];
-}
-
-- (void)setUserTrackingMode:(MGLUserTrackingMode)mode animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Setting userTrackingMode: %lu animated: %@", mode, MGLStringFromBOOL(animated));
- if (mode == _userTrackingMode)
- {
- if (completion)
- {
- completion();
- }
- return;
- }
-
- MGLUserTrackingMode oldMode = _userTrackingMode;
- [self willChangeValueForKey:@"userTrackingMode"];
- _userTrackingMode = mode;
- [self didChangeValueForKey:@"userTrackingMode"];
-
- switch (_userTrackingMode)
- {
- case MGLUserTrackingModeNone:
- {
- self.userTrackingState = MGLUserTrackingStatePossible;
-
- // Immediately update the annotation view; other cases update inside
- // the locationManager:didUpdateLocations: method.
- [self updateUserLocationAnnotationView];
-
- break;
- }
- case MGLUserTrackingModeFollow:
- case MGLUserTrackingModeFollowWithCourse:
- {
- self.userTrackingState = animated ? MGLUserTrackingStatePossible : MGLUserTrackingStateChanged;
- self.showsUserLocation = YES;
-
- break;
- }
- case MGLUserTrackingModeFollowWithHeading:
- {
- if (oldMode == MGLUserTrackingModeNone)
- {
- self.userTrackingState = animated ? MGLUserTrackingStatePossible : MGLUserTrackingStateChanged;
- }
-
- self.showsUserLocation = YES;
-
- if (self.zoomLevel < self.currentMinimumZoom)
- {
- [self setZoomLevel:self.currentMinimumZoom animated:YES];
- }
-
- break;
- }
- }
-
- CLLocation *location;
- if (_userTrackingMode != MGLUserTrackingModeNone && (location = self.userLocation.location) && self.userLocationAnnotationView)
- {
- [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated completionHandler:completion];
- }
- else if (completion)
- {
- completion();
- }
-
- [self validateUserHeadingUpdating];
-
- if ([self.delegate respondsToSelector:@selector(mapView:didChangeUserTrackingMode:animated:)])
- {
- [self.delegate mapView:self didChangeUserTrackingMode:_userTrackingMode animated:animated];
- }
-}
-
-- (void)setUserLocationVerticalAlignment:(MGLAnnotationVerticalAlignment)alignment
-{
- [self setUserLocationVerticalAlignment:alignment animated:YES];
-}
-
-- (void)setUserLocationVerticalAlignment:(MGLAnnotationVerticalAlignment)alignment animated:(BOOL)animated
-{
- _userLocationVerticalAlignment = alignment;
- if (self.userTrackingMode != MGLUserTrackingModeNone)
- {
- CLLocation *location = self.userLocation.location;
- if (location)
- {
- [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated completionHandler:nil];
- }
- }
-}
-
-- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate
-{
- [self setTargetCoordinate:targetCoordinate animated:YES completionHandler:nil];
-}
-
-- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate animated:(BOOL)animated
-{
- [self setTargetCoordinate:targetCoordinate animated:animated completionHandler:nil];
-}
-
-- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
- MGLLogDebug(@"Setting targetCoordinate: %@ animated: %@", MGLStringFromCLLocationCoordinate2D(targetCoordinate), MGLStringFromBOOL(animated));
- BOOL isSynchronous = YES;
- if (targetCoordinate.latitude != self.targetCoordinate.latitude
- || targetCoordinate.longitude != self.targetCoordinate.longitude)
- {
- _targetCoordinate = targetCoordinate;
- if (self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
- {
- self.userTrackingState = MGLUserTrackingStatePossible;
-
- if (CLLocation *location = self.userLocation.location)
- {
- isSynchronous = NO;
- [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated completionHandler:completion];
- }
- }
- }
- if (isSynchronous && completion)
- {
- completion();
- }
-}
-
-- (void)setShowsUserHeadingIndicator:(BOOL)showsUserHeadingIndicator
-{
- MGLLogDebug(@"Setting showsUserHeadingIndicator: %@", MGLStringFromBOOL(showsUserHeadingIndicator));
- _showsUserHeadingIndicator = showsUserHeadingIndicator;
-
- if (_showsUserHeadingIndicator)
- {
- self.showsUserLocation = YES;
- }
- [self validateUserHeadingUpdating];
-}
-
-- (void)validateUserHeadingUpdating
-{
- BOOL canShowPermanentHeadingIndicator = self.showsUserHeadingIndicator && self.userTrackingMode != MGLUserTrackingModeFollowWithCourse;
-
- if (canShowPermanentHeadingIndicator || self.userTrackingMode == MGLUserTrackingModeFollowWithHeading)
- {
- [self updateHeadingForDeviceOrientation];
- [self.locationManager startUpdatingHeading];
- }
- else
- {
- [self.locationManager stopUpdatingHeading];
- }
-}
-
-- (void)locationManager:(id<MGLLocationManager>)manager didUpdateLocations:(NSArray *)locations
-{
- [self locationManager:manager didUpdateLocations:locations animated:YES completionHandler:nil];
-}
-
-- (void)locationManager:(__unused id<MGLLocationManager>)manager didUpdateLocations:(NSArray *)locations animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
- CLLocation *oldLocation = self.userLocation.location;
- CLLocation *newLocation = locations.lastObject;
- _distanceFromOldUserLocation = [newLocation distanceFromLocation:oldLocation];
-
- if ( ! _showsUserLocation || ! newLocation || ! CLLocationCoordinate2DIsValid(newLocation.coordinate)) return;
-
- if (! oldLocation || ! CLLocationCoordinate2DIsValid(oldLocation.coordinate) || [newLocation distanceFromLocation:oldLocation]
- || oldLocation.course != newLocation.course)
- {
- if ( ! oldLocation || ! CLLocationCoordinate2DIsValid(oldLocation.coordinate) || self.userTrackingState != MGLUserTrackingStateBegan)
- {
- self.userLocation.location = newLocation;
- }
-
- if ([self.delegate respondsToSelector:@selector(mapView:didUpdateUserLocation:)])
- {
- [self.delegate mapView:self didUpdateUserLocation:self.userLocation];
- }
- }
-
- [self didUpdateLocationWithUserTrackingAnimated:animated completionHandler:completion];
-
- NSTimeInterval duration = MGLAnimationDuration;
- if (oldLocation && ! CGPointEqualToPoint(self.userLocationAnnotationView.center, CGPointZero))
- {
- duration = MIN([newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp], MGLUserLocationAnimationDuration);
- }
- [self updateUserLocationAnnotationViewAnimatedWithDuration:duration];
-
- if (self.userTrackingMode == MGLUserTrackingModeNone &&
- self.userLocationAnnotationView.accessibilityElementIsFocused &&
- [UIApplication sharedApplication].applicationState == UIApplicationStateActive)
- {
- UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.userLocationAnnotationView);
- }
-}
-
-- (void)didUpdateLocationWithUserTrackingAnimated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
- CLLocation *location = self.userLocation.location;
- if ( ! _showsUserLocation || ! location
- || ! CLLocationCoordinate2DIsValid(location.coordinate)
- || self.userTrackingMode == MGLUserTrackingModeNone)
- {
- if (completion)
- {
- completion();
- }
- return;
- }
-
- // If the user location annotation is already where it’s supposed to be,
- // don’t change the viewport.
- CGPoint correctPoint = self.userLocationAnnotationViewCenter;
- CGPoint currentPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self];
- if (std::abs(currentPoint.x - correctPoint.x) <= 1.0 && std::abs(currentPoint.y - correctPoint.y) <= 1.0
- && self.userTrackingMode != MGLUserTrackingModeFollowWithCourse)
- {
- if (completion)
- {
- completion();
- }
- return;
- }
-
- if (self.userTrackingMode == MGLUserTrackingModeFollowWithCourse
- && CLLocationCoordinate2DIsValid(self.targetCoordinate))
- {
- if (self.userTrackingState != MGLUserTrackingStateBegan)
- {
- // Keep both the user and the destination in view.
- [self didUpdateLocationWithTargetAnimated:animated completionHandler:completion];
- }
- }
- else if (self.userTrackingState == MGLUserTrackingStatePossible)
- {
- // The first location update is often a great distance away from the
- // current viewport, so fly there to provide additional context.
- [self didUpdateLocationSignificantlyAnimated:animated completionHandler:completion];
- }
- else if (self.userTrackingState == MGLUserTrackingStateChanged)
- {
- // Subsequent updates get a more subtle animation.
- [self didUpdateLocationIncrementallyAnimated:animated completionHandler:completion];
- }
- [self unrotateIfNeededAnimated:YES];
-}
-
-/// Changes the viewport based on an incremental location update.
-- (void)didUpdateLocationIncrementallyAnimated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
- [self _setCenterCoordinate:self.userLocation.location.coordinate
- edgePadding:self.edgePaddingForFollowing
- zoomLevel:self.zoomLevel
- direction:self.directionByFollowingWithCourse
- duration:animated ? MGLUserLocationAnimationDuration : 0
- animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]
- completionHandler:completion];
-}
-
-/// Changes the viewport based on a significant location update, such as the
-/// first location update.
-- (void)didUpdateLocationSignificantlyAnimated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
-
- if (_distanceFromOldUserLocation >= MGLDistanceThresholdForCameraPause) {
- self.userTrackingState = MGLUserTrackingStateBeginSignificantTransition;
- } else {
- self.userTrackingState = MGLUserTrackingStateBegan;
- }
-
- MGLMapCamera *camera = self.camera;
- camera.centerCoordinate = self.userLocation.location.coordinate;
- camera.heading = self.directionByFollowingWithCourse;
- if (self.zoomLevel < MGLMinimumZoomLevelForUserTracking)
- {
- camera.altitude = MGLAltitudeForZoomLevel(MGLDefaultZoomLevelForUserTracking,
- camera.pitch,
- camera.centerCoordinate.latitude,
- self.frame.size);
- }
-
- __weak MGLMapView *weakSelf = self;
- [self _flyToCamera:camera
- edgePadding:self.edgePaddingForFollowing
- withDuration:animated ? -1 : 0
- peakAltitude:-1
- completionHandler:^{
- MGLMapView *strongSelf = weakSelf;
- if (strongSelf.userTrackingState == MGLUserTrackingStateBegan ||
- strongSelf.userTrackingState == MGLDistanceThresholdForCameraPause)
- {
- strongSelf.userTrackingState = MGLUserTrackingStateChanged;
- }
- if (completion)
- {
- completion();
- }
- }];
-}
-
-/// Changes the viewport based on a location update in the presence of a target
-/// coordinate that must also be displayed on the map concurrently.
-- (void)didUpdateLocationWithTargetAnimated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
-{
- BOOL firstUpdate = self.userTrackingState == MGLUserTrackingStatePossible;
- void (^animationCompletion)(void);
- if (animated && firstUpdate)
- {
- self.userTrackingState = MGLUserTrackingStateBegan;
- __weak MGLMapView *weakSelf = self;
- animationCompletion = ^{
- MGLMapView *strongSelf = weakSelf;
- if (strongSelf.userTrackingState == MGLUserTrackingStateBegan)
- {
- strongSelf.userTrackingState = MGLUserTrackingStateChanged;
- }
- if (completion)
- {
- completion();
- }
- };
- }
-
- CLLocationCoordinate2D foci[] = {
- self.userLocation.location.coordinate,
- self.targetCoordinate,
- };
- UIEdgeInsets inset = self.edgePaddingForFollowingWithCourse;
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- if (self.userLocationVerticalAlignment == MGLAnnotationVerticalAlignmentCenter)
- {
- inset.bottom = CGRectGetMaxY(self.bounds) - CGRectGetMidY(self.contentFrame);
- }
-#pragma clang diagnostic pop
-
- [self _setVisibleCoordinates:foci
- count:sizeof(foci) / sizeof(foci[0])
- edgePadding:inset
- direction:self.directionByFollowingWithCourse
- duration:animated ? MGLUserLocationAnimationDuration : 0
- animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]
- completionHandler:animationCompletion];
-}
-
-/// Returns the edge padding to apply when moving the map to a tracked location.
-- (UIEdgeInsets)edgePaddingForFollowing
-{
- // Center on user location unless we're already centered there (or very close).
- CGPoint correctPoint = self.userLocationAnnotationViewCenter;
-
- // Shift the entire frame upward or downward to accommodate a shifted user
- // location annotation view.
- CGRect bounds = self.bounds;
- CGRect boundsAroundCorrectPoint = CGRectOffset(bounds,
- correctPoint.x - CGRectGetMidX(bounds),
- correctPoint.y - CGRectGetMidY(bounds));
- return UIEdgeInsetsMake(CGRectGetMinY(boundsAroundCorrectPoint) - CGRectGetMinY(bounds),
- CGRectGetMaxX(boundsAroundCorrectPoint) - CGRectGetMaxX(bounds),
- CGRectGetMaxY(bounds) - CGRectGetMaxY(boundsAroundCorrectPoint),
- CGRectGetMaxX(bounds) - CGRectGetMaxX(boundsAroundCorrectPoint));
-}
-
-/// Returns the edge padding to apply during bifocal course tracking.
-- (UIEdgeInsets)edgePaddingForFollowingWithCourse
-{
- UIEdgeInsets inset = MGLUserLocationAnnotationViewInset;
- inset.top += CGRectGetHeight(self.userLocationAnnotationView.frame);
- inset.bottom += CGRectGetHeight(self.userLocationAnnotationView.frame);
- return inset;
-}
-
-/// Returns the direction the map should be turned to due to course tracking.
-- (CLLocationDirection)directionByFollowingWithCourse
-{
- CLLocationDirection direction = -1;
- if (self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
- {
- if (CLLocationCoordinate2DIsValid(self.targetCoordinate))
- {
- mbgl::LatLng userLatLng = MGLLatLngFromLocationCoordinate2D(self.userLocation.coordinate);
- mbgl::LatLng targetLatLng = MGLLatLngFromLocationCoordinate2D(self.targetCoordinate);
- mbgl::ProjectedMeters userMeters = mbgl::Projection::projectedMetersForLatLng(userLatLng);
- mbgl::ProjectedMeters targetMeters = mbgl::Projection::projectedMetersForLatLng(targetLatLng);
- double angle = atan2(targetMeters.easting() - userMeters.easting(),
- targetMeters.northing() - userMeters.northing());
- direction = mbgl::util::wrap(MGLDegreesFromRadians(angle), 0., 360.);
- }
- else
- {
- direction = self.userLocation.location.course;
- }
-
- if (direction >= 0)
- {
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- if (self.userLocationVerticalAlignment == MGLAnnotationVerticalAlignmentTop)
- {
- direction += 180;
- }
-#pragma clang diagnostic pop
- }
- }
- return direction;
-}
-
-- (BOOL)locationManagerShouldDisplayHeadingCalibration:(id<MGLLocationManager>)manager
-{
- if (self.displayHeadingCalibration) [self performSelector:@selector(dismissHeadingCalibrationDisplay:)
- withObject:manager
- afterDelay:10.0];
-
- return self.displayHeadingCalibration;
-}
-
-- (void)dismissHeadingCalibrationDisplay:(id<MGLLocationManager>)manager
-{
- [manager dismissHeadingCalibrationDisplay];
-}
-
-- (void)locationManager:(__unused id<MGLLocationManager>)manager didUpdateHeading:(CLHeading *)newHeading
-{
- if ( ! _showsUserLocation || self.pan.state == UIGestureRecognizerStateBegan || newHeading.headingAccuracy < 0) return;
-
- self.userLocation.heading = newHeading;
-
- if (self.showsUserHeadingIndicator || self.userTrackingMode == MGLUserTrackingModeFollowWithHeading)
- {
- [self updateUserLocationAnnotationView];
- }
-
- if ([self.delegate respondsToSelector:@selector(mapView:didUpdateUserLocation:)])
- {
- [self.delegate mapView:self didUpdateUserLocation:self.userLocation];
-
- if ( ! _showsUserLocation) return;
- }
-
- CLLocationDirection headingDirection = (newHeading.trueHeading >= 0 ? newHeading.trueHeading : newHeading.magneticHeading);
-
- if (headingDirection >= 0 && self.userTrackingMode == MGLUserTrackingModeFollowWithHeading
- && self.userTrackingState != MGLUserTrackingStateBegan)
- {
- [self _setDirection:headingDirection animated:YES];
- }
-}
-
-- (void)locationManager:(__unused id<MGLLocationManager>)manager didFailWithError:(NSError *)error
-{
- if ([error code] == kCLErrorDenied)
- {
- self.userTrackingMode = MGLUserTrackingModeNone;
- self.showsUserLocation = NO;
-
- if ([self.delegate respondsToSelector:@selector(mapView:didFailToLocateUserWithError:)])
- {
- [self.delegate mapView:self didFailToLocateUserWithError:error];
- }
- }
-}
-
-- (void)updateHeadingForDeviceOrientation
-{
- if (self.locationManager)
- {
- // note that right/left device and interface orientations are opposites (see UIApplication.h)
- //
- CLDeviceOrientation orientation;
- switch ([[UIApplication sharedApplication] statusBarOrientation])
- {
- case (UIInterfaceOrientationLandscapeLeft):
- {
- orientation = CLDeviceOrientationLandscapeRight;
- break;
- }
- case (UIInterfaceOrientationLandscapeRight):
- {
- orientation = CLDeviceOrientationLandscapeLeft;
- break;
- }
- case (UIInterfaceOrientationPortraitUpsideDown):
- {
- orientation = CLDeviceOrientationPortraitUpsideDown;
- break;
- }
- case (UIInterfaceOrientationPortrait):
- default:
- {
- orientation = CLDeviceOrientationPortrait;
- break;
- }
- }
-
- // Setting the location manager's heading orientation causes it to send
- // a heading event, which in turn makes us redraw, which kicks off a
- // loop... so don't do that. rdar://34059173
- if (self.locationManager.headingOrientation != orientation)
- {
- self.locationManager.headingOrientation = orientation;
- }
- }
-}
-
-#pragma mark Data
-
-- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(CGPoint)point
-{
- MGLLogDebug(@"Querying visibleFeaturesAtPoint: %@", NSStringFromCGPoint(point));
- return [self visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:nil];
-}
-
-- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(CGPoint)point inStyleLayersWithIdentifiers:(NSSet<NSString *> *)styleLayerIdentifiers {
- MGLLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@", NSStringFromCGPoint(point), styleLayerIdentifiers);
- return [self visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:styleLayerIdentifiers predicate:nil];
-}
-
-- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(CGPoint)point inStyleLayersWithIdentifiers:(NSSet<NSString *> *)styleLayerIdentifiers predicate:(NSPredicate *)predicate
-{
- MGLLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", NSStringFromCGPoint(point), styleLayerIdentifiers, predicate);
- mbgl::ScreenCoordinate screenCoordinate = { point.x, point.y };
-
- mbgl::optional<std::vector<std::string>> optionalLayerIDs;
- if (styleLayerIdentifiers)
- {
- __block std::vector<std::string> layerIDs;
- layerIDs.reserve(styleLayerIdentifiers.count);
- [styleLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop)
- {
- layerIDs.push_back(identifier.UTF8String);
- }];
- optionalLayerIDs = layerIDs;
- }
-
- mbgl::optional<mbgl::style::Filter> optionalFilter;
- if (predicate) {
- optionalFilter = predicate.mgl_filter;
- }
-
- std::vector<mbgl::Feature> features = _rendererFrontend->getRenderer()->queryRenderedFeatures(screenCoordinate, { optionalLayerIDs, optionalFilter });
- return MGLFeaturesFromMBGLFeatures(features);
-}
-
-- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(CGRect)rect {
- MGLLogDebug(@"Querying visibleFeaturesInRect: %@", NSStringFromCGRect(rect));
- return [self visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:nil];
-}
-
-- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(CGRect)rect inStyleLayersWithIdentifiers:(NSSet<NSString *> *)styleLayerIdentifiers {
- MGLLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@", NSStringFromCGRect(rect), styleLayerIdentifiers);
- return [self visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:styleLayerIdentifiers predicate:nil];
-}
-
-- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(CGRect)rect inStyleLayersWithIdentifiers:(NSSet<NSString *> *)styleLayerIdentifiers predicate:(NSPredicate *)predicate {
- MGLLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", NSStringFromCGRect(rect), styleLayerIdentifiers, predicate);
- mbgl::ScreenBox screenBox = {
- { CGRectGetMinX(rect), CGRectGetMinY(rect) },
- { CGRectGetMaxX(rect), CGRectGetMaxY(rect) },
- };
-
- mbgl::optional<std::vector<std::string>> optionalLayerIDs;
- if (styleLayerIdentifiers) {
- __block std::vector<std::string> layerIDs;
- layerIDs.reserve(styleLayerIdentifiers.count);
- [styleLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) {
- layerIDs.push_back(identifier.UTF8String);
- }];
- optionalLayerIDs = layerIDs;
- }
-
- mbgl::optional<mbgl::style::Filter> optionalFilter;
- if (predicate) {
- optionalFilter = predicate.mgl_filter;
- }
-
- std::vector<mbgl::Feature> features = _rendererFrontend->getRenderer()->queryRenderedFeatures(screenBox, { optionalLayerIDs, optionalFilter });
- return MGLFeaturesFromMBGLFeatures(features);
-}
-
-#pragma mark - Utility -
-
-- (void)animateWithDelay:(NSTimeInterval)delay animations:(void (^)(void))animations
-{
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), animations);
-}
-
-- (CGFloat)currentMinimumZoom
-{
- return fmaxf(*self.mbglMap.getBounds().minZoom, MGLMinimumZoom);
-}
-
-- (BOOL)isRotationAllowed
-{
- return (self.zoomLevel >= self.currentMinimumZoom);
-}
-
-- (void)unrotateIfNeededForGesture
-{
- // Avoid contention with in-progress gestures.
- UIGestureRecognizerState state = self.pinch.state;
- if (self.direction != 0
- && state != UIGestureRecognizerStateBegan
- && state != UIGestureRecognizerStateChanged)
- {
- [self unrotateIfNeededAnimated:YES];
-
- // Snap to north.
- if ((self.direction < MGLToleranceForSnappingToNorth
- || self.direction > 360 - MGLToleranceForSnappingToNorth)
- && self.userTrackingMode != MGLUserTrackingModeFollowWithHeading
- && self.userTrackingMode != MGLUserTrackingModeFollowWithCourse)
- {
- [self resetNorthAnimated:YES];
- }
- }
-}
-
-/// Rotate back to true north if the map view is zoomed too far out.
-- (void)unrotateIfNeededAnimated:(BOOL)animated
-{
- if (self.direction != 0 && ! self.isRotationAllowed
- && self.userTrackingState != MGLUserTrackingStateBegan)
- {
- if (animated)
- {
- self.userInteractionEnabled = NO;
-
- __weak MGLMapView *weakSelf = self;
-
- [self animateWithDelay:0.1 animations:^
- {
- [weakSelf resetNorthAnimated:YES];
-
- [self animateWithDelay:MGLAnimationDuration animations:^
- {
- weakSelf.userInteractionEnabled = YES;
- }];
-
- }];
- }
- else
- {
- [self resetNorthAnimated:NO];
- }
- }
-}
-
-- (void)cameraWillChangeAnimated:(BOOL)animated {
- if (!_mbglMap)
- {
- return;
- }
-
- if ( ! _userLocationAnnotationIsSelected
- || self.userTrackingMode == MGLUserTrackingModeNone
- || self.userTrackingState != MGLUserTrackingStateChanged)
- {
- UIView<MGLCalloutView> *calloutView = self.calloutViewForSelectedAnnotation;
- BOOL dismissesAutomatically = (calloutView
- && [calloutView respondsToSelector:@selector(dismissesAutomatically)]
- && calloutView.dismissesAutomatically);
- // dismissesAutomatically is an optional property and we want to dismiss
- // the callout view if it's unimplemented.
- if (dismissesAutomatically || (calloutView && ![calloutView respondsToSelector:@selector(dismissesAutomatically)]))
- {
- [self deselectAnnotation:self.selectedAnnotation animated:NO];
- }
- }
-
- if ( ! [self isSuppressingChangeDelimiters] )
- {
- if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeWithReason:animated:)])
- {
- [self.delegate mapView:self regionWillChangeWithReason:self.cameraChangeReasonBitmask animated:animated];
- }
- else if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)])
- {
- [self.delegate mapView:self regionWillChangeAnimated:animated];
- }
- }
-}
-
-- (void)cameraIsChanging {
- if (!_mbglMap)
- {
- return;
- }
-
- [self updateCompass];
- [self updateScaleBar];
-
- if ([self.delegate respondsToSelector:@selector(mapView:regionIsChangingWithReason:)])
- {
- [self.delegate mapView:self regionIsChangingWithReason:self.cameraChangeReasonBitmask];
- }
- else if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)])
- {
- [self.delegate mapViewRegionIsChanging:self];
- }
-}
-
-- (void)cameraDidChangeAnimated:(BOOL)animated {
- if (!_mbglMap)
- {
- return;
- }
-
- [self updateCompass];
- [self updateScaleBar];
-
- if ( ! [self isSuppressingChangeDelimiters])
- {
- BOOL respondsToSelector = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)];
- BOOL respondsToSelectorWithReason = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeWithReason:animated:)];
-
- if ((respondsToSelector || respondsToSelectorWithReason) &&
- ([UIApplication sharedApplication].applicationState == UIApplicationStateActive))
- {
- _featureAccessibilityElements = nil;
- _visiblePlaceFeatures = nil;
- _visibleRoadFeatures = nil;
- if (_accessibilityValueAnnouncementIsPending) {
- _accessibilityValueAnnouncementIsPending = NO;
- [self performSelector:@selector(announceAccessibilityValue) withObject:nil afterDelay:0.1];
- } else {
- UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
- }
- }
-
- if (respondsToSelectorWithReason)
- {
- [self.delegate mapView:self regionDidChangeWithReason:self.cameraChangeReasonBitmask animated:animated];
- }
- else if (respondsToSelector)
- {
- [self.delegate mapView:self regionDidChangeAnimated:animated];
- }
-
- [self resetCameraChangeReason];
- }
-}
-
-- (void)announceAccessibilityValue
-{
- UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.accessibilityValue);
- UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
-}
-
-- (void)mapViewWillStartLoadingMap {
- if (!_mbglMap)
- {
- return;
- }
-
- if ([self.delegate respondsToSelector:@selector(mapViewWillStartLoadingMap:)])
- {
- [self.delegate mapViewWillStartLoadingMap:self];
- }
-}
-
-- (void)mapViewDidFinishLoadingMap {
- if (!_mbglMap)
- {
- return;
- }
-
- [self.style willChangeValueForKey:@"sources"];
- [self.style didChangeValueForKey:@"sources"];
- [self.style willChangeValueForKey:@"layers"];
- [self.style didChangeValueForKey:@"layers"];
- if ([self.delegate respondsToSelector:@selector(mapViewDidFinishLoadingMap:)])
- {
- [self.delegate mapViewDidFinishLoadingMap:self];
- }
-}
-
-- (void)mapViewDidFailLoadingMapWithError:(NSError *)error {
- if (!_mbglMap)
- {
- return;
- }
-
- if ([self.delegate respondsToSelector:@selector(mapViewDidFailLoadingMap:withError:)])
- {
- [self.delegate mapViewDidFailLoadingMap:self withError:error];
- }
-}
-
-- (void)mapViewWillStartRenderingFrame {
- if (!_mbglMap)
- {
- return;
- }
-
- if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingFrame:)])
- {
- [self.delegate mapViewWillStartRenderingFrame:self];
- }
-}
-
-- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered {
- if (!_mbglMap)
- {
- return;
- }
-
- if (_isChangingAnnotationLayers)
- {
- _isChangingAnnotationLayers = NO;
- [self.style didChangeValueForKey:@"layers"];
- }
-
- if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)])
- {
- [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered];
- }
-}
-
-- (void)mapViewWillStartRenderingMap {
- if (!_mbglMap)
- {
- return;
- }
-
- if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingMap:)])
- {
- [self.delegate mapViewWillStartRenderingMap:self];
- }
-}
-
-- (void)mapViewDidFinishRenderingMapFullyRendered:(BOOL)fullyRendered {
- if (!_mbglMap)
- {
- return;
- }
-
- UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
-
- if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)])
- {
- [self.delegate mapViewDidFinishRenderingMap:self fullyRendered:fullyRendered];
- }
-}
-
-- (void)mapViewDidBecomeIdle {
- if (!_mbglMap) {
- return;
- }
-
- if ([self.delegate respondsToSelector:@selector(mapViewDidBecomeIdle:)]) {
- [self.delegate mapViewDidBecomeIdle:self];
- }
-}
-
-- (void)mapViewDidFinishLoadingStyle {
- if (!_mbglMap)
- {
- return;
- }
-
- self.style = [[MGLStyle alloc] initWithRawStyle:&self.mbglMap.getStyle() mapView:self];
- if ([self.delegate respondsToSelector:@selector(mapView:didFinishLoadingStyle:)])
- {
- [self.delegate mapView:self didFinishLoadingStyle:self.style];
- }
-}
-
-- (void)sourceDidChange:(MGLSource *)source {
- // no-op: we only show attribution after tapping the info button, so there's no
- // interactive update needed.
-}
-
-- (void)didFailToLoadImage:(NSString *)imageName {
-
- if ([self.delegate respondsToSelector:@selector(mapView:didFailToLoadImage:)]) {
- MGLImage *imageToLoad = [self.delegate mapView:self didFailToLoadImage:imageName];
- if (imageToLoad) {
- auto image = [imageToLoad mgl_styleImageWithIdentifier:imageName];
- _mbglMap->getStyle().addImage(std::move(image));
- }
- }
-}
-
-- (BOOL)shouldRemoveStyleImage:(NSString *)imageName {
- if ([self.delegate respondsToSelector:@selector(mapView:shouldRemoveStyleImage:)]) {
- return [self.delegate mapView:self shouldRemoveStyleImage:imageName];
- }
-
- return YES;
-}
-
-- (void)updateUserLocationAnnotationView
-{
- [self updateUserLocationAnnotationViewAnimatedWithDuration:0];
-}
-
-- (void)updateAnnotationViews
-{
- BOOL delegateImplementsViewForAnnotation = [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)];
-
- if (!delegateImplementsViewForAnnotation)
- {
- return;
- }
-
- // If the map is pitched consider the viewport to be exactly the same as the bounds.
- // Otherwise, add a small buffer.
- CGFloat largestWidth = MAX(_largestAnnotationViewSize.width, CGRectGetWidth(self.frame));
- CGFloat largestHeight = MAX(_largestAnnotationViewSize.height, CGRectGetHeight(self.frame));
- CGFloat widthAdjustment = self.camera.pitch > 0.0 ? 0.0 : -largestWidth * 2.0;
- CGFloat heightAdjustment = self.camera.pitch > 0.0 ? 0.0 : -largestHeight * 2.0;
- CGRect viewPort = CGRectInset(self.bounds, widthAdjustment, heightAdjustment);
-
- NSArray *visibleAnnotations = [self visibleAnnotationsInRect:viewPort];
- NSMutableArray *offscreenAnnotations = [self.annotations mutableCopy];
- [offscreenAnnotations removeObjectsInArray:visibleAnnotations];
-
- // Update the center of visible annotation views
- for (id<MGLAnnotation> annotation in visibleAnnotations)
- {
- // Defer to the shape/polygon styling delegate methods
- if ([annotation isKindOfClass:[MGLMultiPoint class]])
- {
- continue;
- }
-
- // Get the annotation tag then use it to get the context.
- MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
- MGLAssert(annotationTag != MGLAnnotationTagNotFound, @"-visibleAnnotationsInRect: returned unrecognized annotation");
- MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
-
- MGLAnnotationView *annotationView = annotationContext.annotationView;
- if (!annotationView)
- {
- // This will dequeue views if the delegate implements the dequeue call
- annotationView = [self annotationViewForAnnotation:annotationContext.annotation];
-
- if (annotationView)
- {
- annotationView.mapView = self;
- annotationContext.annotationView = annotationView;
-
- // New annotation (created because there is nothing to dequeue) may not have been added to the
- // container view yet. Add them here.
- if (!annotationView.superview) {
- [self.annotationContainerView insertSubview:annotationView atIndex:0];
- }
- }
- }
-
- if (annotationView)
- {
- annotationView.center = MGLPointRounded([self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self]);
- }
- }
-
- MGLCoordinateBounds coordinateBounds = [self convertRect:viewPort toCoordinateBoundsFromView:self];
-
- // Enqueue (and move if required) offscreen annotation views
- for (id<MGLAnnotation> annotation in offscreenAnnotations)
- {
- // Defer to the shape/polygon styling delegate methods
- if ([annotation isKindOfClass:[MGLMultiPoint class]])
- {
- continue;
- }
-
- MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
- MGLAssert(annotationTag != MGLAnnotationTagNotFound, @"-visibleAnnotationsInRect: returned unrecognized annotation");
- MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
- UIView *annotationView = annotationContext.annotationView;
-
- if (annotationView)
- {
- CLLocationCoordinate2D coordinate = annotation.coordinate;
- // Every so often (1 out of 1000 frames?) the mbgl query mechanism fails. This logic spot checks the
- // offscreenAnnotations values -- if they are actually still on screen then the view center is
- // moved and the enqueue operation is avoided. This allows us to keep the performance benefit of
- // using the mbgl query result. It also forces views that have just gone offscreen to be cleared
- // fully from view.
- if (MGLCoordinateInCoordinateBounds(coordinate, coordinateBounds))
- {
- annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self];
- }
- else
- {
- if (annotationView.layer.animationKeys.count > 0) {
- continue;
- }
-
- // Move the annotation view far out of view to the left
- CGPoint adjustedCenter = annotationView.center;
- adjustedCenter.x = -CGRectGetWidth(self.frame) * 10.0;
- annotationView.center = adjustedCenter;
-
- [self enqueueAnnotationViewForAnnotationContext:annotationContext];
- }
- }
- }
-}
-
-- (BOOL)hasAnAnchoredAnnotationCalloutView
-{
- // TODO: Remove duplicate code.
- UIView <MGLCalloutView> *calloutView = self.calloutViewForSelectedAnnotation;
- id <MGLAnnotation> annotation = calloutView.representedObject;
-
- BOOL isAnchoredToAnnotation = (calloutView
- && annotation
- && [calloutView respondsToSelector:@selector(isAnchoredToAnnotation)]
- && calloutView.isAnchoredToAnnotation);
- return isAnchoredToAnnotation;
-}
-
-- (void)updateCalloutView
-{
- UIView <MGLCalloutView> *calloutView = self.calloutViewForSelectedAnnotation;
- id <MGLAnnotation> annotation = calloutView.representedObject;
-
- BOOL isAnchoredToAnnotation = (calloutView
- && annotation
- && [calloutView respondsToSelector:@selector(isAnchoredToAnnotation)]
- && calloutView.isAnchoredToAnnotation);
-
- if (isAnchoredToAnnotation)
- {
- MGLAnnotationTag tag = [self annotationTagForAnnotation:annotation];
- MGLAnnotationView *annotationView = nil;
-
- if (tag != MGLAnnotationTagNotFound) {
- MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(tag);
- annotationView = annotationContext.annotationView;
- } else if (annotation == self.userLocation) {
- annotationView = self.userLocationAnnotationView;
- }
-
- CGRect positioningRect = annotationView ?
- annotationView.frame :
- [self positioningRectForCalloutForAnnotationWithTag:tag];
-
- MGLAssert( ! CGRectIsNull(positioningRect), @"Positioning rect should not be CGRectNull by this point");
-
- CGPoint centerPoint = CGPointMake(CGRectGetMidX(positioningRect), CGRectGetMinY(positioningRect));
-
- if ( ! CGPointEqualToPoint(calloutView.center, centerPoint)) {
- calloutView.center = centerPoint;
- }
- }
-}
-
-- (void)updateAttributionAlertView {
- if (self.attributionController.presentingViewController) {
- self.attributionController.popoverPresentationController.sourceRect = self.attributionButton.frame;
- switch (self.attributionButtonPosition) {
- case MGLOrnamentPositionTopLeft:
- case MGLOrnamentPositionTopRight:
- [self.attributionController.popoverPresentationController setPermittedArrowDirections:UIMenuControllerArrowUp];
- break;
- case MGLOrnamentPositionBottomLeft:
- case MGLOrnamentPositionBottomRight:
- [self.attributionController.popoverPresentationController setPermittedArrowDirections:UIMenuControllerArrowDown];
- break;
- }
- [self.attributionController.popoverPresentationController.containerView setNeedsLayout];
- }
-}
-
-- (void)enqueueAnnotationViewForAnnotationContext:(MGLAnnotationContext &)annotationContext
-{
- MGLAnnotationView *annotationView = annotationContext.annotationView;
-
- if (!annotationView) return;
-
- if (annotationContext.viewReuseIdentifier)
- {
- annotationView.annotation = nil;
- NSMutableArray *annotationViewReuseQueue = [self annotationViewReuseQueueForIdentifier:annotationContext.viewReuseIdentifier];
- if (![annotationViewReuseQueue containsObject:annotationView])
- {
- [annotationViewReuseQueue addObject:annotationView];
- annotationContext.annotationView = nil;
- }
- }
-}
-
-- (void)updateUserLocationAnnotationViewAnimatedWithDuration:(NSTimeInterval)duration
-{
- MGLUserLocationAnnotationView *annotationView = self.userLocationAnnotationView;
- if ( ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate)) {
- annotationView.hidden = YES;
- return;
- }
-
- CGPoint userPoint;
- if (self.userTrackingMode != MGLUserTrackingModeNone
- && self.userTrackingState == MGLUserTrackingStateChanged)
- {
- userPoint = self.userLocationAnnotationViewCenter;
- }
- else
- {
- userPoint = MGLPointRounded([self convertCoordinate:self.userLocation.coordinate toPointToView:self]);
- }
-
- if ( ! annotationView.superview)
- {
- [_mbglView->getView() addSubview:annotationView];
- // Prevents the view from sliding in from the origin.
- annotationView.center = userPoint;
- }
-
- if (CGRectContainsPoint(CGRectInset(self.bounds, -MGLAnnotationUpdateViewportOutset.width,
- -MGLAnnotationUpdateViewportOutset.height), userPoint))
- {
- // Smoothly move the user location annotation view and callout view to
- // the new location.
-
- dispatch_block_t animation = ^{
- if (self.selectedAnnotation == self.userLocation)
- {
- UIView <MGLCalloutView> *calloutView = self.calloutViewForSelectedAnnotation;
- calloutView.frame = CGRectOffset(calloutView.frame,
- userPoint.x - annotationView.center.x,
- userPoint.y - annotationView.center.y);
- }
- annotationView.center = userPoint;
- };
-
- if (duration > 0) {
- [UIView animateWithDuration:duration
- delay:0
- options:(UIViewAnimationOptionCurveLinear |
- UIViewAnimationOptionAllowUserInteraction |
- UIViewAnimationOptionBeginFromCurrentState)
- animations:animation
- completion:NULL];
- }
- else {
- animation();
- }
- _userLocationAnimationCompletionDate = [NSDate dateWithTimeIntervalSinceNow:duration];
-
- annotationView.hidden = NO;
- [annotationView update];
- }
- else
- {
- // User has moved far enough outside of the viewport that showing it or
- // its callout would be useless.
- annotationView.hidden = YES;
-
- if (_userLocationAnnotationIsSelected)
- {
- [self deselectAnnotation:self.selectedAnnotation animated:YES];
- }
- }
-}
-
-/// Intended center point of the user location annotation view with respect to
-/// the overall map view (but respecting the content inset).
-- (CGPoint)userLocationAnnotationViewCenter
-{
- if ([self.delegate respondsToSelector:@selector(mapViewUserLocationAnchorPoint:)])
- {
- CGPoint anchorPoint = [self.delegate mapViewUserLocationAnchorPoint:self];
- return CGPointMake(anchorPoint.x + self.contentInset.left, anchorPoint.y + self.contentInset.top);
- }
-
- CGRect contentFrame = UIEdgeInsetsInsetRect(self.contentFrame, self.edgePaddingForFollowingWithCourse);
-
- if (CGRectIsEmpty(contentFrame))
- {
- contentFrame = self.contentFrame;
- }
-
- CGPoint center = CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame));
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- switch (self.userLocationVerticalAlignment) {
- case MGLAnnotationVerticalAlignmentCenter:
- break;
- case MGLAnnotationVerticalAlignmentTop:
- center.y = CGRectGetMinY(contentFrame);
- break;
- case MGLAnnotationVerticalAlignmentBottom:
- center.y = CGRectGetMaxY(contentFrame);
- break;
- }
-#pragma clang diagnostic pop
-
- return center;
-}
-
-- (void)updateCompass
-{
- [self.compassView updateCompass];
-}
-
-- (void)updateScaleBar
-{
- // Use the `hidden` property (instead of `self.showsScale`) so that we don't
- // break developers who still rely on the <4.0.0 approach of directly
- // setting this property.
- if ( ! self.scaleBar.hidden)
- {
- [(MGLScaleBar *)self.scaleBar setMetersPerPoint:[self metersPerPointAtLatitude:self.centerCoordinate.latitude]];
- }
-}
-
-- (BOOL)isFullyLoaded
-{
- return self.mbglMap.isFullyLoaded();
-}
-
-- (void)prepareForInterfaceBuilder
-{
- [super prepareForInterfaceBuilder];
-
- self.layer.borderColor = [UIColor colorWithRed:59/255.
- green:178/255.
- blue:208/255.
- alpha:0.8].CGColor;
- self.layer.borderWidth = 4;
- self.layer.backgroundColor = [UIColor whiteColor].CGColor;
-
- UIView *diagnosticView = [[UIView alloc] init];
- diagnosticView.translatesAutoresizingMaskIntoConstraints = NO;
- [self addSubview:diagnosticView];
-
- // Headline
- UILabel *headlineLabel = [[UILabel alloc] init];
- headlineLabel.text = NSStringFromClass([self class]);
- headlineLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
- headlineLabel.textAlignment = NSTextAlignmentCenter;
- headlineLabel.numberOfLines = 1;
- headlineLabel.translatesAutoresizingMaskIntoConstraints = NO;
- [headlineLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
- forAxis:UILayoutConstraintAxisHorizontal];
- [diagnosticView addSubview:headlineLabel];
-
- // Explanation
- UILabel *explanationLabel = [[UILabel alloc] init];
- explanationLabel.text = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"DESIGNABLE", nil, nil, @"To display a Mapbox-hosted map here, set %@ to your access token in %@\n\nFor detailed instructions, see:", @"Instructions in Interface Builder designable; {key}, {plist file name}"), @"MGLMapboxAccessToken", @"Info.plist"];
- explanationLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
- explanationLabel.numberOfLines = 0;
- explanationLabel.translatesAutoresizingMaskIntoConstraints = NO;
- [explanationLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
- forAxis:UILayoutConstraintAxisHorizontal];
- [diagnosticView addSubview:explanationLabel];
-
- // Link
- UIButton *linkButton = [UIButton buttonWithType:UIButtonTypeSystem];
- [linkButton setTitle:NSLocalizedStringWithDefaultValue(@"FIRST_STEPS_URL", nil, nil, @"docs.mapbox.com/help/tutorials/first-steps-ios-sdk", @"Setup documentation URL display string; keep as short as possible") forState:UIControlStateNormal];
- linkButton.translatesAutoresizingMaskIntoConstraints = NO;
- linkButton.titleLabel.numberOfLines = 0;
- [linkButton setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
- forAxis:UILayoutConstraintAxisHorizontal];
- [diagnosticView addSubview:linkButton];
-
- // Constraints
- NSDictionary *views = @{
- @"container": diagnosticView,
- @"headline": headlineLabel,
- @"explanation": explanationLabel,
- @"link": linkButton,
- };
- [self addConstraint:
- [NSLayoutConstraint constraintWithItem:diagnosticView
- attribute:NSLayoutAttributeCenterYWithinMargins
- relatedBy:NSLayoutRelationEqual
- toItem:self
- attribute:NSLayoutAttributeCenterYWithinMargins
- multiplier:1
- constant:0]];
- [self addConstraint:
- [NSLayoutConstraint constraintWithItem:diagnosticView
- attribute:NSLayoutAttributeTopMargin
- relatedBy:NSLayoutRelationGreaterThanOrEqual
- toItem:self
- attribute:NSLayoutAttributeTopMargin
- multiplier:1
- constant:8]];
- [self addConstraint:
- [NSLayoutConstraint constraintWithItem:self
- attribute:NSLayoutAttributeBottomMargin
- relatedBy:NSLayoutRelationGreaterThanOrEqual
- toItem:diagnosticView
- attribute:NSLayoutAttributeBottomMargin
- multiplier:1
- constant:8]];
- [self addConstraints:
- [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[container(20@20)]-|"
- options:NSLayoutFormatAlignAllCenterY
- metrics:nil
- views:views]];
- [self addConstraints:
- [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[headline]-[explanation]-[link]|"
- options:0
- metrics:nil
- views:views]];
- [self addConstraints:
- [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[headline]|"
- options:0
- metrics:nil
- views:views]];
- [self addConstraints:
- [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[explanation]|"
- options:0
- metrics:nil
- views:views]];
- [self addConstraints:
- [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[link]|"
- options:0
- metrics:nil
- views:views]];
-}
-
-- (NSMutableArray<MGLAnnotationView *> *)annotationViewReuseQueueForIdentifier:(NSString *)identifier {
- if (!_annotationViewReuseQueueByIdentifier[identifier])
- {
- _annotationViewReuseQueueByIdentifier[identifier] = [NSMutableArray array];
- }
-
- return _annotationViewReuseQueueByIdentifier[identifier];
-}
-
-@end
-
-#pragma mark - IBAdditions methods
-
-@implementation MGLMapView (IBAdditions)
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingStyleURL__
-{
- return [NSSet setWithObject:@"styleURL"];
-}
-
-- (nullable NSString *)styleURL__
-{
- return self.styleURL.absoluteString;
-}
-
-- (void)setStyleURL__:(nullable NSString *)URLString
-{
- URLString = [URLString stringByTrimmingCharactersInSet:
- [NSCharacterSet whitespaceAndNewlineCharacterSet]];
- NSURL *url = URLString.length ? [NSURL URLWithString:URLString] : nil;
- if (URLString.length && !url)
- {
- [NSException raise:MGLInvalidStyleURLException
- format:@"“%@” is not a valid style URL.", URLString];
- }
- self.styleURL = url;
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingLatitude
-{
- return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil];
-}
-
-- (double)latitude
-{
- return self.centerCoordinate.latitude;
-}
-
-- (void)setLatitude:(double)latitude
-{
- if ( ! isnan(_pendingLongitude))
- {
- self.centerCoordinate = CLLocationCoordinate2DMake(latitude, _pendingLongitude);
- _pendingLatitude = NAN;
- _pendingLongitude = NAN;
- }
- else
- {
- // Not enough info to make a valid center coordinate yet. Stash this
- // latitude away until the longitude is set too.
- _pendingLatitude = latitude;
- }
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingLongitude
-{
- return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil];
-}
-
-- (double)longitude
-{
- return self.centerCoordinate.longitude;
-}
-
-- (void)setLongitude:(double)longitude
-{
- if ( ! isnan(_pendingLatitude))
- {
- self.centerCoordinate = CLLocationCoordinate2DMake(_pendingLatitude, longitude);
- _pendingLatitude = NAN;
- _pendingLongitude = NAN;
- }
- else
- {
- // Not enough info to make a valid center coordinate yet. Stash this
- // longitude away until the latitude is set too.
- _pendingLongitude = longitude;
- }
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingAllowsZooming
-{
- return [NSSet setWithObject:@"zoomEnabled"];
-}
-
-- (BOOL)allowsZooming
-{
- return self.zoomEnabled;
-}
-
-- (void)setAllowsZooming:(BOOL)allowsZooming
-{
- MGLLogDebug(@"Setting allowsZooming: %@", MGLStringFromBOOL(allowsZooming));
- self.zoomEnabled = allowsZooming;
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingAllowsScrolling
-{
- return [NSSet setWithObject:@"scrollEnabled"];
-}
-
-- (BOOL)allowsScrolling
-{
- return self.scrollEnabled;
-}
-
-- (void)setAllowsScrolling:(BOOL)allowsScrolling
-{
- MGLLogDebug(@"Setting allowsScrolling: %@", MGLStringFromBOOL(allowsScrolling));
- self.scrollEnabled = allowsScrolling;
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingAllowsRotating
-{
- return [NSSet setWithObject:@"rotateEnabled"];
-}
-
-- (BOOL)allowsRotating
-{
- return self.rotateEnabled;
-}
-
-- (void)setAllowsRotating:(BOOL)allowsRotating
-{
- MGLLogDebug(@"Setting allowsRotating: %@", MGLStringFromBOOL(allowsRotating));
- self.rotateEnabled = allowsRotating;
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingAllowsTilting
-{
- return [NSSet setWithObject:@"pitchEnabled"];
-}
-
-- (BOOL)allowsTilting
-{
- return self.pitchEnabled;
-}
-
-- (void)setAllowsTilting:(BOOL)allowsTilting
-{
- MGLLogDebug(@"Setting allowsTilting: %@", MGLStringFromBOOL(allowsTilting));
- self.pitchEnabled = allowsTilting;
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingShowsHeading
-{
- return [NSSet setWithObject:@"showsUserHeadingIndicator"];
-}
-
-- (BOOL)showsHeading
-{
- return self.showsUserHeadingIndicator;
-}
-
-- (void)setShowsHeading:(BOOL)showsHeading
-{
- MGLLogDebug(@"Setting showsHeading: %@", MGLStringFromBOOL(showsHeading));
- self.showsUserHeadingIndicator = showsHeading;
-}
-
-@end
diff --git a/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/src/MGLMapViewDelegate.h
deleted file mode 100644
index 3ddb7b007f..0000000000
--- a/platform/ios/src/MGLMapViewDelegate.h
+++ /dev/null
@@ -1,775 +0,0 @@
-#import <UIKit/UIKit.h>
-
-#import "Mapbox.h"
-#import "MGLCameraChangeReason.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@class MGLMapView;
-
-/**
- The `MGLMapViewDelegate` protocol defines a set of optional methods that you
- can use to receive map-related update messages. Because many map operations
- require the `MGLMapView` class to load data asynchronously, the map view calls
- these methods to notify your application when specific operations complete. The
- map view also uses these methods to request information about annotations
- displayed on the map, such as the styles and interaction modes to apply to
- individual annotations.
- */
-@protocol MGLMapViewDelegate <NSObject>
-
-@optional
-
-#pragma mark Responding to Map Position Changes
-
-/**
- Asks the delegate whether the map view should be allowed to change from the
- existing camera to the new camera in response to a user gesture.
-
- This method is called as soon as the user gesture is recognized. It is not
- called in response to a programmatic camera change, such as by setting the
- `centerCoordinate` property or calling `-flyToCamera:completionHandler:`.
-
- This method is called many times during gesturing, so you should avoid performing
- complex or performance-intensive tasks in your implementation.
-
- @param mapView The map view that the user is manipulating.
- @param oldCamera The camera representing the viewpoint at the moment the
- gesture is recognized. If this method returns `NO`, the map view’s camera
- continues to be this camera.
- @param newCamera The expected camera after the gesture completes. If this
- method returns `YES`, this camera becomes the map view’s camera.
- @return A Boolean value indicating whether the map view should stay at
- `oldCamera` or change to `newCamera`.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/constraining-gestures/">
- Restrict map panning to an area</a> example to learn how to use this method
- and `MGLMapCamera` objects to restrict a users ability to pan your map.
- */
-- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera;
-
-/**
- :nodoc:
- Asks the delegate whether the map view should be allowed to change from the
- existing camera to the new camera in response to a user gesture.
-
- This method is called as soon as the user gesture is recognized. It is not
- called in response to a programmatic camera change, such as by setting the
- `centerCoordinate` property or calling `-flyToCamera:completionHandler:`.
-
- This method is called many times during gesturing, so you should avoid performing
- complex or performance-intensive tasks in your implementation.
-
- @param mapView The map view that the user is manipulating.
- @param oldCamera The camera representing the viewpoint at the moment the
- gesture is recognized. If this method returns `NO`, the map view’s camera
- continues to be this camera.
- @param newCamera The expected camera after the gesture completes. If this
- method returns `YES`, the viewport of the map will transition to the new camera. Note that the new camera cannot be modified.
- @param reason The reason for the camera change.
- @return A Boolean value indicating whether the map view should stay at
- `oldCamera` or transition to `newCamera`.
-
- @note If this method is implemented `-mapView:shouldChangeFromCamera:toCamera:` will not be called.
- */
-- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera reason:(MGLCameraChangeReason)reason;
-
-/**
- Tells the delegate that the viewpoint depicted by the map view is about to change.
-
- This method is called whenever the currently displayed map camera will start
- changing for any reason.
-
- @param mapView The map view whose viewpoint will change.
- @param animated Whether the change will cause an animated effect on the map.
- */
-- (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated;
-
-/**
- :nodoc:
- Tells the delegate that the viewpoint depicted by the map view is about to change.
-
- This method is called whenever the currently displayed map camera will start
- changing for any reason.
-
- @param mapView The map view whose viewpoint will change.
- @param animated Whether the change will cause an animated effect on the map.
- @param reason The reason for the camera change.
-
- @note If this method is implemented `-mapView:regionWillChangeAnimated:` will not be called.
- */
-- (void)mapView:(MGLMapView *)mapView regionWillChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated;
-
-/**
- Tells the delegate that the viewpoint depicted by the map view is changing.
-
- This method is called as the currently displayed map camera changes as part of
- an animation, whether due to a user gesture or due to a call to a method such
- as `-[MGLMapView setCamera:animated:]`. This method can be called before
- `-mapViewDidFinishLoadingMap:` is called.
-
- During the animation, this method may be called many times to report updates to
- the viewpoint. Therefore, your implementation of this method should be as lightweight
- as possible to avoid affecting performance.
-
- @param mapView The map view whose viewpoint is changing.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/clustering/">
- Cluster point data</a> example to learn how to trigger an action whenever
- the map region changes.
- */
-- (void)mapViewRegionIsChanging:(MGLMapView *)mapView;
-
-/**
- :nodoc:
- Tells the delegate that the viewpoint depicted by the map view is changing.
-
- This method is called as the currently displayed map camera changes as part of
- an animation, whether due to a user gesture or due to a call to a method such
- as `-[MGLMapView setCamera:animated:]`. This method can be called before
- `-mapViewDidFinishLoadingMap:` is called.
-
- During the animation, this method may be called many times to report updates to
- the viewpoint. Therefore, your implementation of this method should be as lightweight
- as possible to avoid affecting performance.
-
- @param mapView The map view whose viewpoint is changing.
- @param reason The reason for the camera change.
-
- @note If this method is implemented `-mapViewRegionIsChanging:` will not be called.
- */
-- (void)mapView:(MGLMapView *)mapView regionIsChangingWithReason:(MGLCameraChangeReason)reason;
-
-/**
- Tells the delegate that the viewpoint depicted by the map view has finished
- changing.
-
- This method is called whenever the currently displayed map camera has finished
- changing, after any calls to `-mapViewRegionIsChanging:` due to animation. Therefore,
- this method can be called before `-mapViewDidFinishLoadingMap:` is called.
-
- @param mapView The map view whose viewpoint has changed.
- @param animated Whether the change caused an animated effect on the map.
- */
-- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated;
-
-/**
- :nodoc:
- Tells the delegate that the viewpoint depicted by the map view has finished
- changing.
-
- This method is called whenever the currently displayed map camera has finished
- changing, after any calls to `-mapViewRegionIsChanging:` due to animation. Therefore,
- this method can be called before `-mapViewDidFinishLoadingMap:` is called.
-
- @param mapView The map view whose viewpoint has changed.
- @param animated Whether the change caused an animated effect on the map.
- @param reason The reason for the camera change.
-
- @note If this method is implemented `-mapView:regionDidChangeAnimated:` will not be called.
- */
-- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated;
-
-#pragma mark Loading the Map
-
-/**
- Tells the delegate that the map view will begin to load.
-
- This method is called whenever the map view starts loading, including when a
- new style has been set and the map must reload.
-
- @param mapView The map view that is starting to load.
- */
-- (void)mapViewWillStartLoadingMap:(MGLMapView *)mapView;
-
-/**
- Tells the delegate that the map view has finished loading.
-
- This method is called whenever the map view finishes loading, either after the
- initial load or after a style change has forced a reload.
-
- @param mapView The map view that has finished loading.
- */
-- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView;
-
-/**
- Tells the delegate that the map view was unable to load data needed for
- displaying the map.
-
- This method may be called for a variety of reasons, including a network
- connection failure or a failure to fetch the style from the server. You can use
- the given error message to notify the user that map data is unavailable.
-
- @param mapView The map view that is unable to load the data.
- @param error The reason the data could not be loaded.
- */
-- (void)mapViewDidFailLoadingMap:(MGLMapView *)mapView withError:(NSError *)error;
-
-// TODO
-- (void)mapViewWillStartRenderingMap:(MGLMapView *)mapView;
-
-// TODO
-- (void)mapViewDidFinishRenderingMap:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered;
-
-/**
- Tells the delegate that the map view is about to redraw.
-
- This method is called any time the map view needs to redraw due to a change in
- the viewpoint or style property transition. This method may be called very
- frequently, even moreso than `-mapViewRegionIsChanging:`. Therefore, your
- implementation of this method should be as lightweight as possible to avoid
- affecting performance.
-
- @param mapView The map view that is about to redraw.
- */
-- (void)mapViewWillStartRenderingFrame:(MGLMapView *)mapView;
-
-/**
- Tells the delegate that the map view has just redrawn.
-
- This method is called any time the map view needs to redraw due to a change in
- the viewpoint or style property transition. This method may be called very
- frequently, even moreso than `-mapViewRegionIsChanging:`. Therefore, your
- implementation of this method should be as lightweight as possible to avoid
- affecting performance.
-
- @param mapView The map view that has just redrawn.
- */
-- (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered;
-
-/**
- Tells the delegate that the map view is entering an idle state, and no more
- drawing will be necessary until new data is loaded or there is some interaction
- with the map.
-
- - No camera transitions are in progress
- - All currently requested tiles have loaded
- - All fade/transition animations have completed
-
- @param mapView The map view that has just entered the idle state.
- */
-- (void)mapViewDidBecomeIdle:(MGLMapView *)mapView;
-
-/**
- Tells the delegate that the map has just finished loading a style.
-
- This method is called during the initialization of the map view and after any
- subsequent loading of a new style. This method is called between the
- `-mapViewWillStartRenderingMap:` and `-mapViewDidFinishRenderingMap:` delegate
- methods. Changes to sources or layers of the current style do not cause this
- method to be called.
-
- This method is the earliest opportunity to modify the layout or appearance of
- the current style before the map view is displayed to the user.
-
- @param mapView The map view that has just loaded a style.
- @param style The style that was loaded.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/runtime-multiple-annotations/">
- Dynamically style interactive points</a> and <a href="https://docs.mapbox.com/ios/maps/examples/shape-collection/">
- Add multiple shapes from a single shape source</a> examples to learn how to
- ensure a map's style has loaded before modifying it at runtime.
- */
-- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style;
-
-- (nullable UIImage *)mapView:(MGLMapView *)mapView didFailToLoadImage:(NSString *)imageName;
-
-/**
- Asks the delegate whether the map view should evict cached images.
-
- This method is called in two scenarios: when the cumulative size of unused images
- exceeds the cache size or when the last tile that includes the image is removed from
- memory.
-
- @param mapView The map view that is evicting the image.
- @param imageName The image name that is going to be removed.
- @return A Boolean value indicating whether the map view should evict
- the cached image.
- */
-- (BOOL)mapView:(MGLMapView *)mapView shouldRemoveStyleImage:(NSString *)imageName;
-
-#pragma mark Tracking User Location
-
-/**
- Tells the delegate that the map view will begin tracking the user’s location.
-
- This method is called when the value of the `showsUserLocation` property
- changes to `YES`.
-
- @param mapView The map view that is tracking the user’s location.
- */
-- (void)mapViewWillStartLocatingUser:(MGLMapView *)mapView;
-
-/**
- Tells the delegate that the map view has stopped tracking the user’s location.
-
- This method is called when the value of the `showsUserLocation` property
- changes to `NO`.
-
- @param mapView The map view that is tracking the user’s location.
- */
-- (void)mapViewDidStopLocatingUser:(MGLMapView *)mapView;
-
-/**
- Tells the delegate that the location of the user was updated.
-
- While the `showsUserLocation` property is set to `YES`, this method is called
- whenever a new location update is received by the map view. This method is also
- called if the map view’s user tracking mode is set to
- `MGLUserTrackingModeFollowWithHeading` and the heading changes, or if it is set
- to `MGLUserTrackingModeFollowWithCourse` and the course changes.
-
- This method is not called if the application is currently running in the
- background. If you want to receive location updates while running in the
- background, you must use the Core Location framework.
-
- @param mapView The map view that is tracking the user’s location.
- @param userLocation The location object representing the user’s latest
- location. This property may be `nil`.
- */
-- (void)mapView:(MGLMapView *)mapView didUpdateUserLocation:(nullable MGLUserLocation *)userLocation;
-
-/**
- Tells the delegate that an attempt to locate the user’s position failed.
-
- @param mapView The map view that is tracking the user’s location.
- @param error An error object containing the reason why location tracking
- failed.
- */
-- (void)mapView:(MGLMapView *)mapView didFailToLocateUserWithError:(NSError *)error;
-
-/**
- Tells the delegate that the map view’s user tracking mode has changed.
-
- This method is called after the map view asynchronously changes to reflect the
- new user tracking mode, for example by beginning to zoom or rotate.
-
- @param mapView The map view that changed its tracking mode.
- @param mode The new tracking mode.
- @param animated Whether the change caused an animated effect on the map.
- */
-- (void)mapView:(MGLMapView *)mapView didChangeUserTrackingMode:(MGLUserTrackingMode)mode animated:(BOOL)animated;
-
-/**
- Returns a screen coordinate at which to position the user location annotation.
- This coordinate is relative to the map view’s origin after applying the map view’s
- content insets.
-
- When unimplemented, the user location annotation is aligned within the center of
- the map view with respect to the content insets.
-
- This method will override any values set by `MGLMapView.userLocationVerticalAlignment`
- or `-[MGLMapView setUserLocationVerticalAlignment:animated:]`.
-
- @param mapView The map view that is tracking the user's location.
- */
-- (CGPoint)mapViewUserLocationAnchorPoint:(MGLMapView *)mapView;
-
-#pragma mark Managing the Appearance of Annotations
-
-/**
- Returns an annotation image object to mark the given point annotation object on
- the map.
-
- Implement this method to mark a point annotation with a static image. If you
- want to mark a particular point annotation with an annotation view instead,
- omit this method or have it return `nil` for that annotation, then implement
- `-mapView:viewForAnnotation:`.
-
- Static annotation images use less memory and draw more quickly than annotation
- views. On the other hand, annotation views are compatible with UIKit, Core
- Animation, and other Cocoa Touch frameworks.
-
- @param mapView The map view that requested the annotation image.
- @param annotation The object representing the annotation that is about to be
- displayed.
- @return The annotation image object to display for the given annotation or
- `nil` if you want to display the default marker image or an annotation view.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/annotation-models/">
- Annotation models</a>, <a href="https://docs.mapbox.com/ios/maps/examples/annotation-view-image/">
- Add annotation views and images</a>, and <a href="https://docs.mapbox.com/ios/maps/examples/marker-image/">
- Mark a place on the map with an image</a> examples to learn to specify which
- image should be used for `MGLAnnotation` objects that have been added to
- your map.
- */
-- (nullable MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation;
-
-/**
- Returns the alpha value to use when rendering a shape annotation.
-
- A value of `0.0` results in a completely transparent shape. A value of `1.0`,
- the default, results in a completely opaque shape.
-
- This method sets the opacity of an entire shape, inclusive of its stroke and
- fill. To independently set the values for stroke or fill, specify an alpha
- component in the color returned by `-mapView:strokeColorForShapeAnnotation:` or
- `-mapView:fillColorForPolygonAnnotation:`.
-
- @param mapView The map view rendering the shape annotation.
- @param annotation The annotation being rendered.
- @return An alpha value between `0` and `1.0`.
- */
-- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation;
-
-/**
- Returns the color to use when rendering the outline of a shape annotation.
-
- The default stroke color is the map view’s tint color. If a pattern color is
- specified, the result is undefined.
-
- Opacity may be set by specifying an alpha component. The default alpha value is
- `1.0` and results in a completely opaque stroke.
-
- @param mapView The map view rendering the shape annotation.
- @param annotation The annotation being rendered.
- @return A color to use for the shape outline.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/annotation-models/">
- Annotation models</a> example to learn how to modify the outline color of an
- `MGLShape` object that has been added to your map as an annotation.
- */
-- (UIColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation;
-
-/**
- Returns the color to use when rendering the fill of a polygon annotation.
-
- The default fill color is the map view’s tint color. If a pattern color is
- specified, the result is undefined.
-
- Opacity may be set by specifying an alpha component. The default alpha value is
- `1.0` and results in a completely opaque shape.
-
- @param mapView The map view rendering the polygon annotation.
- @param annotation The annotation being rendered.
- @return The polygon’s interior fill color.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/polygon/">Add
- a polygon annotation</a> example to learn how to modify the color of a an
- `MGLPolygon` at runtime.
- */
-- (UIColor *)mapView:(MGLMapView *)mapView fillColorForPolygonAnnotation:(MGLPolygon *)annotation;
-
-/**
- Returns the line width in points to use when rendering the outline of a
- polyline annotation.
-
- By default, the polyline is outlined with a line `3.0` points wide.
-
- @param mapView The map view rendering the polygon annotation.
- @param annotation The annotation being rendered.
- @return A line width for the polyline, measured in points.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/line-geojson/">
- Add a line annotation from GeoJSON</a> example to learn how to modify the
- line width of an `MGLPolylineFeature` on your map.
- */
-- (CGFloat)mapView:(MGLMapView *)mapView lineWidthForPolylineAnnotation:(MGLPolyline *)annotation;
-
-#pragma mark Managing Annotation Views
-
-/**
- Returns a view object to mark the given point annotation object on the map.
-
- Implement this method to mark a point annotation with a view object. If you
- want to mark a particular point annotation with a static image instead, omit
- this method or have it return `nil` for that annotation, then implement
- `-mapView:imageForAnnotation:` instead.
-
- Annotation views are compatible with UIKit, Core Animation, and other Cocoa
- Touch frameworks. On the other hand, static annotation images use less memory
- and draw more quickly than annotation views.
-
- The user location annotation view can also be customized via this method. When
- `annotation` is an instance of `MGLUserLocation` (or equal to the map view’s
- `userLocation` property), return an instance of `MGLUserLocationAnnotationView`
- (or a subclass thereof).
-
- @param mapView The map view that requested the annotation view.
- @param annotation The object representing the annotation that is about to be
- displayed.
- @return The view object to display for the given annotation or `nil` if you
- want to display an annotation image instead.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/annotation-view-image/">
- Add annotation views and images</a> example to learn how to specify what
- `MGLViewAnnotation` to use for a given `MGLPointAnnotation` object on your
- map.
- */
-- (nullable MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id <MGLAnnotation>)annotation;
-
-/**
- Tells the delegate that one or more annotation views have been added and
- positioned on the map.
-
- This method is called just after the views are added to the map. You can
- implement this method to animate the addition of the annotation views.
-
- @param mapView The map view to which the annotation views were added.
- @param annotationViews An array of `MGLAnnotationView` objects representing the
- views that were added.
- */
-- (void)mapView:(MGLMapView *)mapView didAddAnnotationViews:(NSArray<MGLAnnotationView *> *)annotationViews;
-
-#pragma mark Selecting Annotations
-
-/**
- Returns a Boolean value indicating whether the shape annotation can be selected.
-
- If the return value is `YES`, the user can select the annotation by tapping
- on it. If the delegate does not implement this method, the default value is `YES`.
-
- @param mapView The map view that has selected the annotation.
- @param annotation The object representing the shape annotation.
- @return A Boolean value indicating whether the annotation can be selected.
- */
-- (BOOL)mapView:(MGLMapView *)mapView shapeAnnotationIsEnabled:(MGLShape *)annotation;
-
-/**
- Tells the delegate that one of its annotations was selected.
-
- You can use this method to track changes in the selection state of annotations.
-
- If the annotation is associated with an annotation view, you can also implement
- `-mapView:didSelectAnnotationView:`, which is called immediately after this
- method is called.
-
- @param mapView The map view containing the annotation.
- @param annotation The annotation that was selected.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/runtime-multiple-annotations/">
- Dynamically style interactive points</a> example to learn how to remove an
- annotation view if it has already been selected.
- */
-- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id <MGLAnnotation>)annotation;
-
-/**
- Tells the delegate that one of its annotations was deselected.
-
- You can use this method to track changes in the selection state of annotations.
-
- If the annotation is associated with an annotation view, you can also implement
- `-mapView:didDeselectAnnotationView:`, which is called immediately after this
- method is called.
-
- @param mapView The map view containing the annotation.
- @param annotation The annotation that was deselected.
- */
-- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id <MGLAnnotation>)annotation;
-
-/**
- Tells the delegate that one of its annotation views was selected.
-
- You can use this method to track changes in the selection state of annotation
- views.
-
- This method is only called for annotation views. To track changes in the
- selection state of all annotations, including those associated with static
- annotation images, implement `-mapView:didSelectAnnotation:`, which is called
- immediately before this method is called.
-
- @param mapView The map view containing the annotation.
- @param annotationView The annotation view that was selected.
- */
-- (void)mapView:(MGLMapView *)mapView didSelectAnnotationView:(MGLAnnotationView *)annotationView;
-
-/**
- Tells the delegate that one of its annotation views was deselected.
-
- You can use this method to track changes in the selection state of annotation
- views.
-
- This method is only called for annotation views. To track changes in the
- selection state of all annotations, including those associated with static
- annotation images, implement `-mapView:didDeselectAnnotation:`, which is called
- immediately before this method is called.
-
- @param mapView The map view containing the annotation.
- @param annotationView The annotation view that was deselected.
- */
-- (void)mapView:(MGLMapView *)mapView didDeselectAnnotationView:(MGLAnnotationView *)annotationView;
-
-#pragma mark Managing Callout Views
-
-/**
- Returns a Boolean value indicating whether the annotation is able to display
- extra information in a callout bubble.
-
- This method is called after an annotation is selected, before any callout is
- displayed for the annotation.
-
- If the return value is `YES`, a callout view is shown when the user taps on an
- annotation, selecting it. The default callout displays the annotation’s title
- and subtitle. You can add accessory views to either end of the callout by
- implementing the `-mapView:leftCalloutAccessoryViewForAnnotation:` and
- `-mapView:rightCalloutAccessoryViewForAnnotation:` methods. You can further
- customize the callout’s contents by implementing the
- `-mapView:calloutViewForAnnotation:` method.
-
- If the return value is `NO`, or if this method is absent from the delegate, or
- if the annotation lacks a title, the annotation will not show a callout even
- when selected.
-
- @param mapView The map view that has selected the annotation.
- @param annotation The object representing the annotation.
- @return A Boolean value indicating whether the annotation should show a
- callout.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/annotation-view-image/">
- Add annotation views and images</a>, <a href="https://docs.mapbox.com/ios/maps/examples/custom-callout/">
- Display custom views as callouts</a>, and <a href="https://docs.mapbox.com/ios/maps/examples/default-callout/">
- Default callout usage</a> examples to learn how to show callouts for
- `MGLAnnotation` objects.
- */
-- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation;
-
-/**
- Returns a callout view to display for the given annotation.
-
- If this method is present in the delegate, it must return a new instance of a
- view dedicated to display the callout. The returned view will be configured by
- the map view.
-
- If this method is absent from the delegate, or if it returns `nil`, a standard,
- two-line, bubble-like callout view is displayed by default.
-
- @param mapView The map view that requested the callout view.
- @param annotation The object representing the annotation.
- @return A view conforming to the `MGLCalloutView` protocol, or `nil` to use the
- default callout view.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/custom-callout/">
- Display custom views as callouts</a> example to learn how to customize an
- `MGLAnnotation` object's `MGLCalloutView`.
- */
-- (nullable id <MGLCalloutView>)mapView:(MGLMapView *)mapView calloutViewForAnnotation:(id <MGLAnnotation>)annotation;
-
-/**
- Returns the view to display on the left side of the standard callout bubble.
-
- The left callout view is typically used to convey information about the
- annotation or to link to custom information provided by your application.
-
- If the view you specify is a descendant of the `UIControl` class, you can use
- the map view’s delegate to receive notifications when your control is tapped,
- by implementing the `-mapView:annotation:calloutAccessoryControlTapped:`
- method. If the view you specify does not descend from `UIControl`, your view is
- responsible for handling any touch events within its bounds.
-
- If this method is absent from the delegate, or if it returns `nil`, the
- standard callout view has no accessory view on its left side. The return value
- of this method is ignored if `-mapView:calloutViewForAnnotation:` is present in
- the delegate.
-
- To display a view on the callout’s right side, implement the
- `-mapView:rightCalloutAccessoryViewForAnnotation:` method.
-
- @param mapView The map view presenting the annotation callout.
- @param annotation The object representing the annotation with the callout.
- @return The accessory view to display.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/default-callout/">
- Default callout usage</a> example to learn how to modify the view that is
- displayed on the left side of the standard callout bubble.
- */
-- (nullable UIView *)mapView:(MGLMapView *)mapView leftCalloutAccessoryViewForAnnotation:(id <MGLAnnotation>)annotation;
-
-/**
- Returns the view to display on the right side of the standard callout bubble.
-
- The right callout view is typically used to convey information about the
- annotation or to link to custom information provided by your application.
-
- If the view you specify is a descendant of the `UIControl` class, you can use
- the map view’s delegate to receive notifications when your control is tapped,
- by implementing the `-mapView:annotation:calloutAccessoryControlTapped:`
- method. If the view you specify does not descend from `UIControl`, your view is
- responsible for handling any touch events within its bounds.
-
- If this method is absent from the delegate, or if it returns `nil`, the
- standard callout view has no accessory view on its right side. The return value
- of this method is ignored if `-mapView:calloutViewForAnnotation:` is present in
- the delegate.
-
- To display a view on the callout’s left side, implement the
- `-mapView:leftCalloutAccessoryViewForAnnotation:` method.
-
- @param mapView The map view presenting the annotation callout.
- @param annotation The object representing the annotation with the callout.
- @return The accessory view to display.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/default-callout/">
- Default callout usage</a> example to learn how to modify the view that is
- displayed on the right side of the standard callout bubble.
- */
-- (nullable UIView *)mapView:(MGLMapView *)mapView rightCalloutAccessoryViewForAnnotation:(id <MGLAnnotation>)annotation;
-
-/**
- Tells the delegate that the user tapped one of the accessory controls in the
- annotation’s callout view.
-
- In a standard callout view, accessory views contain custom content and are
- positioned on either side of the annotation title text. If an accessory view
- you specify is a descendant of the `UIControl` class, the map view calls this
- method as a convenience whenever the user taps your view. You can use this
- method to respond to taps and perform any actions associated with that control.
- For example, if your control displays additional information about the
- annotation, you could use this method to present a modal panel with that
- information.
-
- If your custom accessory views are not descendants of the `UIControl` class,
- the map view does not call this method. If the annotation has a custom callout
- view via the `-mapView:calloutViewForAnnotation:` method, you can specify the
- custom accessory views using the `MGLCalloutView` protocol’s
- `leftAccessoryView` and `rightAccessoryView` properties.
-
- @param mapView The map view containing the specified annotation.
- @param annotation The annotation whose accessory view was tapped.
- @param control The control that was tapped.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/default-callout/">
- Default callout usage</a> example to learn how to trigger an action when the
- standard callout bubble's accessory control is tapped.
- */
-- (void)mapView:(MGLMapView *)mapView annotation:(id <MGLAnnotation>)annotation calloutAccessoryControlTapped:(UIControl *)control;
-
-/**
- Tells the delegate that the user tapped on an annotation’s callout view.
-
- This method is called when the user taps on the body of the callout view, as
- opposed to the callout’s left or right accessory view. If the annotation has a
- custom callout view via the `-mapView:calloutViewForAnnotation:` method, this
- method is only called whenever the callout view calls its delegate’s
- `-[MGLCalloutViewDelegate calloutViewTapped:]` method.
-
- If this method is present on the delegate, the standard callout view’s body
- momentarily highlights when the user taps it, whether or not this method does
- anything in response to the tap.
-
- @param mapView The map view containing the specified annotation.
- @param annotation The annotation whose callout was tapped.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/custom-callout/">
- Display custom views as callouts</a> example to learn how to trigger an
- action when an `MGLAnnotation`s `MGLCalloutView` is tapped.
- */
-- (void)mapView:(MGLMapView *)mapView tapOnCalloutForAnnotation:(id <MGLAnnotation>)annotation;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLMapView_Experimental.h b/platform/ios/src/MGLMapView_Experimental.h
deleted file mode 100644
index 94f8d67fb0..0000000000
--- a/platform/ios/src/MGLMapView_Experimental.h
+++ /dev/null
@@ -1,32 +0,0 @@
-#import <Mapbox/Mapbox.h>
-
-@interface MGLMapView (Experimental)
-
-#pragma mark Rendering Performance Measurement
-
-/** Enable rendering performance measurement. */
-@property (nonatomic) BOOL experimental_enableFrameRateMeasurement;
-
-/**
- Average frames per second over the previous second, updated once per second.
-
- Requires `experimental_enableFrameRateMeasurement`.
- */
-@property (nonatomic, readonly) CGFloat averageFrameRate;
-
-/**
- Frame render duration for the previous frame, updated instantaneously.
-
- Requires `experimental_enableFrameRateMeasurement`.
- */
-@property (nonatomic, readonly) CFTimeInterval frameTime;
-
-/**
- Average frame render duration over the previous second, updated once per
- second.
-
- Requires `experimental_enableFrameRateMeasurement`.
- */
-@property (nonatomic, readonly) CFTimeInterval averageFrameTime;
-
-@end
diff --git a/platform/ios/src/MGLMapView_Private.h b/platform/ios/src/MGLMapView_Private.h
deleted file mode 100644
index 155527000f..0000000000
--- a/platform/ios/src/MGLMapView_Private.h
+++ /dev/null
@@ -1,74 +0,0 @@
-#import "MGLMapView.h"
-#import "MGLUserLocationAnnotationView.h"
-#import "MGLAnnotationContainerView.h"
-
-#include <mbgl/util/size.hpp>
-
-namespace mbgl {
- class Map;
- class Renderer;
-}
-
-class MGLMapViewImpl;
-@class MGLSource;
-
-/// Standard animation duration for UI elements.
-FOUNDATION_EXTERN const NSTimeInterval MGLAnimationDuration;
-
-/// Minimum size of an annotation’s accessibility element.
-FOUNDATION_EXTERN const CGSize MGLAnnotationAccessibilityElementMinimumSize;
-
-/// Indicates that a method (that uses `mbgl::Map`) was called after app termination.
-FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const _Nonnull MGLUnderlyingMapUnavailableException;
-
-@interface MGLMapView (Private)
-
-/// The map view’s OpenGL rendering context.
-@property (nonatomic, readonly, nullable) EAGLContext *context;
-
-/// Currently shown popover representing the selected annotation.
-@property (nonatomic, nonnull) UIView<MGLCalloutView> *calloutViewForSelectedAnnotation;
-
-/// Map observers
-- (void)cameraWillChangeAnimated:(BOOL)animated;
-- (void)cameraIsChanging;
-- (void)cameraDidChangeAnimated:(BOOL)animated;
-- (void)mapViewWillStartLoadingMap;
-- (void)mapViewDidFinishLoadingMap;
-- (void)mapViewDidFailLoadingMapWithError:(nonnull NSError *)error;
-- (void)mapViewWillStartRenderingFrame;
-- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered;
-- (void)mapViewWillStartRenderingMap;
-- (void)mapViewDidFinishRenderingMapFullyRendered:(BOOL)fullyRendered;
-- (void)mapViewDidBecomeIdle;
-- (void)mapViewDidFinishLoadingStyle;
-- (void)sourceDidChange:(nonnull MGLSource *)source;
-- (void)didFailToLoadImage:(nonnull NSString *)imageName;
-- (BOOL)shouldRemoveStyleImage:(nonnull NSString *)imageName;
-
-/** Triggers another render pass even when it is not necessary. */
-- (void)setNeedsRerender;
-
-/// Synchronously render a frame of the map.
-- (void)renderSync;
-
-- (nonnull mbgl::Renderer *)renderer;
-
-/** Returns whether the map view is currently loading or processing any assets required to render the map */
-- (BOOL)isFullyLoaded;
-
-/** Empties the in-memory tile cache. */
-- (void)didReceiveMemoryWarning;
-
-/** Returns an instance of MGLMapView implementation. Used for integration testing. */
-- (nonnull MGLMapViewImpl *) viewImpl;
-
-- (void)pauseRendering:(nonnull NSNotification *)notification;
-- (void)resumeRendering:(nonnull NSNotification *)notification;
-@property (nonatomic, nonnull) MGLUserLocationAnnotationView *userLocationAnnotationView;
-@property (nonatomic, nonnull) MGLAnnotationContainerView *annotationContainerView;
-@property (nonatomic, readonly) BOOL enablePresentsWithTransaction;
-
-- (BOOL) _opaque;
-
-@end
diff --git a/platform/ios/src/MGLMapboxEvents.h b/platform/ios/src/MGLMapboxEvents.h
deleted file mode 100644
index a7d316cc06..0000000000
--- a/platform/ios/src/MGLMapboxEvents.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#import <Foundation/Foundation.h>
-#import "MMEEventsManager.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-/// NSUserDefaults key that controls telemetry user opt-out status
-FOUNDATION_EXTERN NSString * const MGLMapboxMetricsEnabledKey;
-
-@interface MGLMapboxEvents : NSObject
-
-+ (nullable instancetype)sharedInstance;
-
-+ (void)setupWithAccessToken:(NSString *)accessToken;
-+ (void)pushTurnstileEvent;
-+ (void)pushEvent:(NSString *)event withAttributes:(MMEMapboxEventAttributes *)attributeDictionary;
-+ (void)flush;
-+ (void)ensureMetricsOptoutExists;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLMapboxEvents.m b/platform/ios/src/MGLMapboxEvents.m
deleted file mode 100644
index 808c3a88bf..0000000000
--- a/platform/ios/src/MGLMapboxEvents.m
+++ /dev/null
@@ -1,200 +0,0 @@
-#import "MGLMapboxEvents.h"
-#import "MBXSKUToken.h"
-#import "NSBundle+MGLAdditions.h"
-#import "MGLAccountManager_Private.h"
-
-// NSUserDefaults and Info.plist keys
-NSString * const MGLMapboxMetricsEnabledKey = @"MGLMapboxMetricsEnabled";
-static NSString * const MGLMapboxMetricsDebugLoggingEnabledKey = @"MGLMapboxMetricsDebugLoggingEnabled";
-static NSString * const MGLMapboxMetricsEnabledSettingShownInAppKey = @"MGLMapboxMetricsEnabledSettingShownInApp";
-static NSString * const MGLTelemetryAccessTokenKey = @"MGLTelemetryAccessToken";
-static NSString * const MGLTelemetryBaseURLKey = @"MGLTelemetryBaseURL";
-static NSString * const MGLEventsProfileKey = @"MMEEventsProfile";
-static NSString * const MGLVariableGeofenceKey = @"VariableGeofence";
-
-static NSString * const MGLAPIClientUserAgentBase = @"mapbox-maps-ios";
-
-@interface MGLMapboxEvents ()
-
-@property (nonatomic) MMEEventsManager *eventsManager;
-@property (nonatomic) NSURL *baseURL;
-@property (nonatomic, copy) NSString *accessToken;
-
-@end
-
-@implementation MGLMapboxEvents
-
-+ (void)initialize {
- if (self == [MGLMapboxEvents class]) {
- NSBundle *bundle = [NSBundle mainBundle];
- NSNumber *accountTypeNumber = [bundle objectForInfoDictionaryKey:MGLMapboxAccountTypeKey];
- [[NSUserDefaults standardUserDefaults] registerDefaults:@{MGLMapboxAccountTypeKey: accountTypeNumber ?: @0,
- MGLMapboxMetricsEnabledKey: @YES,
- MGLMapboxMetricsDebugLoggingEnabledKey: @NO}];
- }
-}
-
-+ (nullable instancetype)sharedInstance {
-
- static dispatch_once_t onceToken;
- static MGLMapboxEvents *_sharedInstance;
- dispatch_once(&onceToken, ^{
- _sharedInstance = [[self alloc] init];
- });
- return _sharedInstance;
-}
-
-- (instancetype)init {
- self = [super init];
- if (self) {
- _eventsManager = MMEEventsManager.sharedManager;
- _eventsManager.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsDebugLoggingEnabledKey];
- _eventsManager.accountType = [[NSUserDefaults standardUserDefaults] integerForKey:MGLMapboxAccountTypeKey];
- _eventsManager.metricsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsEnabledKey];
-
- // It is possible for the shared instance of this class to be created because of a call to
- // +[MGLAccountManager load] early on in the app lifecycle of the host application.
- // If user default values for access token and base URL are available, they are stored here
- // on local properties so that they can be applied later once MMEEventsManager is fully initialized
- // (once -[MMEEventsManager initializeWithAccessToken:userAgentBase:hostSDKVersion:] is called.
- // Normally, the telem access token and base URL are not set this way. However, overriding these values
- // with user defaults can be useful for testing with an alternative (test) backend system.
- if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryAccessTokenKey]) {
- self.accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryAccessTokenKey];
- }
- if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryBaseURLKey]) {
- self.baseURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryBaseURLKey]];
- }
-
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChange:) name:NSUserDefaultsDidChangeNotification object:nil];
- }
- return self;
-}
-
-- (void)dealloc {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
-- (void)userDefaultsDidChange:(NSNotification *)notification {
- dispatch_async(dispatch_get_main_queue(), ^{
- [self updateNonDisablingConfigurationValues];
- [self updateDisablingConfigurationValuesWithNotification:notification];
- });
-}
-
-- (void)updateNonDisablingConfigurationValues {
- self.eventsManager.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsDebugLoggingEnabledKey];
-
- // It is possible for the telemetry access token key to have been set yet `userDefaultsDidChange:`
- // is called before `setupWithAccessToken:` is called.
- // In that case, setting the access token here will have no effect. In practice, that's fine
- // because the access token value will be resolved when `setupWithAccessToken:` is called eventually
- if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryAccessTokenKey]) {
- self.eventsManager.accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryAccessTokenKey];
- }
-
- // It is possible for the telemetry base URL key to have been set yet `userDefaultsDidChange:`
- // is called before setupWithAccessToken: is called.
- // In that case, setting the base URL here will have no effect. In practice, that's fine
- // because the base URL value will be resolved when `setupWithAccessToken:` is called eventually
- if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryBaseURLKey]) {
- NSURL *baseURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryBaseURLKey]];
- self.eventsManager.baseURL = baseURL;
- }
-}
-
-- (void)updateDisablingConfigurationValuesWithNotification:(NSNotification *)notification {
- // Guard against over calling pause / resume if the values this implementation actually
- // cares about have not changed. We guard because the pause and resume method checks CoreLocation's
- // authorization status and that can drag on the main thread if done too many times (e.g. if the host
- // app heavily uses the user defaults API and this method is called very frequently)
- if ([[notification object] respondsToSelector:@selector(objectForKey:)]) {
- NSUserDefaults *userDefaults = [notification object];
-
- NSInteger accountType = [userDefaults integerForKey:MGLMapboxAccountTypeKey];
- BOOL metricsEnabled = [userDefaults boolForKey:MGLMapboxMetricsEnabledKey];
-
- if (accountType != self.eventsManager.accountType || metricsEnabled != self.eventsManager.metricsEnabled) {
- self.eventsManager.accountType = accountType;
- self.eventsManager.metricsEnabled = metricsEnabled;
-
- [self.eventsManager pauseOrResumeMetricsCollectionIfRequired];
- }
- }
-}
-
-+ (void)setupWithAccessToken:(NSString *)accessToken {
- int64_t delayTime = 0;
-
- if ([[[NSBundle mainBundle] objectForInfoDictionaryKey:MGLEventsProfileKey] isEqualToString:MGLVariableGeofenceKey]) {
- delayTime = 10;
- }
-
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- NSString *semanticVersion = [NSBundle mgl_frameworkInfoDictionary][@"MGLSemanticVersionString"];
- NSString *shortVersion = [NSBundle mgl_frameworkInfoDictionary][@"CFBundleShortVersionString"];
- NSString *sdkVersion = semanticVersion ?: shortVersion;
-
- // It is possible that an alternative access token was already set on this instance when the class was loaded
- // Use it if it exists
- NSString *resolvedAccessToken = [MGLMapboxEvents sharedInstance].accessToken ?: accessToken;
-
- [[[self sharedInstance] eventsManager] initializeWithAccessToken:resolvedAccessToken userAgentBase:MGLAPIClientUserAgentBase hostSDKVersion:sdkVersion];
-
- // It is possible that an alternative base URL was set on this instance when the class was loaded
- // Use it if it exists
- if ([MGLMapboxEvents sharedInstance].baseURL) {
- [[MGLMapboxEvents sharedInstance] eventsManager].baseURL = [MGLMapboxEvents sharedInstance].baseURL;
- }
-
- [[self sharedInstance] eventsManager].skuId = MBXAccountsSKUIDMapsUser;
-
- [self flush];
- });
-}
-
-+ (void)pushTurnstileEvent {
- [[[self sharedInstance] eventsManager] sendTurnstileEvent];
-}
-
-+ (void)pushEvent:(NSString *)event withAttributes:(MMEMapboxEventAttributes *)attributeDictionary {
- [[[self sharedInstance] eventsManager] enqueueEventWithName:event attributes:attributeDictionary];
-}
-
-+ (void)flush {
- [[[self sharedInstance] eventsManager] flush];
-}
-
-+ (void)ensureMetricsOptoutExists {
- NSNumber *shownInAppNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:MGLMapboxMetricsEnabledSettingShownInAppKey];
- BOOL metricsEnabledSettingShownInAppFlag = [shownInAppNumber boolValue];
-
- if (!metricsEnabledSettingShownInAppFlag &&
- [[NSUserDefaults standardUserDefaults] integerForKey:MGLMapboxAccountTypeKey] == 0) {
- // Opt-out is not configured in UI, so check for Settings.bundle
- id defaultEnabledValue;
- NSString *appSettingsBundle = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];
-
- if (appSettingsBundle) {
- // Dynamic Settings.bundle loading based on http://stackoverflow.com/a/510329/2094275
- NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[appSettingsBundle stringByAppendingPathComponent:@"Root.plist"]];
- NSArray *preferences = settings[@"PreferenceSpecifiers"];
- for (NSDictionary *prefSpecification in preferences) {
- if ([prefSpecification[@"Key"] isEqualToString:MGLMapboxMetricsEnabledKey]) {
- defaultEnabledValue = prefSpecification[@"DefaultValue"];
- }
- }
- }
-
- if (!defaultEnabledValue) {
- [NSException raise:@"Telemetry opt-out missing" format:
- @"End users must be able to opt out of Mapbox Telemetry in your app, either inside Settings (via Settings.bundle) or inside this app. "
- @"By default, this opt-out control is included as a menu item in the attribution action sheet. "
- @"If you reimplement the opt-out control inside this app, disable this assertion by setting MGLMapboxMetricsEnabledSettingShownInApp to YES in Info.plist."
- @"\n\nSee https://docs.mapbox.com/help/how-mapbox-works/attribution/#mapbox-maps-sdk-for-ios for more information."
- @"\n\nAdditionally, by hiding this attribution control you agree to display the required attribution elsewhere in this app."];
- }
- }
-}
-
-@end
diff --git a/platform/ios/src/MGLSDKUpdateChecker.h b/platform/ios/src/MGLSDKUpdateChecker.h
deleted file mode 100644
index 13cef46ad4..0000000000
--- a/platform/ios/src/MGLSDKUpdateChecker.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#import <Foundation/Foundation.h>
-
-#import "MGLFoundation.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface MGLSDKUpdateChecker : NSObject
-
-+ (void)checkForUpdates;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLSDKUpdateChecker.mm b/platform/ios/src/MGLSDKUpdateChecker.mm
deleted file mode 100644
index cfea139bdb..0000000000
--- a/platform/ios/src/MGLSDKUpdateChecker.mm
+++ /dev/null
@@ -1,37 +0,0 @@
-#import "MGLSDKUpdateChecker.h"
-#import "NSBundle+MGLAdditions.h"
-
-@implementation MGLSDKUpdateChecker
-
-+ (void)checkForUpdates {
-#if TARGET_IPHONE_SIMULATOR
- // Abort if running in a playground.
- if ([[NSBundle mainBundle].bundleIdentifier hasPrefix:@"com.apple.dt.playground."]) {
- return;
- }
-
- NSString *currentVersion = [NSBundle mgl_frameworkInfoDictionary][@"MGLSemanticVersionString"];
-
- // Skip version check if weʼre doing gl-native development, as the framework
- // version is `1` until built for packaging.
- if ([currentVersion isEqualToString:@"1.0.0"]) {
- return;
- }
-
- NSURL *url = [NSURL URLWithString:@"https://docs.mapbox.com/ios/maps/latest_version.txt"];
- [[NSURLSession.sharedSession dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
- if (error || ((NSHTTPURLResponse *)response).statusCode != 200) {
- return;
- }
-
- NSString *latestVersion = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
- latestVersion = [latestVersion stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
- if (![currentVersion isEqualToString:latestVersion]) {
- NSString *updateAvailable = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"SDK_UPDATE_AVAILABLE", nil, nil, @"Mapbox Maps SDK for iOS version %@ is now available:", @"Developer-only SDK update notification; {latest version, in format x.x.x}"), latestVersion];
- NSLog(@"%@ https://github.com/mapbox/mapbox-gl-native/releases/tag/ios-v%@", updateAvailable, latestVersion);
- }
- }] resume];
-#endif
-}
-
-@end
diff --git a/platform/ios/src/MGLScaleBar.h b/platform/ios/src/MGLScaleBar.h
deleted file mode 100644
index 77fd6736b5..0000000000
--- a/platform/ios/src/MGLScaleBar.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#import <UIKit/UIKit.h>
-#import <CoreLocation/CoreLocation.h>
-
-@interface MGLScaleBar : UIView
-
-// Sets the scale and redraws the scale bar
-@property (nonatomic, assign) CLLocationDistance metersPerPoint;
-
-@end
diff --git a/platform/ios/src/MGLScaleBar.mm b/platform/ios/src/MGLScaleBar.mm
deleted file mode 100644
index 3efa80013f..0000000000
--- a/platform/ios/src/MGLScaleBar.mm
+++ /dev/null
@@ -1,556 +0,0 @@
-#import "Mapbox.h"
-#import "MGLScaleBar.h"
-
-static const CGFloat MGLFeetPerMile = 5280;
-
-struct MGLRow {
- CLLocationDistance distance;
- NSUInteger numberOfBars;
-};
-
-static const MGLRow MGLMetricTable[] = {
- {.distance = 1, .numberOfBars = 2},
- {.distance = 2, .numberOfBars = 2},
- {.distance = 4, .numberOfBars = 2},
- {.distance = 10, .numberOfBars = 2},
- {.distance = 20, .numberOfBars = 2},
- {.distance = 50, .numberOfBars = 2},
- {.distance = 75, .numberOfBars = 3},
- {.distance = 100, .numberOfBars = 2},
- {.distance = 150, .numberOfBars = 2},
- {.distance = 200, .numberOfBars = 2},
- {.distance = 300, .numberOfBars = 3},
- {.distance = 500, .numberOfBars = 2},
- {.distance = 1000, .numberOfBars = 2},
- {.distance = 1500, .numberOfBars = 2},
- {.distance = 3000, .numberOfBars = 3},
- {.distance = 5000, .numberOfBars = 2},
- {.distance = 10000, .numberOfBars = 2},
- {.distance = 20000, .numberOfBars = 2},
- {.distance = 30000, .numberOfBars = 3},
- {.distance = 50000, .numberOfBars = 2},
- {.distance = 100000, .numberOfBars = 2},
- {.distance = 200000, .numberOfBars = 2},
- {.distance = 300000, .numberOfBars = 3},
- {.distance = 400000, .numberOfBars = 2},
- {.distance = 500000, .numberOfBars = 2},
- {.distance = 600000, .numberOfBars = 3},
- {.distance = 800000, .numberOfBars = 2},
-};
-
-static const MGLRow MGLImperialTable[] ={
- {.distance = 4, .numberOfBars = 2},
- {.distance = 6, .numberOfBars = 2},
- {.distance = 10, .numberOfBars = 2},
- {.distance = 20, .numberOfBars = 2},
- {.distance = 30, .numberOfBars = 2},
- {.distance = 50, .numberOfBars = 2},
- {.distance = 75, .numberOfBars = 3},
- {.distance = 100, .numberOfBars = 2},
- {.distance = 200, .numberOfBars = 2},
- {.distance = 300, .numberOfBars = 3},
- {.distance = 400, .numberOfBars = 2},
- {.distance = 600, .numberOfBars = 3},
- {.distance = 800, .numberOfBars = 2},
- {.distance = 1000, .numberOfBars = 2},
- {.distance = 0.25f*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 0.5f*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 1*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 2*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 3*MGLFeetPerMile, .numberOfBars = 3},
- {.distance = 4*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 8*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 12*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 15*MGLFeetPerMile, .numberOfBars = 3},
- {.distance = 20*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 30*MGLFeetPerMile, .numberOfBars = 3},
- {.distance = 40*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 80*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 120*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 200*MGLFeetPerMile, .numberOfBars = 2},
- {.distance = 300*MGLFeetPerMile, .numberOfBars = 3},
- {.distance = 400*MGLFeetPerMile, .numberOfBars = 2},
-};
-
-@class MGLScaleBarLabel;
-
-@interface MGLScaleBar()
-@property (nonatomic) NSArray<UIView *> *labelViews;
-@property (nonatomic) NSArray<UIView *> *bars;
-@property (nonatomic) UIView *containerView;
-@property (nonatomic) MGLDistanceFormatter *formatter;
-@property (nonatomic, assign) MGLRow row;
-@property (nonatomic) UIColor *primaryColor;
-@property (nonatomic) UIColor *secondaryColor;
-@property (nonatomic, assign) CGFloat borderWidth;
-@property (nonatomic) NSMutableDictionary* labelImageCache;
-@property (nonatomic) MGLScaleBarLabel* prototypeLabel;
-@property (nonatomic) CGFloat lastLabelWidth;
-@property (nonatomic) CGSize size;
-@property (nonatomic) BOOL recalculateSize;
-@property (nonatomic) BOOL shouldLayoutBars;
-@property (nonatomic) NSNumber *testingRightToLeftOverride;
-@end
-
-static const CGFloat MGLBarHeight = 4;
-static const CGFloat MGLFeetPerMeter = 3.28084;
-static const CGFloat MGLScaleBarLabelWidthHint = 30.0;
-static const CGFloat MGLScaleBarMinimumBarWidth = 30.0; // Arbitrary
-
-@interface MGLScaleBarLabel : UILabel
-
-@end
-
-@implementation MGLScaleBarLabel
-
-- (void)drawTextInRect:(CGRect)rect {
- CGSize shadowOffset = self.shadowOffset;
-
- CGContextRef context = UIGraphicsGetCurrentContext();
- CGContextSetLineWidth(context, 2);
- CGContextSetLineJoin(context, kCGLineJoinRound);
-
- CGContextSetTextDrawingMode(context, kCGTextStroke);
- self.textColor = [UIColor whiteColor];
- [super drawTextInRect:rect];
-
- CGContextSetTextDrawingMode(context, kCGTextFill);
- self.textColor = [UIColor blackColor];
- self.shadowOffset = CGSizeMake(0, 0);
- [super drawTextInRect:rect];
-
- self.shadowOffset = shadowOffset;
-}
-
-@end
-
-@implementation MGLScaleBar
-
-- (instancetype)initWithCoder:(NSCoder *)decoder {
- if (self = [super initWithCoder:decoder]) {
- [self commonInit];
- }
- return self;
-}
-
-- (instancetype)initWithFrame:(CGRect)frame {
- if (self = [super initWithFrame:frame]) {
- [self commonInit];
- }
- return self;
-}
-
-- (void)commonInit {
- _size = CGSizeZero;
-
- _primaryColor = [UIColor colorWithRed:18.0/255.0 green:45.0/255.0 blue:17.0/255.0 alpha:1];
- _secondaryColor = [UIColor colorWithRed:247.0/255.0 green:247.0/255.0 blue:247.0/255.0 alpha:1];
- _borderWidth = 1.0f;
-
- self.clipsToBounds = NO;
- self.hidden = YES;
-
- _containerView = [[UIView alloc] init];
- _containerView.clipsToBounds = YES;
- _containerView.backgroundColor = _secondaryColor;
- _containerView.layer.borderColor = _primaryColor.CGColor;
- _containerView.layer.borderWidth = _borderWidth / [[UIScreen mainScreen] scale];
-
- _containerView.layer.cornerRadius = MGLBarHeight / 2.0;
- _containerView.layer.masksToBounds = YES;
-
- [self addSubview:_containerView];
-
- _formatter = [[MGLDistanceFormatter alloc] init];
-
- // Image labels are now images
- _labelImageCache = [[NSMutableDictionary alloc] init];
- _prototypeLabel = [[MGLScaleBarLabel alloc] init];
- _prototypeLabel.font = [UIFont systemFontOfSize:8 weight:UIFontWeightMedium];
- _prototypeLabel.clipsToBounds = NO;
-
- NSUInteger numberOfLabels = 4;
- NSMutableArray *labelViews = [NSMutableArray arrayWithCapacity:numberOfLabels];
-
- for (NSUInteger i = 0; i < numberOfLabels; i++) {
- UIView *view = [[UIView alloc] init];
- view.bounds = CGRectZero;
- view.clipsToBounds = NO;
- view.contentMode = UIViewContentModeCenter;
- view.hidden = YES;
- [labelViews addObject:view];
- [self addSubview:view];
- }
- _labelViews = [labelViews copy];
- _lastLabelWidth = MGLScaleBarLabelWidthHint;
-
- // Zero is a special case (no formatting)
- [self addZeroLabel];
-
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resetLabelImageCache) name:NSCurrentLocaleDidChangeNotification object:nil];
-}
-
-- (void)dealloc {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
-- (void)resetLabelImageCache {
- self.labelImageCache = [[NSMutableDictionary alloc] init];
- [self addZeroLabel];
-}
-
-#pragma mark - Dimensions
-
-- (void)setBorderWidth:(CGFloat)borderWidth {
- _borderWidth = borderWidth;
- _containerView.layer.borderWidth = borderWidth / [[UIScreen mainScreen] scale];
-}
-
-// Determines the width of the bars NOT the size of the entire scale bar,
-// which includes space for (half) a label.
-// Uses the current set `row`
-- (CGFloat)actualWidth {
- CGFloat unitsPerPoint = [self unitsPerPoint];
-
- if (unitsPerPoint == 0.0) {
- return 0.0;
- }
-
- CGFloat width = self.row.distance / unitsPerPoint;
-
- if (width <= MGLScaleBarMinimumBarWidth) {
- return 0.0;
- }
-
- // Round, so that each bar section has an integer width
- return self.row.numberOfBars * floor(width/self.row.numberOfBars);
-}
-
-- (CGFloat)maximumWidth {
- // TODO: Consider taking Scale Bar margins into account here.
- CGFloat fullWidth = CGRectGetWidth(self.superview.bounds);
- return floorf(fullWidth / 2);
-}
-
-- (CGFloat)unitsPerPoint {
- return [self usesMetricSystem] ? self.metersPerPoint : self.metersPerPoint * MGLFeetPerMeter;
-}
-
-#pragma mark - Convenience methods
-
-- (BOOL)usesRightToLeftLayout {
- if (self.testingRightToLeftOverride) {
- return [self.testingRightToLeftOverride boolValue];
- }
-
- return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.superview.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft;
-}
-
-- (BOOL)usesMetricSystem {
- NSLocale *locale = [NSLocale currentLocale];
- return [[locale objectForKey:NSLocaleUsesMetricSystem] boolValue];
-}
-
-- (MGLRow)preferredRow {
- CLLocationDistance maximumDistance = [self maximumWidth] * [self unitsPerPoint];
-
- BOOL useMetric = [self usesMetricSystem];
-
- const MGLRow *row;
- const MGLRow *table;
- NSUInteger count;
-
- if (useMetric) {
- row = table = MGLMetricTable;
- count = sizeof(MGLMetricTable) / sizeof(MGLMetricTable[0]);
- }
- else {
- row = table = MGLImperialTable;
- count = sizeof(MGLImperialTable) / sizeof(MGLImperialTable[0]);
- }
-
- while (row < table + count) {
- if (row->distance > maximumDistance) {
- // use the previous row
- NSAssert(row != table, @"");
- return *(row - 1);
- }
- ++row;
- }
-
- // Didn't find it, just return the first.
- return *table;
-}
-
-#pragma mark - Setters
-
-- (void)setMetersPerPoint:(CLLocationDistance)metersPerPoint {
- if (_metersPerPoint == metersPerPoint) {
- return;
- }
-
- _metersPerPoint = metersPerPoint;
-
- [self updateVisibility];
-
- self.recalculateSize = YES;
- [self invalidateIntrinsicContentSize];
-}
-
-- (CGSize)intrinsicContentSize {
- // Size is calculated elsewhere - since intrinsicContentSize is part of the
- // constraint system, this should be done in updateConstraints
- if (self.size.width < 0.0) {
- return CGSizeZero;
- }
- return self.size;
-}
-
-/// updateConstraints
-///
-/// The primary job of updateConstraints here is to recalculate the
-/// intrinsicContentSize: _metersPerPoint and the maximum width determine the
-/// current "row", which in turn determines the "actualWidth". To obtain the full
-/// width of the scale bar, we also need to include some space for the "last"
-/// label
-
-- (void)updateConstraints {
- if (self.isHidden || !self.recalculateSize) {
- [super updateConstraints];
- return;
- }
-
- // TODO: Improve this (and the side-effects)
- self.row = [self preferredRow];
-
- NSAssert(self.row.numberOfBars > 0, @"");
-
- CGFloat totalBarWidth = self.actualWidth;
-
- if (totalBarWidth <= 0.0) {
- [super updateConstraints];
- return;
- }
-
- // Determine the "lastLabelWidth". This has changed to take a maximum of each
- // label, to ensure that the size does not change in LTR & RTL layouts, and
- // also to stop jiggling when the scale bar is on the right hand of the screen
- // This will most likely be a constant, as we take a max using a "hint" for
- // the initial value
-
- if (self.shouldLayoutBars) {
- [self updateLabels];
- }
-
- CGFloat halfLabelWidth = ceil(self.lastLabelWidth/2);
-
- self.size = CGSizeMake(totalBarWidth + halfLabelWidth, 16);
-
- [self setNeedsLayout];
- [super updateConstraints]; // This calls intrinsicContentSize
-}
-
-- (void)updateVisibility {
- BOOL metric = [self usesMetricSystem];
-
- NSUInteger count = metric
- ? sizeof(MGLMetricTable) / sizeof(MGLMetricTable[0])
- : sizeof(MGLImperialTable) / sizeof(MGLImperialTable[0]);
-
- CLLocationDistance maximumDistance = [self maximumWidth] * [self unitsPerPoint];
- CLLocationDistance allowedDistance = metric
- ? MGLMetricTable[count-1].distance
- : MGLImperialTable[count-1].distance;
-
- CGFloat alpha = maximumDistance > allowedDistance ? .0f : 1.0f;
-
- if (self.alpha != alpha) {
- [UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
- self.alpha = alpha;
- } completion:nil];
- }
-}
-
-- (void)setRow:(MGLRow)row {
- if (_row.distance == row.distance) {
- return;
- }
-
- self.shouldLayoutBars = YES;
-
- _row = row;
-}
-
-#pragma mark - Views
-
-- (NSArray<UIView *> *)bars {
- if (!_bars) {
- NSMutableArray *bars = [NSMutableArray array];
- for (NSUInteger i = 0; i < self.row.numberOfBars; i++) {
- UIView *bar = [[UIView alloc] init];
- [bars addObject:bar];
- [self.containerView addSubview:bar];
- }
- _bars = bars;
- }
- return _bars;
-}
-
-#pragma mark - Labels
-
-- (void)addZeroLabel {
- NSDecimalNumber *zeroNumber = [NSDecimalNumber decimalNumberWithString:@"0"];
- NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
- NSString *text = [formatter stringFromNumber:zeroNumber];
-
- UIImage* image = [self imageForLabelText:text];
- [self.labelImageCache setObject:image forKey:@(0)];
-}
-
-- (UIImage*)imageForLabelText:(NSString*)text {
- self.prototypeLabel.text = text;
- [self.prototypeLabel setNeedsDisplay];
- [self.prototypeLabel sizeToFit];
-
- // Now render
- UIGraphicsBeginImageContextWithOptions(self.prototypeLabel.bounds.size, NO, 0.0);
- [self.prototypeLabel.layer renderInContext: UIGraphicsGetCurrentContext()];
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return image;
-}
-
-- (UIImage*)cachedLabelImageForDistance:(CLLocationDistance)barDistance {
- // Make a slightly nicer key, rather than something that's a double.
- NSUInteger floorDist = (NSUInteger)(barDistance*100);
-
- NSNumber *key = @(floorDist);
- UIImage *cachedImage = [self.labelImageCache objectForKey:key];
-
- if (cachedImage) {
- return cachedImage;
- }
-
- // Calc it
- NSString *text = [self.formatter stringFromDistance:barDistance];
- UIImage *image = [self imageForLabelText:text];
-
- [self.labelImageCache setObject:image forKey:key];
-
- return image;
-}
-
-- (void)updateLabels {
- NSEnumerator<UIView*> *viewEnumerator = [self.labelViews objectEnumerator];
- NSUInteger i = 0;
- CLLocationDistance multiplier = (self.row.distance / self.row.numberOfBars);
-
- if (![self usesMetricSystem]) {
- multiplier /= MGLFeetPerMeter;
- }
-
- for (; i <= self.row.numberOfBars; i++) {
- UIView *labelView = [viewEnumerator nextObject];
- labelView.hidden = NO;
-
- CLLocationDistance barDistance = multiplier * i;
- UIImage *image = [self cachedLabelImageForDistance:barDistance];
-
- self.lastLabelWidth = MAX(self.lastLabelWidth, image.size.width);
-
- labelView.layer.contents = (id)image.CGImage;
- labelView.layer.contentsScale = image.scale;
- }
-
- // Hide the rest.
- for (; i < self.labelViews.count; i++) {
- UIView *labelView = [viewEnumerator nextObject];
- labelView.hidden = YES;
- }
-}
-
-#pragma mark - Layout
-
-- (void)layoutSubviews {
- [super layoutSubviews];
-
- if (!self.recalculateSize) {
- return;
- }
-
- self.recalculateSize = NO;
-
- // If size is 0, then we keep the existing layout (which will fade out)
- if (self.size.width <= 0.0) {
- return;
- }
-
- CGFloat totalBarWidth = self.actualWidth;
-
- if (totalBarWidth <= 0.0) {
- return;
- }
-
- if (self.shouldLayoutBars) {
- self.shouldLayoutBars = NO;
- [_bars makeObjectsPerformSelector:@selector(removeFromSuperview)];
- _bars = nil;
- }
-
- // Re-layout the component bars and labels of the scale bar
- CGFloat intrinsicContentHeight = self.intrinsicContentSize.height;
- CGFloat barWidth = totalBarWidth/self.bars.count;
-
- BOOL RTL = [self usesRightToLeftLayout];
- CGFloat halfLabelWidth = ceil(self.lastLabelWidth/2);
- CGFloat barOffset = RTL ? halfLabelWidth : 0.0;
-
- self.containerView.frame = CGRectMake(barOffset,
- intrinsicContentHeight - MGLBarHeight,
- totalBarWidth,
- MGLBarHeight);
-
- [self layoutBarsWithWidth:barWidth];
-
- CGFloat yPosition = round(0.5 * ( intrinsicContentHeight - MGLBarHeight));
- CGFloat barDelta = RTL ? -barWidth : barWidth;
- [self layoutLabelsWithOffset:barOffset delta:barDelta yPosition:yPosition];
-}
-
-- (void)layoutBarsWithWidth:(CGFloat)barWidth {
- NSUInteger i = 0;
- for (UIView *bar in self.bars) {
- CGFloat xPosition = barWidth * i;
- bar.backgroundColor = (i % 2 == 0) ? self.primaryColor : self.secondaryColor;
- bar.frame = CGRectMake(xPosition, 0, barWidth, MGLBarHeight);
- i++;
- }
-}
-
-- (void)layoutLabelsWithOffset:(CGFloat)barOffset delta:(CGFloat)barDelta yPosition:(CGFloat)yPosition {
-#if !defined(NS_BLOCK_ASSERTIONS)
- NSUInteger countOfVisibleLabels = 0;
- for (UIView *view in self.labelViews) {
- if (!view.isHidden) {
- countOfVisibleLabels++;
- }
- }
- NSAssert(self.bars.count == countOfVisibleLabels - 1, @"");
-#endif
-
- CGFloat xPosition = barOffset;
-
- if (barDelta < 0) {
- xPosition -= (barDelta*self.bars.count);
- }
-
- for (UIView *label in self.labelViews) {
- // Label frames have 0 size - though the layer contents use "center" and do
- // not clip to bounds. This way we don't need to worry about positioning the
- // label. (Though you won't see the label in the view debugger)
- label.frame = CGRectMake(xPosition, yPosition, 0.0, 0.0);
-
- xPosition += barDelta;
- }
-}
-@end
diff --git a/platform/ios/src/MGLTelemetryConfig.h b/platform/ios/src/MGLTelemetryConfig.h
deleted file mode 100644
index 96e525c969..0000000000
--- a/platform/ios/src/MGLTelemetryConfig.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#import <Foundation/Foundation.h>
-#import <CoreLocation/CoreLocation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface MGLTelemetryConfig : NSObject
-
-@property (nonatomic) CLLocationDistance MGLLocationManagerHibernationRadius;
-
-extern NSString *const MGLMapboxMetricsProfile;
-
-@property (class, nullable, nonatomic, readonly) MGLTelemetryConfig *sharedConfig;
-
-- (void)configurationFromKey:(NSString *)key;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLTelemetryConfig.m b/platform/ios/src/MGLTelemetryConfig.m
deleted file mode 100644
index 828bafb14f..0000000000
--- a/platform/ios/src/MGLTelemetryConfig.m
+++ /dev/null
@@ -1,35 +0,0 @@
-#import "MGLTelemetryConfig.h"
-
-static const CLLocationDistance MGLConfigHibernationRadiusDefault = 300.0;
-static const CLLocationDistance MGLConfigHibernationRadiusWide = 600.0;
-
-NSString *const MGLMapboxMetricsProfile = @"MGLMapboxMetricsProfile";
-
-static NSString *const MGLConfigHibernationRadiusWideKey = @"WideGeoFence";
-
-@implementation MGLTelemetryConfig
-
-- (instancetype) init {
- self = [super init];
- if (self) {
- _MGLLocationManagerHibernationRadius = MGLConfigHibernationRadiusDefault;
- }
- return self;
-}
-
-+ (nullable instancetype)sharedConfig {
- static dispatch_once_t onceToken;
- static MGLTelemetryConfig *_sharedConfig;
- dispatch_once(&onceToken, ^{
- _sharedConfig = [[self alloc] init];
- });
- return _sharedConfig;
-}
-
-- (void)configurationFromKey:(NSString *)key {
- if ([key isEqualToString:MGLConfigHibernationRadiusWideKey]) {
- _MGLLocationManagerHibernationRadius = MGLConfigHibernationRadiusWide;
- }
-}
-
-@end
diff --git a/platform/ios/src/MGLUserLocation.h b/platform/ios/src/MGLUserLocation.h
deleted file mode 100644
index d7c8576c47..0000000000
--- a/platform/ios/src/MGLUserLocation.h
+++ /dev/null
@@ -1,57 +0,0 @@
-#import <Foundation/Foundation.h>
-#import <CoreLocation/CoreLocation.h>
-
-#import "MGLFoundation.h"
-#import "MGLAnnotation.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-/**
- The MGLUserLocation class defines a specific type of annotation that identifies
- the user’s current location. You do not create instances of this class
- directly. Instead, you retrieve an existing `MGLUserLocation` object from the
- `userLocation` property of the map view displayed in your application.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/user-location-annotation/">
- Customize the user location annotation</a> example to learn how to overide the
- default user location annotation.
- */
-MGL_EXPORT
-@interface MGLUserLocation : NSObject <MGLAnnotation, NSSecureCoding>
-
-#pragma mark Determining the User’s Position
-
-/**
- The current location of the device. (read-only)
-
- This property returns `nil` if the user’s location has not yet been determined.
- */
-@property (nonatomic, readonly, nullable) CLLocation *location;
-
-/**
- A Boolean value indicating whether the user’s location is currently being
- updated. (read-only)
- */
-@property (nonatomic, readonly, getter=isUpdating) BOOL updating;
-
-/**
- The heading of the user location. (read-only)
-
- This property is `nil` if the user location tracking mode is not
- `MGLUserTrackingModeFollowWithHeading` or if
- `MGLMapView.showsUserHeadingIndicator` is disabled.
- */
-@property (nonatomic, readonly, nullable) CLHeading *heading;
-
-#pragma mark Accessing the User Annotation Text
-
-/** The title to display for the user location annotation. */
-@property (nonatomic, copy) NSString *title;
-
-/** The subtitle to display for the user location annotation. */
-@property (nonatomic, copy, nullable) NSString *subtitle;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLUserLocation.m b/platform/ios/src/MGLUserLocation.m
deleted file mode 100644
index 245cbf4371..0000000000
--- a/platform/ios/src/MGLUserLocation.m
+++ /dev/null
@@ -1,124 +0,0 @@
-#import "MGLUserLocation_Private.h"
-
-#import "MGLMapView.h"
-#import "NSBundle+MGLAdditions.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface MGLUserLocation ()
-
-@property (nonatomic, weak) MGLMapView *mapView;
-
-@end
-
-NS_ASSUME_NONNULL_END
-
-@implementation MGLUserLocation
-
-- (instancetype)initWithMapView:(MGLMapView *)mapView
-{
- if (self = [super init])
- {
- _mapView = mapView;
- }
-
- return self;
-}
-
-+ (BOOL)supportsSecureCoding {
- return YES;
-}
-
-- (instancetype)initWithCoder:(NSCoder *)decoder {
- if (self = [super init]) {
- _location = [decoder decodeObjectOfClass:[CLLocation class] forKey:@"location"];
- _title = [decoder decodeObjectOfClass:[NSString class] forKey:@"title"];
- _subtitle = [decoder decodeObjectOfClass:[NSString class] forKey:@"subtitle"];
- }
- return self;
-}
-
-- (void)encodeWithCoder:(NSCoder *)coder {
- [coder encodeObject:_location forKey:@"location"];
- [coder encodeObject:_title forKey:@"title"];
- [coder encodeObject:_subtitle forKey:@"subtitle"];
-}
-
-- (BOOL)isEqual:(id)other {
- if (self == other) return YES;
- if (![other isKindOfClass:[MGLUserLocation class]]) return NO;
-
- MGLUserLocation *otherUserLocation = other;
- return ((!self.location && !otherUserLocation.location) || [self.location distanceFromLocation:otherUserLocation.location] == 0)
- && ((!self.title && !otherUserLocation.title) || [self.title isEqualToString:otherUserLocation.title])
- && ((!self.subtitle && !otherUserLocation.subtitle) || [self.subtitle isEqualToString:otherUserLocation.subtitle]);
-}
-
-- (NSUInteger)hash {
- NSUInteger hash = [super hash];
- hash += [_location hash];
- hash += [_heading hash];
- hash += [_title hash];
- hash += [_subtitle hash];
- return hash;
-}
-
-+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
-{
- return ! [key isEqualToString:@"location"] && ! [key isEqualToString:@"heading"];
-}
-
-+ (NSSet<NSString *> *)keyPathsForValuesAffectingCoordinate
-{
- return [NSSet setWithObject:@"location"];
-}
-
-- (void)setLocation:(CLLocation *)newLocation
-{
- if ( ! newLocation || ! CLLocationCoordinate2DIsValid(newLocation.coordinate)) return;
- if ( _location && CLLocationCoordinate2DIsValid(_location.coordinate) && [newLocation distanceFromLocation:_location] == 0) return;
- if (newLocation.coordinate.latitude == 0 && newLocation.coordinate.longitude == 0) return;
-
- [self willChangeValueForKey:@"location"];
- _location = newLocation;
- [self didChangeValueForKey:@"location"];
-}
-
-- (BOOL)isUpdating
-{
- return self.mapView.userTrackingMode != MGLUserTrackingModeNone;
-}
-
-- (void)setHeading:(CLHeading *)newHeading
-{
- if (newHeading.trueHeading != _heading.trueHeading)
- {
- [self willChangeValueForKey:@"heading"];
- _heading = newHeading;
- [self didChangeValueForKey:@"heading"];
- }
-}
-
-- (CLLocationCoordinate2D)coordinate
-{
- return _location ? _location.coordinate : kCLLocationCoordinate2DInvalid;
-}
-
-- (NSString *)title
-{
- return _title ?: NSLocalizedStringWithDefaultValue(@"USER_DOT_TITLE", nil, nil, @"You Are Here", @"Default user location annotation title");
-}
-
-- (NSString *)description
-{
- return [NSString stringWithFormat:@"<%@: %p; location = %f, %f; updating = %@; altitude = %.0fm; heading = %.0f°; title = %@; subtitle = %@>",
- NSStringFromClass([self class]), (void *)self,
- self.location.coordinate.latitude, self.location.coordinate.longitude,
- self.updating ? @"yes" : @"no",
- self.location.altitude,
- self.heading.trueHeading,
- self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
- self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle];
-}
-
-@end
diff --git a/platform/ios/src/MGLUserLocationAnnotationView.h b/platform/ios/src/MGLUserLocationAnnotationView.h
deleted file mode 100644
index f5197b9f76..0000000000
--- a/platform/ios/src/MGLUserLocationAnnotationView.h
+++ /dev/null
@@ -1,64 +0,0 @@
-#import <UIKit/UIKit.h>
-#import <CoreLocation/CoreLocation.h>
-
-#import "MGLFoundation.h"
-#import "MGLAnnotationView.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@class MGLMapView;
-@class MGLUserLocation;
-
-/** View representing an `MGLUserLocation` on screen. */
-MGL_EXPORT
-@interface MGLUserLocationAnnotationView : MGLAnnotationView
-
-/**
- Returns the associated map view.
-
- The value of this property is nil during initialization.
- */
-@property (nonatomic, readonly, weak, nullable) MGLMapView *mapView;
-
-/**
- Returns the annotation object indicating the user’s current location.
-
- The value of this property is nil during initialization and while user tracking
- is inactive.
-
- #### Related examples
- See the <a href="https://docs.mapbox.com/ios/maps/examples/user-location-annotation/">
- Customize the user location annotation</a> example to learn how to customize
- the default user location annotation object.
- */
-@property (nonatomic, readonly, weak, nullable) MGLUserLocation *userLocation;
-
-/**
- Returns the layer that should be used for annotation selection hit testing.
-
- The default value of this property is the presentation layer of the view’s Core
- Animation layer. When subclassing, you may override this property to specify a
- different layer to be used for hit testing. This can be useful when you wish to
- limit the interactive area of the annotation to a specific sublayer.
- */
-@property (nonatomic, readonly, weak) CALayer *hitTestLayer;
-
-/**
- Updates the user location annotation.
-
- Use this method to update the appearance of the user location annotation. This
- method is called by the associated map view when it has determined that the
- user location annotation needs to be updated. This can happen in response to
- user interaction, a change in the user’s location, when the user tracking mode
- changes, or when the viewport changes.
-
- @note During user interaction with the map, this method may be called many
- times to update the user location annotation. Therefore, your implementation of
- this method should be as lightweight as possible to avoid negatively affecting
- performance.
- */
-- (void)update;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLUserLocationAnnotationView.m b/platform/ios/src/MGLUserLocationAnnotationView.m
deleted file mode 100644
index 9795565050..0000000000
--- a/platform/ios/src/MGLUserLocationAnnotationView.m
+++ /dev/null
@@ -1,100 +0,0 @@
-#import "MGLUserLocationAnnotationView.h"
-
-#import "MGLUserLocation.h"
-#import "MGLUserLocation_Private.h"
-#import "MGLAnnotationView_Private.h"
-#import "MGLAnnotation.h"
-#import "MGLMapView.h"
-#import "MGLCoordinateFormatter.h"
-#import "NSBundle+MGLAdditions.h"
-
-@interface MGLUserLocationAnnotationView()
-@property (nonatomic, weak, nullable) MGLMapView *mapView;
-@property (nonatomic, weak, nullable) MGLUserLocation *userLocation;
-@property (nonatomic, weak) CALayer *hitTestLayer;
-@end
-
-@implementation MGLUserLocationAnnotationView {
- MGLCoordinateFormatter *_accessibilityCoordinateFormatter;
-}
-
-- (instancetype)initWithFrame:(CGRect)frame
-{
- self = [super initWithFrame:frame];
- if (self == nil) return nil;
-
- self.accessibilityTraits = UIAccessibilityTraitButton | UIAccessibilityTraitAdjustable | UIAccessibilityTraitUpdatesFrequently;
-
- _accessibilityCoordinateFormatter = [[MGLCoordinateFormatter alloc] init];
- _accessibilityCoordinateFormatter.unitStyle = NSFormattingUnitStyleLong;
-
- return self;
-}
-
-- (CALayer *)hitTestLayer
-{
- return self.layer.presentationLayer;
-}
-
-- (void)update
-{
- // Left blank intentionally. Subclasses should usually override this in order to update the annotation’s appearance.
-}
-
-- (BOOL)isAccessibilityElement
-{
- return !self.hidden;
-}
-
-- (NSString *)accessibilityLabel
-{
- return self.userLocation.title;
-}
-
-- (NSString *)accessibilityValue
-{
- if (self.userLocation.subtitle)
- {
- return self.userLocation.subtitle;
- }
-
- // Each arcminute of longitude is at most about 1 nmi, too small for low zoom levels.
- // Each arcsecond of longitude is at most about 30 m, too small for all but the very highest of zoom levels.
- double zoomLevel = self.mapView.zoomLevel;
- _accessibilityCoordinateFormatter.allowsMinutes = zoomLevel > 8;
- _accessibilityCoordinateFormatter.allowsSeconds = zoomLevel > 20;
-
- return [_accessibilityCoordinateFormatter stringFromCoordinate:self.mapView.centerCoordinate];
-}
-
-- (CGRect)accessibilityFrame
-{
- return CGRectInset(self.frame, -15, -15);
-}
-
-- (UIBezierPath *)accessibilityPath
-{
- return [UIBezierPath bezierPathWithOvalInRect:self.frame];
-}
-
-- (void)accessibilityIncrement
-{
- [self.mapView accessibilityIncrement];
-}
-
-- (void)accessibilityDecrement
-{
- [self.mapView accessibilityDecrement];
-}
-
-- (void)setHidden:(BOOL)hidden
-{
- BOOL oldValue = super.hidden;
- [super setHidden:hidden];
- if (oldValue != hidden)
- {
- UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
- }
-}
-
-@end
diff --git a/platform/ios/src/MGLUserLocationAnnotationView_Private.h b/platform/ios/src/MGLUserLocationAnnotationView_Private.h
deleted file mode 100644
index 3e12beab34..0000000000
--- a/platform/ios/src/MGLUserLocationAnnotationView_Private.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#import "MGLUserLocationAnnotationView.h"
-#import "MGLUserLocation.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@class MGLMapView;
-
-@interface MGLUserLocationAnnotationView (Private)
-
-@property (nonatomic, weak, nullable) MGLUserLocation *userLocation;
-@property (nonatomic, weak, nullable) MGLMapView *mapView;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLUserLocationHeadingArrowLayer.h b/platform/ios/src/MGLUserLocationHeadingArrowLayer.h
deleted file mode 100644
index 6c01356944..0000000000
--- a/platform/ios/src/MGLUserLocationHeadingArrowLayer.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#import <QuartzCore/QuartzCore.h>
-#import "MGLUserLocationAnnotationView.h"
-#import "MGLUserLocationHeadingIndicator.h"
-
-@interface MGLUserLocationHeadingArrowLayer : CAShapeLayer <MGLUserLocationHeadingIndicator>
-
-- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView;
-- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy;
-- (void)updateTintColor:(CGColorRef)color;
-
-@end
diff --git a/platform/ios/src/MGLUserLocationHeadingArrowLayer.m b/platform/ios/src/MGLUserLocationHeadingArrowLayer.m
deleted file mode 100644
index d81cb5a09a..0000000000
--- a/platform/ios/src/MGLUserLocationHeadingArrowLayer.m
+++ /dev/null
@@ -1,59 +0,0 @@
-#import "MGLUserLocationHeadingArrowLayer.h"
-
-#import "MGLFaux3DUserLocationAnnotationView.h"
-#import "MGLGeometry.h"
-
-const CGFloat MGLUserLocationHeadingArrowSize = 8;
-
-@implementation MGLUserLocationHeadingArrowLayer
-
-- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView
-{
- CGFloat size = userLocationView.bounds.size.width + MGLUserLocationHeadingArrowSize;
-
- self = [super init];
- self.bounds = CGRectMake(0, 0, size, size);
- self.position = CGPointMake(CGRectGetMidX(userLocationView.bounds), CGRectGetMidY(userLocationView.bounds));
- self.path = [self arrowPath];
- self.fillColor = userLocationView.tintColor.CGColor;
- self.shouldRasterize = YES;
- self.rasterizationScale = UIScreen.mainScreen.scale;
- self.drawsAsynchronously = YES;
-
- self.strokeColor = UIColor.whiteColor.CGColor;
- self.lineWidth = 1.0;
- self.lineJoin = kCALineJoinRound;
-
- return self;
-}
-
-- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy
-{
- // unimplemented
-}
-
-- (void)updateTintColor:(CGColorRef)color
-{
- self.fillColor = color;
-}
-
-- (CGPathRef)arrowPath {
- CGFloat center = roundf(CGRectGetMidX(self.bounds));
- CGFloat size = MGLUserLocationHeadingArrowSize;
-
- CGPoint top = CGPointMake(center, 0);
- CGPoint left = CGPointMake(center - size, size);
- CGPoint right = CGPointMake(center + size, size);
- CGPoint middle = CGPointMake(center, size / M_PI);
-
- UIBezierPath *bezierPath = [UIBezierPath bezierPath];
- [bezierPath moveToPoint:top];
- [bezierPath addLineToPoint:left];
- [bezierPath addQuadCurveToPoint:right controlPoint:middle];
- [bezierPath addLineToPoint:top];
- [bezierPath closePath];
-
- return bezierPath.CGPath;
-}
-
-@end
diff --git a/platform/ios/src/MGLUserLocationHeadingBeamLayer.h b/platform/ios/src/MGLUserLocationHeadingBeamLayer.h
deleted file mode 100644
index 93f8ea17ab..0000000000
--- a/platform/ios/src/MGLUserLocationHeadingBeamLayer.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#import <QuartzCore/QuartzCore.h>
-#import "MGLUserLocationAnnotationView.h"
-#import "MGLUserLocationHeadingIndicator.h"
-
-@interface MGLUserLocationHeadingBeamLayer : CALayer <MGLUserLocationHeadingIndicator>
-
-- (MGLUserLocationHeadingBeamLayer *)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView;
-- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy;
-- (void)updateTintColor:(CGColorRef)color;
-
-@end
diff --git a/platform/ios/src/MGLUserLocationHeadingBeamLayer.m b/platform/ios/src/MGLUserLocationHeadingBeamLayer.m
deleted file mode 100644
index efe7e4db93..0000000000
--- a/platform/ios/src/MGLUserLocationHeadingBeamLayer.m
+++ /dev/null
@@ -1,104 +0,0 @@
-#import "MGLUserLocationHeadingBeamLayer.h"
-
-#import "MGLFaux3DUserLocationAnnotationView.h"
-#import "MGLGeometry.h"
-
-@implementation MGLUserLocationHeadingBeamLayer
-{
- CAShapeLayer *_maskLayer;
-}
-
-- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView
-{
- CGFloat size = MGLUserLocationAnnotationHaloSize;
-
- self = [super init];
- self.bounds = CGRectMake(0, 0, size, size);
- self.position = CGPointMake(CGRectGetMidX(userLocationView.bounds), CGRectGetMidY(userLocationView.bounds));
- self.contents = (__bridge id)[self gradientImageWithTintColor:userLocationView.tintColor.CGColor];
- self.contentsGravity = kCAGravityBottom;
- self.contentsScale = UIScreen.mainScreen.scale;
- self.opacity = 0.4;
- self.shouldRasterize = YES;
- self.rasterizationScale = UIScreen.mainScreen.scale;
- self.drawsAsynchronously = YES;
-
- _maskLayer = [CAShapeLayer layer];
- _maskLayer.frame = self.bounds;
- _maskLayer.path = [self clippingMaskForAccuracy:0];
- self.mask = _maskLayer;
-
- return self;
-}
-
-- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy
-{
- // recalculate the clipping mask based on updated accuracy
- _maskLayer.path = [self clippingMaskForAccuracy:accuracy];
-}
-
-- (void)updateTintColor:(CGColorRef)color
-{
- // redraw the raw tinted gradient
- self.contents = (__bridge id)[self gradientImageWithTintColor:color];
-}
-
-- (CGImageRef)gradientImageWithTintColor:(CGColorRef)tintColor
-{
- UIImage *image;
-
- CGFloat haloRadius = MGLUserLocationAnnotationHaloSize / 2.0;
-
- UIGraphicsBeginImageContextWithOptions(CGSizeMake(MGLUserLocationAnnotationHaloSize, haloRadius), NO, 0);
-
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
- CGContextRef context = UIGraphicsGetCurrentContext();
-
- // gradient from the tint color to no-alpha tint color
- CGFloat gradientLocations[] = {0.0, 1.0};
- CGGradientRef gradient = CGGradientCreateWithColors(
- colorSpace,
- (__bridge CFArrayRef)@[(__bridge id)tintColor,
- (id)CFBridgingRelease(CGColorCreateCopyWithAlpha(tintColor, 0))],
- gradientLocations);
-
- // draw the gradient from the center point to the edge (full halo radius)
- CGPoint centerPoint = CGPointMake(haloRadius, haloRadius);
- CGContextDrawRadialGradient(context, gradient,
- centerPoint, 0.0,
- centerPoint, haloRadius,
- kNilOptions);
-
- image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
-
- CGGradientRelease(gradient);
- CGColorSpaceRelease(colorSpace);
-
- return image.CGImage;
-}
-
-- (CGPathRef)clippingMaskForAccuracy:(CGFloat)accuracy
-{
- // size the mask using accuracy, but keep within a good display range
- CGFloat clippingDegrees = 90 - accuracy;
- clippingDegrees = fmin(clippingDegrees, 70); // most accurate
- clippingDegrees = fmax(clippingDegrees, 10); // least accurate
-
- CGRect ovalRect = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize);
- UIBezierPath *ovalPath = UIBezierPath.bezierPath;
-
- // clip the oval to ± incoming accuracy degrees (converted to radians), from the top
- [ovalPath addArcWithCenter:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))
- radius:CGRectGetWidth(ovalRect) / 2.0
- startAngle:MGLRadiansFromDegrees(-180 + clippingDegrees)
- endAngle:MGLRadiansFromDegrees(-clippingDegrees)
- clockwise:YES];
-
- [ovalPath addLineToPoint:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))];
- [ovalPath closePath];
-
- return ovalPath.CGPath;
-}
-
-@end
diff --git a/platform/ios/src/MGLUserLocationHeadingIndicator.h b/platform/ios/src/MGLUserLocationHeadingIndicator.h
deleted file mode 100644
index 61476b96a2..0000000000
--- a/platform/ios/src/MGLUserLocationHeadingIndicator.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#import <QuartzCore/QuartzCore.h>
-#import "MGLUserLocationAnnotationView.h"
-
-@protocol MGLUserLocationHeadingIndicator <NSObject>
-
-- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView;
-- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy;
-- (void)updateTintColor:(CGColorRef)color;
-
-@end
diff --git a/platform/ios/src/MGLUserLocation_Private.h b/platform/ios/src/MGLUserLocation_Private.h
deleted file mode 100644
index 48f6d40e8c..0000000000
--- a/platform/ios/src/MGLUserLocation_Private.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#import "MGLUserLocation.h"
-
-#import <CoreLocation/CoreLocation.h>
-
-@class MGLMapView;
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface MGLUserLocation (Private)
-
-@property (nonatomic, weak) MGLMapView *mapView;
-@property (nonatomic, readwrite, nullable) CLLocation *location;
-@property (nonatomic, readwrite, nullable) CLHeading *heading;
-
-- (instancetype)initWithMapView:(MGLMapView *)mapView;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/Mapbox-Prefix.pch b/platform/ios/src/Mapbox-Prefix.pch
deleted file mode 100644
index 6754020861..0000000000
--- a/platform/ios/src/Mapbox-Prefix.pch
+++ /dev/null
@@ -1 +0,0 @@
-#import "MMENamespacedDependencies.h"
diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h
deleted file mode 100644
index 98e673577c..0000000000
--- a/platform/ios/src/Mapbox.h
+++ /dev/null
@@ -1,77 +0,0 @@
-#import <Foundation/Foundation.h>
-
-#import "MGLFoundation.h"
-
-/// Project version number for Mapbox.
-FOUNDATION_EXPORT MGL_EXPORT double MapboxVersionNumber;
-
-/// Project version string for Mapbox.
-FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[];
-
-#import "MGLAnnotationView.h"
-#import "MGLAccountManager.h"
-#import "MGLAnnotation.h"
-#import "MGLAnnotationImage.h"
-#import "MGLCalloutView.h"
-#import "MGLClockDirectionFormatter.h"
-#import "MGLCluster.h"
-#import "MGLCompassButton.h"
-#import "MGLCompassDirectionFormatter.h"
-#import "MGLCoordinateFormatter.h"
-#import "MGLDistanceFormatter.h"
-#import "MGLFeature.h"
-#import "MGLGeometry.h"
-#import "MGLLight.h"
-#import "MGLMapCamera.h"
-#import "MGLMapView.h"
-#import "MGLMapView+IBAdditions.h"
-#import "MGLMapViewDelegate.h"
-#import "MGLMultiPoint.h"
-#import "MGLOfflinePack.h"
-#import "MGLOfflineRegion.h"
-#import "MGLOfflineStorage.h"
-#import "MGLOverlay.h"
-#import "MGLPointAnnotation.h"
-#import "MGLPointCollection.h"
-#import "MGLPolygon.h"
-#import "MGLPolyline.h"
-#import "MGLShape.h"
-#import "MGLShapeCollection.h"
-#import "MGLStyle.h"
-#import "MGLStyleLayer.h"
-#import "MGLForegroundStyleLayer.h"
-#import "MGLVectorStyleLayer.h"
-#import "MGLFillExtrusionStyleLayer.h"
-#import "MGLFillStyleLayer.h"
-#import "MGLLineStyleLayer.h"
-#import "MGLSymbolStyleLayer.h"
-#import "MGLRasterStyleLayer.h"
-#import "MGLCircleStyleLayer.h"
-#import "MGLHeatmapStyleLayer.h"
-#import "MGLHillshadeStyleLayer.h"
-#import "MGLBackgroundStyleLayer.h"
-#import "MGLOpenGLStyleLayer.h"
-#import "MGLSource.h"
-#import "MGLTileSource.h"
-#import "MGLVectorTileSource.h"
-#import "MGLShapeSource.h"
-#import "MGLComputedShapeSource.h"
-#import "MGLRasterTileSource.h"
-#import "MGLRasterDEMSource.h"
-#import "MGLImageSource.h"
-#import "MGLShapeOfflineRegion.h"
-#import "MGLTilePyramidOfflineRegion.h"
-#import "MGLTypes.h"
-#import "MGLUserLocation.h"
-#import "MGLUserLocationAnnotationView.h"
-#import "NSValue+MGLAdditions.h"
-#import "MGLStyleValue.h"
-#import "MGLAttributionInfo.h"
-#import "MGLMapSnapshotter.h"
-#import "NSExpression+MGLAdditions.h"
-#import "NSPredicate+MGLAdditions.h"
-#import "MGLLocationManager.h"
-#import "MGLLoggingConfiguration.h"
-#import "MGLNetworkConfiguration.h"
-#import "MGLAttributedExpression.h"
-#import "MGLSDKMetricsManager.h"
diff --git a/platform/ios/src/NSOrthography+MGLAdditions.h b/platform/ios/src/NSOrthography+MGLAdditions.h
deleted file mode 100644
index a552fc7774..0000000000
--- a/platform/ios/src/NSOrthography+MGLAdditions.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#import <Foundation/Foundation.h>
-
-@interface NSOrthography (NSOrthography_MGLAdditions)
-
-/**
- Returns a four-letter ISO 15924 code representing the name of the dominant
- script for a given language.
-
- On iOS 11 or newer, this method wraps
- `+[NSOrthography defaultOrthographyForLanguage:]` and supports any language.
- On iOS 10 and older, this method only returns values for Mapbox
- Streets-supported languages.
-
- @param language The ISO-639 code representing a language.
- */
-+ (NSString *)mgl_dominantScriptForMapboxStreetsLanguage:(NSString *)language;
-
-@end
diff --git a/platform/ios/src/NSOrthography+MGLAdditions.m b/platform/ios/src/NSOrthography+MGLAdditions.m
deleted file mode 100644
index f48a2ffcbe..0000000000
--- a/platform/ios/src/NSOrthography+MGLAdditions.m
+++ /dev/null
@@ -1,37 +0,0 @@
-#import "NSOrthography+MGLAdditions.h"
-
-@implementation NSOrthography (MGLAdditions)
-
-+ (NSString *)mgl_dominantScriptForMapboxStreetsLanguage:(NSString *)language {
- if (@available(iOS 11.0, *)) {
- NSLocale *locale = [NSLocale localeWithLocaleIdentifier:language];
- NSOrthography *orthography = [NSOrthography defaultOrthographyForLanguage:locale.localeIdentifier];
-
- return orthography.dominantScript;
- }
-
- // Manually map Mapbox Streets languages to ISO 15924 script codes.
- NSSet *latinLanguages = [NSSet setWithObjects:@"de", @"en", @"es", @"fr", @"pt", nil];
- NSSet *hansLanguages = [NSSet setWithObjects:@"zh", @"zh-Hans", nil];
-
- if ([latinLanguages containsObject:language]) {
- return @"Latn";
- } else if ([hansLanguages containsObject:language]) {
- return @"Hans";
- } else if ([language isEqualToString:@"zh-Hant"]) {
- return @"Hant";
- } else if ([language isEqualToString:@"ru"]) {
- return @"Cyrl";
- } else if ([language isEqualToString:@"ar"]) {
- return @"Arab";
- } else if ([language isEqualToString:@"ja"]) {
- return @"Jpan";
- } else if ([language isEqualToString:@"ko"]) {
- return @"Kore";
- } else {
- // Code for undetermined script
- return @"Zyyy";
- }
-}
-
-@end
diff --git a/platform/ios/src/UIColor+MGLAdditions.h b/platform/ios/src/UIColor+MGLAdditions.h
deleted file mode 100644
index 19702fa105..0000000000
--- a/platform/ios/src/UIColor+MGLAdditions.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#import <UIKit/UIKit.h>
-
-#include <mbgl/util/color.hpp>
-#include <mbgl/style/property_value.hpp>
-
-@interface UIColor (MGLAdditions)
-
-- (mbgl::Color)mgl_color;
-
-- (mbgl::style::PropertyValue<mbgl::Color>)mgl_colorPropertyValue;
-
-+ (UIColor *)mgl_colorWithColor:(mbgl::Color)color;
-
-@end
-
-@interface NSExpression (MGLColorAdditions)
-
-+ (NSExpression *)mgl_expressionForRGBComponents:(NSArray<NSExpression *> *)components;
-+ (NSExpression *)mgl_expressionForRGBAComponents:(NSArray<NSExpression *> *)components;
-+ (UIColor *)mgl_colorWithRGBComponents:(NSArray<NSExpression *> *)components;
-
-@end
diff --git a/platform/ios/src/UIColor+MGLAdditions.mm b/platform/ios/src/UIColor+MGLAdditions.mm
deleted file mode 100644
index 68e77f5b10..0000000000
--- a/platform/ios/src/UIColor+MGLAdditions.mm
+++ /dev/null
@@ -1,86 +0,0 @@
-#import "UIColor+MGLAdditions.h"
-
-@implementation UIColor (MGLAdditions)
-
-- (mbgl::Color)mgl_color
-{
- CGFloat r, g, b, a;
- [self getRed:&r green:&g blue:&b alpha:&a];
- // UIColor provides non-premultiplied color components, so we have to premultiply each
- // color component with the alpha value to transform it into a valid
- // mbgl::Color which expects premultiplied color components.
- return { static_cast<float>(r*a), static_cast<float>(g*a), static_cast<float>(b*a), static_cast<float>(a) };
-}
-
-- (mbgl::style::PropertyValue<mbgl::Color>)mgl_colorPropertyValue
-{
- mbgl::Color color = self.mgl_color;
- return {{ color.r, color.g, color.b, color.a }};
-}
-
-+ (UIColor *)mgl_colorWithColor:(mbgl::Color)color
-{
- // If there is no alpha value, return original color values.
- if (color.a == 0.0f) {
- return [UIColor colorWithRed:color.r green:color.g blue:color.b alpha:color.a];
- }
-
- // mbgl::Color provides premultiplied color components, so we have to convert color
- // components to non-premultiplied values to return a valid UIColor object.
- float red = static_cast<float>((color.r / color.a));
- float green = static_cast<float>((color.g / color.a));
- float blue = static_cast<float>((color.b / color.a));
-
- return [UIColor colorWithRed:red green:green blue:blue alpha:color.a];
-}
-
-@end
-
-@implementation NSExpression (MGLColorAdditions)
-
-+ (NSExpression *)mgl_expressionForRGBComponents:(NSArray<NSExpression *> *)components {
- if (UIColor *color = [self mgl_colorWithRGBComponents:components]) {
- return [NSExpression expressionForConstantValue:color];
- }
-
- NSExpression *color = [NSExpression expressionForConstantValue:[UIColor class]];
- NSExpression *alpha = [NSExpression expressionForConstantValue:@1.0];
- return [NSExpression expressionForFunction:color
- selectorName:@"colorWithRed:green:blue:alpha:"
- arguments:[components arrayByAddingObject:alpha]];
-}
-
-+ (NSExpression *)mgl_expressionForRGBAComponents:(NSArray<NSExpression *> *)components {
- if (UIColor *color = [self mgl_colorWithRGBComponents:components]) {
- return [NSExpression expressionForConstantValue:color];
- }
-
- NSExpression *color = [NSExpression expressionForConstantValue:[UIColor class]];
- return [NSExpression expressionForFunction:color
- selectorName:@"colorWithRed:green:blue:alpha:"
- arguments:components];
-}
-
-+ (UIColor *)mgl_colorWithRGBComponents:(NSArray<NSExpression *> *)components {
- if (components.count < 3 || components.count > 4) {
- return nil;
- }
-
- for (NSExpression *component in components) {
- if (component.expressionType != NSConstantValueExpressionType) {
- return nil;
- }
-
- NSNumber *number = (NSNumber *)component.constantValue;
- if (![number isKindOfClass:[NSNumber class]]) {
- return nil;
- }
- }
-
- return [UIColor colorWithRed:[components[0].constantValue doubleValue] / 255.0
- green:[components[1].constantValue doubleValue] / 255.0
- blue:[components[2].constantValue doubleValue] / 255.0
- alpha:components.count == 3 ? 1.0 : [components[3].constantValue doubleValue]];
-}
-
-@end
diff --git a/platform/ios/src/UIDevice+MGLAdditions.h b/platform/ios/src/UIDevice+MGLAdditions.h
deleted file mode 100644
index a61aedf2db..0000000000
--- a/platform/ios/src/UIDevice+MGLAdditions.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#import <UIKit/UIKit.h>
-
-@interface UIDevice (MGLAdditions)
-
-@property (nonatomic, readonly) BOOL mgl_isLegacyDevice;
-
-@end
diff --git a/platform/ios/src/UIDevice+MGLAdditions.m b/platform/ios/src/UIDevice+MGLAdditions.m
deleted file mode 100644
index 3522c07401..0000000000
--- a/platform/ios/src/UIDevice+MGLAdditions.m
+++ /dev/null
@@ -1,53 +0,0 @@
-#import "UIDevice+MGLAdditions.h"
-#include <sys/sysctl.h>
-
-@implementation UIDevice (MGLAdditions)
-
-- (NSString *)modelString {
-#if TARGET_OS_SIMULATOR
- return [[[NSProcessInfo processInfo] environment] objectForKey:@"SIMULATOR_MODEL_IDENTIFIER"];
-#else
- char *typeSpecifier = "hw.machine";
-
- size_t size;
- sysctlbyname(typeSpecifier, NULL, &size, NULL, 0);
-
- char *answer = malloc(size);
- sysctlbyname(typeSpecifier, answer, &size, NULL, 0);
-
- NSString *results = [NSString stringWithCString:answer encoding:NSUTF8StringEncoding];
-
- free(answer);
- return results;
-#endif
-}
-
-- (BOOL)mgl_isLegacyDevice {
- // This is a list of supported devices that cannot maintain a reasonable frame
- // rate under typical load. For brevity, unsupported devices are not included.
- NSSet *blacklist = [NSSet setWithObjects:
- @"iPhone4", // iPhone 4s
- @"iPhone5", // iPhone 5, 5c
- @"iPhone6", // iPhone 5s
-
- @"iPad2", // iPad 2, Mini
- @"iPad3", // iPad 3
- @"iPad4", // iPad Air, Mini 2, Mini 3
-
- @"iPod5", // iPod Touch 5
-
- nil
- ];
-
- NSString *model = [self modelString];
-
- for (NSString *blacklistedModel in blacklist) {
- if ([model hasPrefix:[blacklistedModel stringByAppendingString:@","]]) {
- return YES;
- }
- }
-
- return NO;
-}
-
-@end
diff --git a/platform/ios/src/UIImage+MGLAdditions.h b/platform/ios/src/UIImage+MGLAdditions.h
deleted file mode 100644
index 23fac9dbeb..0000000000
--- a/platform/ios/src/UIImage+MGLAdditions.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#import <UIKit/UIKit.h>
-
-#import "MGLTypes.h"
-
-#include <mbgl/style/image.hpp>
-
-NS_ASSUME_NONNULL_BEGIN
-
-FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const MGLResourceNotFoundException;
-
-@interface UIImage (MGLAdditions)
-
-- (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)styleImage;
-
-- (nullable instancetype)initWithMGLPremultipliedImage:(const mbgl::PremultipliedImage&&)mbglImage scale:(CGFloat)scale;
-
-- (std::unique_ptr<mbgl::style::Image>)mgl_styleImageWithIdentifier:(NSString *)identifier;
-
-- (mbgl::PremultipliedImage)mgl_premultipliedImage;
-
-+ (UIImage *)mgl_resourceImageNamed:(NSString *)imageName;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/UIImage+MGLAdditions.mm b/platform/ios/src/UIImage+MGLAdditions.mm
deleted file mode 100644
index 9d05abd6ca..0000000000
--- a/platform/ios/src/UIImage+MGLAdditions.mm
+++ /dev/null
@@ -1,64 +0,0 @@
-#import "UIImage+MGLAdditions.h"
-#import "NSBundle+MGLAdditions.h"
-
-#include <mbgl/util/image+MGLAdditions.hpp>
-
-const MGLExceptionName MGLResourceNotFoundException = @"MGLResourceNotFoundException";
-
-@implementation UIImage (MGLAdditions)
-
-- (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)styleImage
-{
- CGImageRef image = CGImageCreateWithMGLPremultipliedImage(styleImage->getImage().clone());
- if (!image) {
- return nil;
- }
-
- if (self = [self initWithCGImage:image scale:styleImage->getPixelRatio() orientation:UIImageOrientationUp])
- {
- if (styleImage->isSdf())
- {
- self = [self imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
- }
- }
- CGImageRelease(image);
- return self;
-}
-
-- (nullable instancetype)initWithMGLPremultipliedImage:(const mbgl::PremultipliedImage&&)mbglImage scale:(CGFloat)scale
-{
- CGImageRef image = CGImageCreateWithMGLPremultipliedImage(mbglImage.clone());
- if (!image) {
- return nil;
- }
-
- self = [self initWithCGImage:image scale:scale orientation:UIImageOrientationUp];
-
- CGImageRelease(image);
- return self;
-}
-
-- (std::unique_ptr<mbgl::style::Image>)mgl_styleImageWithIdentifier:(NSString *)identifier {
- BOOL isTemplate = self.renderingMode == UIImageRenderingModeAlwaysTemplate;
- return std::make_unique<mbgl::style::Image>([identifier UTF8String],
- self.mgl_premultipliedImage,
- float(self.scale), isTemplate);
-}
-
-- (mbgl::PremultipliedImage)mgl_premultipliedImage {
- return MGLPremultipliedImageFromCGImage(self.CGImage);
-}
-
-+ (UIImage *)mgl_resourceImageNamed:(NSString *)imageName {
- UIImage *image = [UIImage imageNamed:imageName
- inBundle:[NSBundle mgl_frameworkBundle]
- compatibleWithTraitCollection:nil];
-
- if (!image) {
- [NSException raise:MGLResourceNotFoundException format:@"The resource named “%@” could not be found in the Mapbox framework bundle.", imageName];
- }
-
- return image;
-}
-
-@end
diff --git a/platform/ios/src/UIView+MGLAdditions.h b/platform/ios/src/UIView+MGLAdditions.h
deleted file mode 100644
index ef074215b3..0000000000
--- a/platform/ios/src/UIView+MGLAdditions.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#import <UIKit/UIKit.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface UIView (MGLAdditions)
-
-- (nullable UIViewController *)mgl_viewControllerForLayoutGuides;
-
-- (NSLayoutYAxisAnchor *)mgl_safeTopAnchor;
-
-- (NSLayoutXAxisAnchor *)mgl_safeLeadingAnchor;
-
-- (NSLayoutYAxisAnchor *)mgl_safeBottomAnchor;
-
-- (NSLayoutXAxisAnchor *)mgl_safeTrailingAnchor;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/UIView+MGLAdditions.m b/platform/ios/src/UIView+MGLAdditions.m
deleted file mode 100644
index 43c54409bd..0000000000
--- a/platform/ios/src/UIView+MGLAdditions.m
+++ /dev/null
@@ -1,69 +0,0 @@
-#import "UIView+MGLAdditions.h"
-
-@implementation UIView (MGLAdditions)
-
-- (UIViewController *)mgl_viewControllerForLayoutGuides
-{
- // Per -[UIResponder nextResponder] documentation, a UIView’s next responder
- // is its managing UIViewController if applicable, or otherwise its
- // superview. UIWindow’s next responder is UIApplication, which has no next
- // responder.
- UIResponder *laterResponder = self;
- while ([laterResponder isKindOfClass:[UIView class]])
- {
- laterResponder = laterResponder.nextResponder;
- }
- if ([laterResponder isKindOfClass:[UIViewController class]])
- {
- return (UIViewController *)laterResponder;
- }
- return nil;
-}
-
-- (NSLayoutYAxisAnchor *)mgl_safeTopAnchor {
- if (@available(iOS 11.0, *)) {
- return self.safeAreaLayoutGuide.topAnchor;
- } else {
- UIViewController *viewController = self.mgl_viewControllerForLayoutGuides;
- BOOL useLayoutGuides = viewController.view && viewController.automaticallyAdjustsScrollViewInsets;
- if (useLayoutGuides) {
- return viewController.topLayoutGuide.bottomAnchor;
- }
- else {
- return self.topAnchor;
- }
- }
-}
-
-- (NSLayoutXAxisAnchor *)mgl_safeLeadingAnchor {
- if (@available(iOS 11.0, *)) {
- return self.safeAreaLayoutGuide.leadingAnchor;
- } else {
- return self.leadingAnchor;
- }
-}
-
-- (NSLayoutYAxisAnchor *)mgl_safeBottomAnchor {
- if (@available(iOS 11.0, *)) {
- return self.safeAreaLayoutGuide.bottomAnchor;
- } else {
- UIViewController *viewController = self.mgl_viewControllerForLayoutGuides;
- BOOL useLayoutGuides = viewController.view && viewController.automaticallyAdjustsScrollViewInsets;
- if (useLayoutGuides) {
- return viewController.bottomLayoutGuide.topAnchor;
- }
- else {
- return self.bottomAnchor;
- }
- }
-}
-
-- (NSLayoutXAxisAnchor *)mgl_safeTrailingAnchor {
- if (@available(iOS 11.0, *)) {
- return self.safeAreaLayoutGuide.trailingAnchor;
- } else {
- return self.trailingAnchor;
- }
-}
-
-@end
diff --git a/platform/ios/src/UIViewController+MGLAdditions.h b/platform/ios/src/UIViewController+MGLAdditions.h
deleted file mode 100644
index b60375a6f6..0000000000
--- a/platform/ios/src/UIViewController+MGLAdditions.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#import <UIKit/UIKit.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface UIViewController (MGLAdditions)
-
-@property (readonly) UIViewController *mgl_topMostViewController;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/UIViewController+MGLAdditions.m b/platform/ios/src/UIViewController+MGLAdditions.m
deleted file mode 100644
index 746fdd8db8..0000000000
--- a/platform/ios/src/UIViewController+MGLAdditions.m
+++ /dev/null
@@ -1,22 +0,0 @@
-#import "UIViewController+MGLAdditions.h"
-
-@implementation UIViewController (MGLAdditions)
-
-- (UIViewController *)mgl_topMostViewController
-{
- if ([self isKindOfClass:[UINavigationController class]])
- {
- return [[(UINavigationController *)self visibleViewController] mgl_topMostViewController];
- }
- else if ([self isKindOfClass:[UITabBarController class]])
- {
- return [[(UITabBarController *)self selectedViewController] mgl_topMostViewController];
- }
- else if (self.presentedViewController)
- {
- return [self.presentedViewController mgl_topMostViewController];
- }
- return self;
-}
-
-@end