diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d87872139..ace53f9e7b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Known issues: ## iOS master +- `MGLMapView` methods that alter the viewport now accept optional completion handlers. ([#3090](https://github.com/mapbox/mapbox-gl-native/pull/3090)) - Fixed an issue preventing the compass from responding to taps after the compass is moved programmatically. ([#3117](https://github.com/mapbox/mapbox-gl-native/pull/3117)) ## iOS 3.0.0 diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index 83ef395cf9e..edc3ca53f3b 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -134,6 +134,14 @@ IB_DESIGNABLE * @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. */ - (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. +* @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. +* @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. */ +- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion; + /** 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. */ @@ -157,6 +165,16 @@ IB_DESIGNABLE * @param animated Specify `YES` to animate the change by smoothly scrolling and zooming or `NO` to immediately display the given bounds. */ - (void)setVisibleCoordinates:(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. +* @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. +* @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:(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. @@ -193,6 +211,13 @@ IB_DESIGNABLE * @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. */ - (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. +* @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; + #pragma mark - Converting Map Coordinates /** @name Converting Map Coordinates */ diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp index a16c1d4dc29..d787a39e111 100644 --- a/include/mbgl/map/camera.hpp +++ b/include/mbgl/map/camera.hpp @@ -7,6 +7,8 @@ #include #include +#include + namespace mbgl { struct CameraOptions { @@ -16,6 +18,8 @@ struct CameraOptions { mapbox::util::optional pitch; mapbox::util::optional duration; mapbox::util::optional easing; + std::function transitionFrameFn; + std::function transitionFinishFn; }; } diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 452b3075753..c923adb1601 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1565,14 +1565,18 @@ - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:( [self setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:self.direction animated:animated]; } -- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated +- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated { + [self setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:direction animated:animated completionHandler:NULL]; +} + +- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion { self.userTrackingMode = MGLUserTrackingModeNone; - [self _setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:direction animated:animated]; + [self _setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:direction animated:animated completionHandler:completion]; } -- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated +- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion { _mbglMap->cancelTransitions(); @@ -1589,6 +1593,17 @@ - (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel: options.duration = durationInSeconds(duration); options.easing = MGLUnitBezierForMediaTimingFunction(nil); } + if (completion) + { + options.transitionFinishFn = [completion]() { + // 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_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + completion(); + }); + }; + } _mbglMap->easeTo(options); [self unrotateIfNeededAnimated:animated]; @@ -1679,7 +1694,11 @@ - (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUIn [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil]; } -- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(CAMediaTimingFunction *)function +- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function { + [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:duration animationTimingFunction:function completionHandler:NULL]; +} + +- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion { _mbglMap->cancelTransitions(); @@ -1702,6 +1721,14 @@ - (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUIn options.duration = durationInSeconds(duration); options.easing = MGLUnitBezierForMediaTimingFunction(function); } + if (completion) + { + options.transitionFinishFn = [completion]() { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + completion(); + }); + }; + } _mbglMap->easeTo(options); [self didChangeValueForKey:@"visibleCoordinateBounds"]; @@ -1815,7 +1842,12 @@ - (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated [self setCamera:camera withDuration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil]; } -- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(CAMediaTimingFunction *)function +- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function +{ + [self setCamera:camera withDuration:duration animationTimingFunction:function completionHandler:NULL]; +} + +- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion { _mbglMap->cancelTransitions(); @@ -1880,6 +1912,14 @@ - (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration a options.duration = durationInSeconds(duration); options.easing = MGLUnitBezierForMediaTimingFunction(function); } + if (completion) + { + options.transitionFinishFn = [completion]() { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + completion(); + }); + }; + } _mbglMap->easeTo(options); } @@ -2675,7 +2715,7 @@ - (void)locationManager:(__unused CLLocationManager *)manager didUpdateLocations { // at sufficient detail, just re-center the map; don't zoom // - [self _setCenterCoordinate:self.userLocation.location.coordinate zoomLevel:self.zoomLevel direction:course animated:YES]; + [self _setCenterCoordinate:self.userLocation.location.coordinate zoomLevel:self.zoomLevel direction:course animated:YES completionHandler:NULL]; } else { diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 55a492c493d..7f0363c709c 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -262,6 +262,9 @@ void Transform::_easeTo(const CameraOptions& options, double new_scale, double n state.angle = angle; state.pitch = pitch; + if (options.transitionFinishFn) { + options.transitionFinishFn(); + } view.notifyMapChange(MapChangeRegionDidChange); } else { view.notifyMapChange(MapChangeRegionWillChangeAnimated); @@ -291,6 +294,9 @@ void Transform::_easeTo(const CameraOptions& options, double new_scale, double n state.pitch = util::interpolate(startP, pitch, t); // At t = 1.0, a DidChangeAnimated notification should be sent from finish(). if (t < 1.0) { + if (options.transitionFrameFn) { + options.transitionFrameFn(t); + } view.notifyMapChange(MapChangeRegionIsChanging); } return update; @@ -299,6 +305,9 @@ void Transform::_easeTo(const CameraOptions& options, double new_scale, double n state.panning = false; state.scaling = false; state.rotating = false; + if (options.transitionFinishFn) { + options.transitionFinishFn(); + } view.notifyMapChange(MapChangeRegionDidChangeAnimated); }, *easeOptions.duration); }