summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2016-01-17 12:23:58 -0800
committerMinh Nguyễn <mxn@1ec5.org>2016-01-20 11:02:31 -0800
commit453000af3b17a8776ff0e78593dd5682c8cdab8d (patch)
tree31b7ca1e831021a49079badf2d3d0ccef4d88c5e
parenta11f7d6ef3a8ff00371346412ca9b3b72f17ce15 (diff)
downloadqtlocation-mapboxgl-453000af3b17a8776ff0e78593dd5682c8cdab8d.tar.gz
[ios] Affix user dot on screen in user tracking mode
In user tracking mode, keep the user dot in a fixed location on screen, instead easing the map view so that the fixed location corresponds to the new user location. There is one exception: MGLMapView reacts to the first location update the same way as before, so that it appears to move to where the user dot has been all along. In course tracking mode, weight the user dot down towards the bottom of the view, since it’s more important to see the road ahead than the road behind. Fixes #2600, and #1041 to some extent.
-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.);