summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2019-06-21 17:45:36 -0700
committerJason Wray <friedbunny@users.noreply.github.com>2019-06-21 17:45:36 -0700
commitf7f74a6deb57ad4ac22a30604a0dce39024feca2 (patch)
treea65905601da41fbcbf771235c6ede56389950ce2
parentc40b7279a2df68edb6c7b1ba11586bcbc724e21c (diff)
downloadqtlocation-mapboxgl-f7f74a6deb57ad4ac22a30604a0dce39024feca2.tar.gz
[ios, macos] Added completion handlers to animated MGLMapView methods
-rw-r--r--platform/darwin/test/MGLMapViewTests.m117
-rw-r--r--platform/ios/CHANGELOG.md7
-rw-r--r--platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.m9
-rw-r--r--platform/ios/src/MGLMapView.h187
-rw-r--r--platform/ios/src/MGLMapView.mm176
-rw-r--r--platform/macos/CHANGELOG.md7
-rw-r--r--platform/macos/src/MGLMapView.h108
-rw-r--r--platform/macos/src/MGLMapView.mm61
8 files changed, 545 insertions, 127 deletions
diff --git a/platform/darwin/test/MGLMapViewTests.m b/platform/darwin/test/MGLMapViewTests.m
index 9a8e7bdb64..9ad7016a61 100644
--- a/platform/darwin/test/MGLMapViewTests.m
+++ b/platform/darwin/test/MGLMapViewTests.m
@@ -1,5 +1,12 @@
#import <Mapbox/Mapbox.h>
#import <XCTest/XCTest.h>
+#import <TargetConditionals.h>
+
+#if TARGET_OS_IPHONE
+ #define MGLEdgeInsetsZero UIEdgeInsetsZero
+#else
+ #define MGLEdgeInsetsZero NSEdgeInsetsZero
+#endif
static MGLMapView *mapView;
@@ -27,13 +34,13 @@ static MGLMapView *mapView;
MGLCoordinateBounds leftAntimeridianBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(-75, 175), CLLocationCoordinate2DMake(75, 180));
CGRect leftAntimeridianBoundsRect = [mapView convertCoordinateBounds:leftAntimeridianBounds toRectToView:mapView];
-
+
MGLCoordinateBounds rightAntimeridianBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(-75, -180), CLLocationCoordinate2DMake(75, -175));
CGRect rightAntimeridianBoundsRect = [mapView convertCoordinateBounds:rightAntimeridianBounds toRectToView:mapView];
-
+
MGLCoordinateBounds spanningBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(24, 140), CLLocationCoordinate2DMake(44, 240));
CGRect spanningBoundsRect = [mapView convertCoordinateBounds:spanningBounds toRectToView:mapView];
-
+
// If the resulting CGRect from -convertCoordinateBounds:toRectToView:
// intersects the set of bounds to the left and right of the
// antimeridian, then we know that the CGRect spans across the antimeridian
@@ -41,4 +48,108 @@ static MGLMapView *mapView;
XCTAssertTrue(CGRectIntersectsRect(spanningBoundsRect, rightAntimeridianBoundsRect), @"Something");
}
+#if TARGET_OS_IPHONE
+- (void)testUserTrackingModeCompletion {
+ __block BOOL completed = NO;
+ [mapView setUserTrackingMode:MGLUserTrackingModeNone animated:NO completionHandler:^{
+ completed = YES;
+ }];
+ XCTAssertTrue(completed, @"Completion block should get called synchronously when the mode is unchanged.");
+
+ completed = NO;
+ [mapView setUserTrackingMode:MGLUserTrackingModeNone animated:YES completionHandler:^{
+ completed = YES;
+ }];
+ XCTAssertTrue(completed, @"Completion block should get called synchronously when the mode is unchanged.");
+
+ completed = NO;
+ [mapView setUserTrackingMode:MGLUserTrackingModeFollow animated:NO completionHandler:^{
+ completed = YES;
+ }];
+ XCTAssertTrue(completed, @"Completion block should get called synchronously when there’s no location.");
+
+ completed = NO;
+ [mapView setUserTrackingMode:MGLUserTrackingModeFollowWithHeading animated:YES completionHandler:^{
+ completed = YES;
+ }];
+ XCTAssertTrue(completed, @"Completion block should get called synchronously when there’s no location.");
+}
+
+- (void)testTargetCoordinateCompletion {
+ __block BOOL completed = NO;
+ [mapView setTargetCoordinate:kCLLocationCoordinate2DInvalid animated:NO completionHandler:^{
+ completed = YES;
+ }];
+ XCTAssertTrue(completed, @"Completion block should get called synchronously when the target coordinate is unchanged.");
+
+ completed = NO;
+ [mapView setTargetCoordinate:kCLLocationCoordinate2DInvalid animated:YES completionHandler:^{
+ completed = YES;
+ }];
+ XCTAssertTrue(completed, @"Completion block should get called synchronously when the target coordinate is unchanged.");
+
+ completed = NO;
+ [mapView setUserTrackingMode:MGLUserTrackingModeFollow animated:NO completionHandler:nil];
+ [mapView setTargetCoordinate:CLLocationCoordinate2DMake(39.128106, -84.516293) animated:YES completionHandler:^{
+ completed = YES;
+ }];
+ XCTAssertTrue(completed, @"Completion block should get called synchronously when not tracking user course.");
+
+ completed = NO;
+ [mapView setUserTrackingMode:MGLUserTrackingModeFollowWithCourse animated:NO completionHandler:nil];
+ [mapView setTargetCoordinate:CLLocationCoordinate2DMake(39.224407, -84.394957) animated:YES completionHandler:^{
+ completed = YES;
+ }];
+ XCTAssertTrue(completed, @"Completion block should get called synchronously when there’s no location.");
+}
+#endif
+
+- (void)testVisibleCoordinatesCompletion {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should get called when not animated"];
+ MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1));
+ [mapView setVisibleCoordinateBounds:unitBounds edgePadding:MGLEdgeInsetsZero animated:NO completionHandler:^{
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[expectation] timeout:1];
+
+#if TARGET_OS_IPHONE
+ expectation = [self expectationWithDescription:@"Completion block should get called when animated"];
+ CLLocationCoordinate2D antiunitCoordinates[] = {
+ CLLocationCoordinate2DMake(0, 0),
+ CLLocationCoordinate2DMake(-1, -1),
+ };
+ [mapView setVisibleCoordinates:antiunitCoordinates
+ count:sizeof(antiunitCoordinates) / sizeof(antiunitCoordinates[0])
+ edgePadding:UIEdgeInsetsZero
+ direction:0
+ duration:0
+ animationTimingFunction:nil
+ completionHandler:^{
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[expectation] timeout:1];
+#endif
+}
+
+- (void)testShowAnnotationsCompletion {
+ __block BOOL completed = NO;
+ [mapView showAnnotations:@[] edgePadding:MGLEdgeInsetsZero animated:NO completionHandler:^{
+ completed = YES;
+ }];
+ XCTAssertTrue(completed, @"Completion block should get called synchronously when there are no annotations to show.");
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should get called when not animated"];
+ MGLPointAnnotation *annotation = [[MGLPointAnnotation alloc] init];
+ [mapView showAnnotations:@[annotation] edgePadding:MGLEdgeInsetsZero animated:NO completionHandler:^{
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[expectation] timeout:1];
+
+ expectation = [self expectationWithDescription:@"Completion block should get called when animated."];
+ [mapView showAnnotations:@[annotation] edgePadding:MGLEdgeInsetsZero animated:YES completionHandler:^{
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[expectation] timeout:1];
+}
+
@end
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index 01d70385ac..ec4aa42cc3 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -5,6 +5,13 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
## 5.2.0
* Fixed an issue where the two-finger tilt gesture would continue after lifting one finger. ([#14969](https://github.com/mapbox/mapbox-gl-native/pull/14969))
+* Added variants of several animated `MGLMapView` methods that accept completion handlers ([#14381](https://github.com/mapbox/mapbox-gl-native/pull/14381)):
+ * `-[MGLMapView setVisibleCoordinateBounds:edgePadding:animated:completionHandler:]`
+ * `-[MGLMapView setContentInset:animated:completionHandler:]`
+ * `-[MGLMapView setUserTrackingMode:animated:completionHandler:]`
+ * `-[MGLMapView setTargetCoordinate:animated:completionHandler:]`
+ * `-[MGLMapView showAnnotations:edgePadding:animated:completionHandler:]`
+ * `-[MGLMapView selectAnnotation:animated:completionHandler:]`
## 5.1.0 - June 19, 2019
diff --git a/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.m b/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.m
index 777afe3380..8b5d94f6f3 100644
--- a/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.m
+++ b/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.m
@@ -112,13 +112,16 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// Also note, that at this point, the internal mechanism that determines if
// an annotation view is offscreen and should be put back in the reuse queue
// will have run, and `viewForAnnotation` may return nil
-
- [self.mapView selectAnnotation:point moveIntoView:moveIntoView animateSelection:animateSelection];
+
+ XCTestExpectation *selectionCompleted = [self expectationWithDescription:@"Selection completed"];
+ [self.mapView selectAnnotation:point moveIntoView:moveIntoView animateSelection:animateSelection completionHandler:^{
+ [selectionCompleted fulfill];
+ }];
// Animated selection takes MGLAnimationDuration (0.3 seconds), so wait a little
// longer. We don't need to wait as long if we're not animated (but we do
// want the runloop to tick over)
- [self waitFor:animateSelection ? 0.4: 0.05];
+ [self waitForExpectations:@[selectionCompleted] timeout:animateSelection ? 0.4: 0.05];
UIView *annotationViewAfterSelection = [self.mapView viewForAnnotation:point];
CLLocationCoordinate2D mapCenterAfterSelection = self.mapView.centerCoordinate;
diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h
index 3baae2ee3c..67b8033c55 100644
--- a/platform/ios/src/MGLMapView.h
+++ b/platform/ios/src/MGLMapView.h
@@ -504,6 +504,9 @@ MGL_EXPORT
/**
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
@@ -515,6 +518,20 @@ MGL_EXPORT
- (void)setUserTrackingMode:(MGLUserTrackingMode)mode animated:(BOOL)animated;
/**
+ 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`.
@@ -604,6 +621,9 @@ MGL_EXPORT
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
@@ -611,6 +631,28 @@ MGL_EXPORT
*/
- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate animated:(BOOL)animated;
+/**
+ 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
/**
@@ -892,13 +934,16 @@ MGL_EXPORT
- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds animated:(BOOL)animated;
/**
- Changes the receiver’s viewport to fit the given coordinate bounds and
- optionally some additional padding on each side.
+ 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
@@ -909,8 +954,26 @@ MGL_EXPORT
- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated;
/**
- Changes the receiver’s viewport to fit all of the given coordinates and
- optionally some additional padding on each side.
+ 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
@@ -928,8 +991,8 @@ MGL_EXPORT
- (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 and
- optionally some additional padding on each side.
+ 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
@@ -970,6 +1033,9 @@ MGL_EXPORT
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
@@ -980,6 +1046,23 @@ MGL_EXPORT
- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated;
/**
+ 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;
@@ -1117,8 +1200,8 @@ MGL_EXPORT
- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds;
/**
- Returns the camera that best fits the given coordinate bounds, optionally with
- some additional padding on each side.
+ 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
@@ -1135,8 +1218,9 @@ MGL_EXPORT
- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets;
/**
- Returns the camera that best fits the given coordinate bounds, with the specified camera,
- optionally with some additional padding on each side.
+ 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.
@@ -1155,8 +1239,8 @@ MGL_EXPORT
- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets;
/**
- Returns the camera that best fits the given shape, with the specified camera,
- optionally with some additional padding on each side.
+ 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.
@@ -1164,8 +1248,8 @@ MGL_EXPORT
@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.
+ (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
@@ -1174,16 +1258,17 @@ MGL_EXPORT
- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingShape:(MGLShape *)shape edgePadding:(UIEdgeInsets)insets;
/**
- Returns the camera that best fits the given shape, with the specified direction,
- optionally with some additional padding on each side.
+ 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 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.
+ (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
@@ -1192,7 +1277,7 @@ MGL_EXPORT
- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(UIEdgeInsets)insets;
/**
- Returns the point in this view’s coordinate system on which to "anchor" in
+ 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
@@ -1243,6 +1328,9 @@ MGL_EXPORT
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.
+
+ 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
@@ -1251,6 +1339,29 @@ MGL_EXPORT
*/
- (void)setContentInset:(UIEdgeInsets)contentInset animated:(BOOL)animated;
+/**
+ 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.
+
+ @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
/**
@@ -1501,6 +1612,9 @@ MGL_EXPORT
| `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.
@@ -1511,15 +1625,40 @@ MGL_EXPORT
- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated;
/**
+ 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. This method should be
- considered "alpha" and as such is liable to change.
+ 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 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;
+- (void)selectAnnotation:(id <MGLAnnotation>)annotation moveIntoView:(BOOL)moveIntoView animateSelection:(BOOL)animateSelection completionHandler:(nullable void (^)(void))completion;
/**
Deselects an annotation and hides its callout view.
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 9693e3cff9..45eb345149 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -1021,22 +1021,30 @@ public:
- (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:nil];
+ [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];
+ [self didUpdateLocationWithUserTrackingAnimated:animated completionHandler:completion];
}
// Compass, logo and attribution button constraints needs to be updated.z
@@ -1873,7 +1881,7 @@ public:
{
CGPoint calloutPoint = [singleTap locationInView:self];
CGRect positionRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:calloutPoint];
- [self selectAnnotation:annotation moveIntoView:YES animateSelection:YES calloutPositioningRect:positionRect];
+ [self selectAnnotation:annotation moveIntoView:YES animateSelection:YES calloutPositioningRect:positionRect completionHandler:nil];
}
else if (self.selectedAnnotation)
{
@@ -3331,28 +3339,14 @@ public:
- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated
{
- 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
- animated:animated];
+ [self setVisibleCoordinateBounds:bounds edgePadding:insets animated:animated completionHandler:nil];
}
-- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction animated:(BOOL)animated
+- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
{
- MGLLogDebug(@"Setting visibleCoordinateBounds: %@ edgePadding: %@ direction: %f animated: %@",
+ MGLLogDebug(@"Setting visibleCoordinateBounds: %@ edgePadding: %@ animated: %@",
MGLStringFromCoordinateBounds(bounds),
NSStringFromUIEdgeInsets(insets),
- direction,
MGLStringFromBOOL(animated));
CLLocationCoordinate2D coordinates[] = {
{bounds.ne.latitude, bounds.sw.longitude},
@@ -3363,8 +3357,10 @@ public:
[self setVisibleCoordinates:coordinates
count:sizeof(coordinates) / sizeof(coordinates[0])
edgePadding:insets
- direction:direction
- animated:animated];
+ 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
@@ -3373,17 +3369,7 @@ public:
count,
NSStringFromUIEdgeInsets(insets),
MGLStringFromBOOL(animated));
- [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:self.direction animated:animated];
-}
-
-- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction animated:(BOOL)animated
-{
- MGLLogDebug(@"Setting: %lu coordinates edgePadding: %@ direction: %f animated: %@",
- count,
- NSStringFromUIEdgeInsets(insets),
- direction,
- MGLStringFromBOOL(animated));
- [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil];
+ [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 {
@@ -4699,17 +4685,22 @@ public:
- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated
{
- [self selectAnnotation:annotation moveIntoView:animated animateSelection:animated];
+ [self selectAnnotation:annotation animated:animated completionHandler:nil];
}
-- (void)selectAnnotation:(id <MGLAnnotation>)annotation moveIntoView:(BOOL)moveIntoView animateSelection:(BOOL)animateSelection
+- (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];
+ [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
+- (void)selectAnnotation:(id <MGLAnnotation>)annotation moveIntoView:(BOOL)moveIntoView animateSelection:(BOOL)animateSelection calloutPositioningRect:(CGRect)calloutPositioningRect completionHandler:(nullable void (^)(void))completion
{
if ( ! annotation) return;
@@ -4919,7 +4910,11 @@ public:
{
CGPoint center = CGPointMake(CGRectGetMidX(constrainedRect), CGRectGetMidY(constrainedRect));
CLLocationCoordinate2D centerCoord = [self convertPoint:center toCoordinateFromView:self];
- [self setCenterCoordinate:centerCoord animated:animateSelection];
+ [self setCenterCoordinate:centerCoord zoomLevel:self.zoomLevel direction:self.direction animated:animateSelection completionHandler:completion];
+ }
+ else if (completion)
+ {
+ completion();
}
}
@@ -5117,8 +5112,19 @@ public:
- (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 || ! annotations.count) return;
+ if ( ! annotations.count)
+ {
+ if (completion) {
+ completion();
+ }
+ return;
+ }
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
@@ -5136,7 +5142,8 @@ public:
[self setVisibleCoordinateBounds:MGLCoordinateBoundsFromLatLngBounds(bounds)
edgePadding:insets
- animated:animated];
+ animated:animated
+ completionHandler:completion];
}
@@ -5345,8 +5352,20 @@ public:
- (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) return;
+ if (mode == _userTrackingMode)
+ {
+ if (completion)
+ {
+ completion();
+ }
+ return;
+ }
MGLUserTrackingMode oldMode = _userTrackingMode;
[self willChangeValueForKey:@"userTrackingMode"];
@@ -5391,13 +5410,14 @@ public:
}
}
- if (_userTrackingMode != MGLUserTrackingModeNone)
+ CLLocation *location;
+ if (_userTrackingMode != MGLUserTrackingModeNone && (location = self.userLocation.location) && self.userLocationAnnotationView)
{
- CLLocation *location = self.userLocation.location;
- if (location && self.userLocationAnnotationView)
- {
- [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated];
- }
+ [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated completionHandler:completion];
+ }
+ else if (completion)
+ {
+ completion();
}
[self validateUserHeadingUpdating];
@@ -5421,7 +5441,7 @@ public:
CLLocation *location = self.userLocation.location;
if (location)
{
- [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated];
+ [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated completionHandler:nil];
}
}
}
@@ -5434,7 +5454,13 @@ public:
- (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)
{
@@ -5443,13 +5469,17 @@ public:
{
self.userTrackingState = MGLUserTrackingStatePossible;
- CLLocation *location = self.userLocation.location;
- if (location)
+ if (CLLocation *location = self.userLocation.location)
{
- [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated];
+ isSynchronous = NO;
+ [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated completionHandler:completion];
}
}
}
+ if (isSynchronous && completion)
+ {
+ completion();
+ }
}
- (void)setShowsUserHeadingIndicator:(BOOL)showsUserHeadingIndicator
@@ -5481,10 +5511,10 @@ public:
- (void)locationManager:(id<MGLLocationManager>)manager didUpdateLocations:(NSArray *)locations
{
- [self locationManager:manager didUpdateLocations:locations animated:YES];
+ [self locationManager:manager didUpdateLocations:locations animated:YES completionHandler:nil];
}
-- (void)locationManager:(__unused id<MGLLocationManager>)manager didUpdateLocations:(NSArray *)locations animated:(BOOL)animated
+- (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;
@@ -5506,7 +5536,7 @@ public:
}
}
- [self didUpdateLocationWithUserTrackingAnimated:animated];
+ [self didUpdateLocationWithUserTrackingAnimated:animated completionHandler:completion];
NSTimeInterval duration = MGLAnimationDuration;
if (oldLocation && ! CGPointEqualToPoint(self.userLocationAnnotationView.center, CGPointZero))
@@ -5523,13 +5553,17 @@ public:
}
}
-- (void)didUpdateLocationWithUserTrackingAnimated:(BOOL)animated
+- (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;
}
@@ -5540,6 +5574,10 @@ public:
if (std::abs(currentPoint.x - correctPoint.x) <= 1.0 && std::abs(currentPoint.y - correctPoint.y) <= 1.0
&& self.userTrackingMode != MGLUserTrackingModeFollowWithCourse)
{
+ if (completion)
+ {
+ completion();
+ }
return;
}
@@ -5549,25 +5587,25 @@ public:
if (self.userTrackingState != MGLUserTrackingStateBegan)
{
// Keep both the user and the destination in view.
- [self didUpdateLocationWithTargetAnimated:animated];
+ [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];
+ [self didUpdateLocationSignificantlyAnimated:animated completionHandler:completion];
}
else if (self.userTrackingState == MGLUserTrackingStateChanged)
{
// Subsequent updates get a more subtle animation.
- [self didUpdateLocationIncrementallyAnimated:animated];
+ [self didUpdateLocationIncrementallyAnimated:animated completionHandler:completion];
}
[self unrotateIfNeededAnimated:YES];
}
/// Changes the viewport based on an incremental location update.
-- (void)didUpdateLocationIncrementallyAnimated:(BOOL)animated
+- (void)didUpdateLocationIncrementallyAnimated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
{
[self _setCenterCoordinate:self.userLocation.location.coordinate
edgePadding:self.edgePaddingForFollowing
@@ -5575,12 +5613,12 @@ public:
direction:self.directionByFollowingWithCourse
duration:animated ? MGLUserLocationAnimationDuration : 0
animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]
- completionHandler:nil];
+ completionHandler:completion];
}
/// Changes the viewport based on a significant location update, such as the
/// first location update.
-- (void)didUpdateLocationSignificantlyAnimated:(BOOL)animated
+- (void)didUpdateLocationSignificantlyAnimated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
{
if (_distanceFromOldUserLocation >= MGLDistanceThresholdForCameraPause) {
@@ -5612,25 +5650,33 @@ public:
{
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
+- (void)didUpdateLocationWithTargetAnimated:(BOOL)animated completionHandler:(nullable void (^)(void))completion
{
BOOL firstUpdate = self.userTrackingState == MGLUserTrackingStatePossible;
- void (^completion)(void);
+ void (^animationCompletion)(void);
if (animated && firstUpdate)
{
self.userTrackingState = MGLUserTrackingStateBegan;
__weak MGLMapView *weakSelf = self;
- completion = ^{
+ animationCompletion = ^{
MGLMapView *strongSelf = weakSelf;
if (strongSelf.userTrackingState == MGLUserTrackingStateBegan)
{
strongSelf.userTrackingState = MGLUserTrackingStateChanged;
}
+ if (completion)
+ {
+ completion();
+ }
};
}
@@ -5654,7 +5700,7 @@ public:
direction:self.directionByFollowingWithCourse
duration:animated ? MGLUserLocationAnimationDuration : 0
animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]
- completionHandler:completion];
+ completionHandler:animationCompletion];
}
/// Returns the edge padding to apply when moving the map to a tracked location.
diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md
index 3eb34c5ac1..48a68d2e26 100644
--- a/platform/macos/CHANGELOG.md
+++ b/platform/macos/CHANGELOG.md
@@ -13,6 +13,13 @@
* The `-[MGLMapView setCamera:withDuration:animationTimingFunction:edgePadding:completionHandler:]` method now adds the current value of the `MGLMapView.contentInsets` property to the `edgePadding` parameter. ([#14813](https://github.com/mapbox/mapbox-gl-native/pull/14813))
+### Other changes
+
+* Added variants of multiple animated `MGLMapView` methods that accept completion handlers ([#14381](https://github.com/mapbox/mapbox-gl-native/pull/14381)):
+ * `-[MGLMapView setVisibleCoordinateBounds:edgePadding:animated:completionHandler:]`
+ * `-[MGLMapView setContentInsets:animated:completionHandler:]`
+ * `-[MGLMapView showAnnotations:edgePadding:animated:completionHandler:]`
+
## 0.14.0 - May 22, 2018
### Styles and rendering
diff --git a/platform/macos/src/MGLMapView.h b/platform/macos/src/MGLMapView.h
index 35db5257d9..fbe57524b8 100644
--- a/platform/macos/src/MGLMapView.h
+++ b/platform/macos/src/MGLMapView.h
@@ -434,8 +434,16 @@ MGL_EXPORT IB_DESIGNABLE
- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds animated:(BOOL)animated;
/**
- Changes the receiver’s viewport to fit the given coordinate bounds and
- optionally some additional padding on each side.
+ 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
@@ -446,6 +454,24 @@ MGL_EXPORT IB_DESIGNABLE
- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated;
/**
+ 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:(NSEdgeInsets)insets animated:(BOOL)animated 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
@@ -465,6 +491,9 @@ MGL_EXPORT IB_DESIGNABLE
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
@@ -475,6 +504,23 @@ MGL_EXPORT IB_DESIGNABLE
- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated;
/**
+ 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:(NSEdgeInsets)insets animated:(BOOL)animated 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.
@@ -486,8 +532,8 @@ MGL_EXPORT IB_DESIGNABLE
- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds;
/**
- Returns the camera that best fits the given coordinate bounds, optionally with
- some additional padding on each side.
+ 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
@@ -500,8 +546,9 @@ MGL_EXPORT IB_DESIGNABLE
- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets;
/**
- Returns the camera that best fits the given coordinate bounds, with the specified camera,
- optionally with some additional padding on each side.
+ 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.
@@ -516,8 +563,8 @@ MGL_EXPORT IB_DESIGNABLE
- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets;
/**
- Returns the camera that best fits the given shape, with the specified camera,
- optionally with some additional padding on each side.
+ 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.
@@ -525,22 +572,23 @@ MGL_EXPORT IB_DESIGNABLE
@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.
+ (close to the ground) as possible while still including the entire shape.
+ The initial camera's pitch and direction will be honored.
*/
- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingShape:(MGLShape *)shape edgePadding:(NSEdgeInsets)insets;
/**
- Returns the camera that best fits the given shape, with the specified direction,
- optionally with some additional padding on each side.
+ 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 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.
+ @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.
*/
- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(NSEdgeInsets)insets;
@@ -589,6 +637,9 @@ MGL_EXPORT IB_DESIGNABLE
When the value of the `automaticallyAdjustsContentInsets` property is `YES`,
the value of this property may be overridden at any time.
+
+ To specify a completion handler to execute after the animation finishes, use
+ the `-setContentInsets:animated:completionHandler:` method.
@param contentInsets The new values to inset the content by.
@param animated Specify `YES` if you want the map view to animate the change to
@@ -597,6 +648,28 @@ MGL_EXPORT IB_DESIGNABLE
*/
- (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated;
+/**
+ 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 `NSEdgeInsetsZero`, 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 value of the `automaticallyAdjustsContentInsets` property is `YES`,
+ the value of this property may be overridden at any time.
+
+ @param contentInsets 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 insets or `NO` if you want the map to inset the content
+ immediately.
+ @param completion The block executed after the animation finishes.
+ */
+- (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion;
+
#pragma mark Configuring How the User Interacts with the Map
/**
@@ -784,9 +857,6 @@ MGL_EXPORT IB_DESIGNABLE
annotation is *not* centered within the viewport.
@param annotation The annotation object to select.
-
- @note In versions prior to `4.0.0` selecting an offscreen annotation did not
- change the camera.
*/
- (void)selectAnnotation:(id <MGLAnnotation>)annotation;
diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm
index 0a1ec5516a..ad3d1eee51 100644
--- a/platform/macos/src/MGLMapView.mm
+++ b/platform/macos/src/MGLMapView.mm
@@ -1010,13 +1010,26 @@ public:
}
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate animated:(BOOL)animated {
+ [self setCenterCoordinate:centerCoordinate animated:animated completionHandler:nil];
+}
+
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion {
MGLLogDebug(@"Setting centerCoordinate: %@ animated: %@", MGLStringFromCLLocationCoordinate2D(centerCoordinate), MGLStringFromBOOL(animated));
+ mbgl::AnimationOptions animationOptions = MGLDurationFromTimeInterval(animated ? MGLAnimationDuration : 0);
+ animationOptions.transitionFinishFn = ^() {
+ [self didChangeValueForKey:@"centerCoordinate"];
+ if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion();
+ });
+ }
+ };
+
[self willChangeValueForKey:@"centerCoordinate"];
_mbglMap->easeTo(mbgl::CameraOptions()
.withCenter(MGLLatLngFromLocationCoordinate2D(centerCoordinate))
.withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets)),
- MGLDurationFromTimeInterval(animated ? MGLAnimationDuration : 0));
- [self didChangeValueForKey:@"centerCoordinate"];
+ animationOptions);
}
- (void)offsetCenterCoordinateBy:(NSPoint)delta animated:(BOOL)animated {
@@ -1296,6 +1309,10 @@ public:
}
- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated {
+ [self setVisibleCoordinateBounds:bounds edgePadding:insets animated:animated completionHandler:nil];
+}
+
+- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion {
_mbglMap->cancelTransitions();
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
@@ -1308,12 +1325,18 @@ public:
MGLMapCamera *camera = [self cameraForCameraOptions:cameraOptions];
if ([self.camera isEqualToMapCamera:camera]) {
+ completion();
return;
}
[self willChangeValueForKey:@"visibleCoordinateBounds"];
animationOptions.transitionFinishFn = ^() {
[self didChangeValueForKey:@"visibleCoordinateBounds"];
+ if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion();
+ });
+ }
};
_mbglMap->easeTo(cameraOptions, animationOptions);
}
@@ -1413,8 +1436,14 @@ public:
}
- (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated {
-
+ [self setContentInsets:contentInsets animated:animated completionHandler:nil];
+}
+
+- (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion {
if (NSEdgeInsetsEqual(contentInsets, self.contentInsets)) {
+ if (completion) {
+ completion();
+ }
return;
}
MGLLogDebug(@"Setting contentInset: %@ animated:", MGLStringFromNSEdgeInsets(contentInsets), MGLStringFromBOOL(animated));
@@ -1423,7 +1452,7 @@ public:
// content insets.
CLLocationCoordinate2D oldCenter = self.centerCoordinate;
_contentInsets = contentInsets;
- [self setCenterCoordinate:oldCenter animated:animated];
+ [self setCenterCoordinate:oldCenter animated:animated completionHandler:completion];
}
#pragma mark Mouse events and gestures
@@ -2465,25 +2494,31 @@ public:
}
- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated {
- if ( ! annotations || ! annotations.count) return;
+ [self showAnnotations:annotations edgePadding:insets animated:animated completionHandler:nil];
+}
+
+- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion {
+ if (!annotations.count) {
+ if (completion) {
+ completion();
+ }
+ return;
+ }
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
- for (id <MGLAnnotation> annotation in annotations)
- {
- if ([annotation conformsToProtocol:@protocol(MGLOverlay)])
- {
+ for (id <MGLAnnotation> annotation in annotations) {
+ if ([annotation conformsToProtocol:@protocol(MGLOverlay)]) {
bounds.extend(MGLLatLngBoundsFromCoordinateBounds(((id <MGLOverlay>)annotation).overlayBounds));
- }
- else
- {
+ } else {
bounds.extend(MGLLatLngFromLocationCoordinate2D(annotation.coordinate));
}
}
[self setVisibleCoordinateBounds:MGLCoordinateBoundsFromLatLngBounds(bounds)
edgePadding:insets
- animated:animated];
+ animated:animated
+ completionHandler:completion];
}
/// Returns a popover detailing the annotation.