summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--platform/ios/src/MGLMapView.mm92
2 files changed, 76 insertions, 17 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d684e7e8b2..9653d34886 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -47,6 +47,7 @@ Known issues:
- A new method on MGLMapView, `-flyToCamera:withDuration:completionHandler:`, lets you transition between viewpoints along an arc as if by aircraft. ([#3171](https://github.com/mapbox/mapbox-gl-native/pull/3171), [#3301](https://github.com/mapbox/mapbox-gl-native/pull/3301))
- MGLMapCamera’s `altitude` values now match those of MKMapCamera. ([#3362](https://github.com/mapbox/mapbox-gl-native/pull/3362))
- MGLMapView properties like `centerCoordinate` and `camera` now offset the center to account for any translucent top or bottom bar. As a result, when user tracking is enabled and the map view is an immediate child of a view controller, the user dot is centered in the unobscured portion of the map view. To override this offset, modify the `contentInset` property; you may also need to set the containing view controller’s `automaticallyAdjustsScrollViewInsets` property to `NO`. ([#3583](https://github.com/mapbox/mapbox-gl-native/pull/3583))
+- In user tracking mode, the user dot stays in a fixed position within MGLMapView while the map pans smoothly. In course tracking mode, the user puck is shifted towards the bottom of the view. ([#3589](https://github.com/mapbox/mapbox-gl-native/pull/3589))
- The user dot’s callout view is now centered above the user dot. It was previously offset slightly to the left. ([#3261](https://github.com/mapbox/mapbox-gl-native/pull/3261))
- Fixed an issue with small map views not properly fitting annotations within bounds. (#[3407](https://github.com/mapbox/mapbox-gl-native/pull/3407))
- When the user rotates the map to within 7° of true north, the map view now snaps to true north. ([#3403](https://github.com/mapbox/mapbox-gl-native/pull/3403))
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index bc1419ef63..046ebfd433 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -143,6 +143,8 @@ public:
/// Currently shown popover representing the selected annotation.
@property (nonatomic) UIView<MGLCalloutView> *calloutViewForSelectedAnnotation;
@property (nonatomic) MGLUserLocationAnnotationView *userLocationAnnotationView;
+/// True if the map view has completed its move to the first reported user location in user tracking mode.
+@property (nonatomic) BOOL hasBegunTrackingUserLocation;
@property (nonatomic) CLLocationManager *locationManager;
@property (nonatomic) CGFloat scale;
@property (nonatomic) CGFloat angle;
@@ -816,6 +818,12 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
}
}
+/// Returns the frame of inset content within the map view.
+- (CGRect)contentFrame
+{
+ return UIEdgeInsetsInsetRect(self.bounds, self.contentInset);
+}
+
#pragma mark - Life Cycle -
- (void)updateFromDisplayLink
@@ -1651,28 +1659,27 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
{
self.userTrackingMode = MGLUserTrackingModeNone;
- [self _setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:direction animated:animated completionHandler:completion];
+ [self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:zoomLevel direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion];
}
-- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated completionHandler:(nullable void (^)(void))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
{
_mbglMap->cancelTransitions();
mbgl::CameraOptions cameraOptions;
cameraOptions.center = MGLLatLngFromLocationCoordinate2D(centerCoordinate);
- cameraOptions.padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
+ cameraOptions.padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
cameraOptions.zoom = zoomLevel;
if (direction >= 0)
{
cameraOptions.angle = MGLRadiansFromDegrees(-direction);
}
- NSTimeInterval duration = animated ? MGLAnimationDuration : 0;
mbgl::AnimationOptions animationOptions;
- if (animated)
+ if (duration)
{
animationOptions.duration = MGLDurationInSeconds(duration);
- animationOptions.easing = MGLUnitBezierForMediaTimingFunction(nil);
+ animationOptions.easing = MGLUnitBezierForMediaTimingFunction(function);
}
if (completion)
{
@@ -2935,6 +2942,7 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
}
_userTrackingMode = mode;
+ self.hasBegunTrackingUserLocation = NO;
switch (_userTrackingMode)
{
@@ -3021,17 +3029,43 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
{
// center on user location unless we're already centered there (or very close)
//
- CGPoint mapCenterPoint = [self convertCoordinate:self.centerCoordinate toPointToView:self];
- CGPoint userLocationPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self];
+ CGPoint correctPoint = self.userLocationAnnotationViewCenter;
+ CGPoint currentPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self];
- if (std::abs(userLocationPoint.x - mapCenterPoint.x) > 1.0 || std::abs(userLocationPoint.y - mapCenterPoint.y) > 1.0)
+ if (std::abs(currentPoint.x - correctPoint.x) > 1.0 || std::abs(currentPoint.y - correctPoint.y) > 1.0)
{
if (round(self.zoomLevel) >= 10)
{
// at sufficient detail, just re-center the map; don't zoom
//
- [self _setCenterCoordinate:self.userLocation.location.coordinate zoomLevel:self.zoomLevel direction:course animated:YES completionHandler:NULL];
- [self unrotateIfNeededAnimated:YES];
+ if (self.hasBegunTrackingUserLocation)
+ {
+ UIEdgeInsets insets = UIEdgeInsetsMake(correctPoint.y, correctPoint.x,
+ CGRectGetHeight(self.bounds) - correctPoint.y,
+ CGRectGetWidth(self.bounds) - correctPoint.x);
+ CAMediaTimingFunction *linearFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
+ [self _setCenterCoordinate:self.userLocation.location.coordinate
+ edgePadding:insets
+ zoomLevel:self.zoomLevel
+ direction:course
+ duration:1
+ animationTimingFunction:linearFunction
+ completionHandler:NULL];
+ }
+ else
+ {
+ __weak MGLMapView *weakSelf = self;
+ [self _setCenterCoordinate:self.userLocation.location.coordinate
+ edgePadding:self.contentInset
+ zoomLevel:self.zoomLevel
+ direction:course
+ duration:MGLAnimationDuration
+ animationTimingFunction:nil
+ completionHandler:^{
+ MGLMapView *strongSelf = weakSelf;
+ strongSelf.hasBegunTrackingUserLocation = YES;
+ }];
+ }
}
else
{
@@ -3047,12 +3081,12 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
CGFloat pixelRadius = fminf(self.bounds.size.width, self.bounds.size.height) / 2;
- CLLocationCoordinate2D actualSouthWest = [self convertPoint:CGPointMake(userLocationPoint.x - pixelRadius,
- userLocationPoint.y - pixelRadius)
+ CLLocationCoordinate2D actualSouthWest = [self convertPoint:CGPointMake(currentPoint.x - pixelRadius,
+ currentPoint.y - pixelRadius)
toCoordinateFromView:self];
- CLLocationCoordinate2D actualNorthEast = [self convertPoint:CGPointMake(userLocationPoint.x + pixelRadius,
- userLocationPoint.y + pixelRadius)
+ CLLocationCoordinate2D actualNorthEast = [self convertPoint:CGPointMake(currentPoint.x + pixelRadius,
+ currentPoint.y + pixelRadius)
toCoordinateFromView:self];
if (desiredNorthEast.latitude != actualNorthEast.latitude ||
@@ -3062,9 +3096,9 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
{
// assumes we won't disrupt tracking mode
[self setVisibleCoordinateBounds:MGLCoordinateBoundsMake(desiredSouthWest, desiredNorthEast) edgePadding:UIEdgeInsetsZero direction:course animated:YES];
- [self unrotateIfNeededAnimated:YES];
}
}
+ [self unrotateIfNeededAnimated:YES];
}
}
@@ -3332,7 +3366,15 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
if ( ! self.userLocationAnnotationView.superview) [self.glView addSubview:self.userLocationAnnotationView];
- CGPoint userPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self];
+ CGPoint userPoint;
+ if (self.userTrackingMode != MGLUserTrackingModeNone && self.hasBegunTrackingUserLocation)
+ {
+ userPoint = self.userLocationAnnotationViewCenter;
+ }
+ else
+ {
+ userPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self];
+ }
if (CGRectContainsPoint(CGRectInset(self.bounds, -MGLAnnotationUpdateViewportOutset.width,
-MGLAnnotationUpdateViewportOutset.height), userPoint))
@@ -3349,6 +3391,22 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
}
}
+/// Intended center point of the user location annotation view.
+- (CGPoint)userLocationAnnotationViewCenter
+{
+ CGRect contentFrame = self.contentFrame;
+ CGPoint center = CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame));
+
+ // When tracking course, it’s more important to see the road ahead, so
+ // weight the user dot down towards the bottom.
+ if (self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
+ {
+ center.y = CGRectGetHeight(contentFrame) - CGRectGetHeight(self.userLocationAnnotationView.frame);
+ }
+
+ return center;
+}
+
- (void)updateCompass
{
CLLocationDirection degrees = mbgl::util::wrap(-self.direction, 0., 360.);