Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[ios] Affix user dot on screen in user tracking mode
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
1ec5 committed Jan 19, 2016
1 parent 8dfba0e commit 1fa73ad
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,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))
Expand Down
92 changes: 75 additions & 17 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ @interface MGLMapView () <UIGestureRecognizerDelegate,
/// 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;
Expand Down Expand Up @@ -816,6 +818,12 @@ - (void)adjustContentInset
}
}

/// Returns the frame of inset content within the map view.
- (CGRect)contentFrame
{
return UIEdgeInsetsInsetRect(self.bounds, self.contentInset);
}

#pragma mark - Life Cycle -

- (void)updateFromDisplayLink
Expand Down Expand Up @@ -1651,28 +1659,27 @@ - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(
{
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)
{
Expand Down Expand Up @@ -2938,6 +2945,7 @@ - (void)setUserTrackingMode:(MGLUserTrackingMode)mode animated:(BOOL)animated
}

_userTrackingMode = mode;
self.hasBegunTrackingUserLocation = NO;

switch (_userTrackingMode)
{
Expand Down Expand Up @@ -3024,17 +3032,43 @@ - (void)locationManager:(__unused CLLocationManager *)manager didUpdateLocations
{
// 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
{
Expand All @@ -3050,12 +3084,12 @@ - (void)locationManager:(__unused CLLocationManager *)manager didUpdateLocations

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 ||
Expand All @@ -3065,9 +3099,9 @@ - (void)locationManager:(__unused CLLocationManager *)manager didUpdateLocations
{
// assumes we won't disrupt tracking mode
[self setVisibleCoordinateBounds:MGLCoordinateBoundsMake(desiredSouthWest, desiredNorthEast) edgePadding:UIEdgeInsetsZero direction:course animated:YES];
[self unrotateIfNeededAnimated:YES];
}
}
[self unrotateIfNeededAnimated:YES];
}
}

Expand Down Expand Up @@ -3335,7 +3369,15 @@ - (void)updateUserLocationAnnotationView

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))
Expand All @@ -3352,6 +3394,22 @@ - (void)updateUserLocationAnnotationView
}
}

/// 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.);
Expand Down

0 comments on commit 1fa73ad

Please sign in to comment.