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

Commit

Permalink
[ios] Animate user dot between user location updates
Browse files Browse the repository at this point in the history
Use UIView animation to explicitly animate the user dot between user location updates. There is a tricky special case, which is that the callout must point to the annotation view’s implicit frame but must quickly rendezvous with the explicit frame.

Fixes #1041.
  • Loading branch information
1ec5 committed Apr 18, 2016
1 parent f936295 commit 52abcb5
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 20 deletions.
1 change: 1 addition & 0 deletions platform/ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Mapbox welcomes participation and contributions from everyone. If you’d like

- Applications linking against the SDK static framework no longer need to add `-ObjC` to the Other Linker Flags (`OTHER_LDFLAGS`) build setting. If you previously added this flag solely for this SDK, removing the flag may potentially reduce the overall size of your application. ([#4641](https://github.com/mapbox/mapbox-gl-native/pull/4641))
- Removed the `armv7s` slice from the SDK to reduce its size. iPhone 5 and iPhone 5c automatically use the `armv7` slice instead. ([#4641](https://github.com/mapbox/mapbox-gl-native/pull/4641))
- The user dot now moves smoothly between user location updates while user location tracking is disabled. ([#1582](https://github.com/mapbox/mapbox-gl-native/pull/1582))
- User location heading updates now resume properly when an app becomes active again. ([#4674](https://github.com/mapbox/mapbox-gl-native/pull/4674))
- A more specific user agent string is now sent with style and tile requests. ([#4012](https://github.com/mapbox/mapbox-gl-native/pull/4012))
- Removed unused SVG files from the SDK’s resource bundle. ([#4641](https://github.com/mapbox/mapbox-gl-native/pull/4641))
Expand Down
5 changes: 5 additions & 0 deletions platform/ios/app/MBXCustomCalloutView.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ - (instancetype)initWithFrame:(CGRect)frame

- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated
{
if ([self.delegate respondsToSelector:@selector(calloutViewWillAppear:)])
{
[self.delegate performSelector:@selector(calloutViewWillAppear:) withObject:self];
}

[view addSubview:self];
// prepare title label
if ([self.representedObject respondsToSelector:@selector(title)])
Expand Down
5 changes: 5 additions & 0 deletions platform/ios/include/MGLCalloutView.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)calloutViewTapped:(UIView<MGLCalloutView> *)calloutView;

/**
Called before the callout view appears on screen, or before the appearance animation will start.
*/
- (void)calloutViewWillAppear:(UIView<MGLCalloutView> *)calloutView;

@end

NS_ASSUME_NONNULL_END
100 changes: 80 additions & 20 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ @implementation MGLMapView
/// Size of the rectangle formed by unioning the maximum slop area around every annotation image.
CGSize _unionedAnnotationImageSize;
std::vector<MGLAnnotationTag> _annotationsNearbyLastTap;
CGPoint _initialImplicitCalloutViewOffset;
NSDate *_userLocationAnimationCompletionDate;

BOOL _isWaitingForRedundantReachableNotification;
BOOL _isTargetingInterfaceBuilder;
Expand Down Expand Up @@ -1268,21 +1270,14 @@ - (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap

CGPoint tapPoint = [singleTap locationInView:self];

if (self.userLocationVisible)
if (self.userLocationVisible
&& [self.userLocationAnnotationView.layer.presentationLayer hitTest:tapPoint])
{
// Assume that the user is fat-fingering an annotation.
CGRect hitRect = CGRectInset({ tapPoint, CGSizeZero },
-MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);

if (CGRectIntersectsRect(hitRect, self.userLocationAnnotationView.frame))
if ( ! _userLocationAnnotationIsSelected)
{
if ( ! _userLocationAnnotationIsSelected)
{
[self selectAnnotation:self.userLocation animated:YES];
}
return;
[self selectAnnotation:self.userLocation animated:YES];
}
return;
}

MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:tapPoint persistingResults:YES];
Expand Down Expand Up @@ -2830,9 +2825,12 @@ - (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated

if (_userLocationAnnotationIsSelected)
{
positioningRect = CGRectInset(self.userLocationAnnotationView.frame,
-MGLAnnotationImagePaddingForCallout,
-MGLAnnotationImagePaddingForCallout);
positioningRect = [self.userLocationAnnotationView.layer.presentationLayer frame];

CGRect implicitAnnotationFrame = [self.userLocationAnnotationView.layer.presentationLayer frame];
CGRect explicitAnnotationFrame = self.userLocationAnnotationView.frame;
_initialImplicitCalloutViewOffset = CGPointMake(CGRectGetMinX(explicitAnnotationFrame) - CGRectGetMinX(implicitAnnotationFrame),
CGRectGetMinY(explicitAnnotationFrame) - CGRectGetMinY(implicitAnnotationFrame));
}

// consult delegate for left and/or right accessory views
Expand Down Expand Up @@ -2940,7 +2938,7 @@ - (void)deselectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animate
{
if ( ! annotation) return;

if ([self.selectedAnnotation isEqual:annotation])
if (self.selectedAnnotation == annotation)
{
// dismiss popup
[self.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated];
Expand All @@ -2957,6 +2955,35 @@ - (void)deselectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animate
}
}

- (void)calloutViewWillAppear:(UIView <MGLCalloutView> *)calloutView
{
if (_userLocationAnnotationIsSelected ||
CGPointEqualToPoint(_initialImplicitCalloutViewOffset, CGPointZero))
{
return;
}

// The user location callout view initially points to the user location
// annotation’s implicit (visual) frame, which is offset from the
// annotation’s explicit frame. Now the callout view needs to rendezvous
// with the explicit frame. Then,
// -updateUserLocationAnnotationViewAnimatedWithDuration: will take over the
// next time an updated location arrives.
[UIView animateWithDuration:_userLocationAnimationCompletionDate.timeIntervalSinceNow
delay:0
options:(UIViewAnimationOptionCurveLinear |
UIViewAnimationOptionAllowUserInteraction |
UIViewAnimationOptionBeginFromCurrentState)
animations:^
{
calloutView.frame = CGRectOffset(calloutView.frame,
_initialImplicitCalloutViewOffset.x,
_initialImplicitCalloutViewOffset.y);
_initialImplicitCalloutViewOffset = CGPointZero;
}
completion:NULL];
}

- (void)showAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations animated:(BOOL)animated
{
CGFloat maximumPadding = 100;
Expand Down Expand Up @@ -3269,7 +3296,12 @@ - (void)locationManager:(__unused CLLocationManager *)manager didUpdateLocations
self.userLocationAnnotationView.haloLayer.hidden = ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate) ||
newLocation.horizontalAccuracy > 10;

[self updateUserLocationAnnotationView];
NSTimeInterval duration = MGLAnimationDuration;
if (oldLocation && ! CGPointEqualToPoint(self.userLocationAnnotationView.center, CGPointZero))
{
duration = MAX([newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp], MGLUserLocationAnimationDuration);
}
[self updateUserLocationAnnotationViewAnimatedWithDuration:duration];
}

- (void)didUpdateLocationWithUserTrackingAnimated:(BOOL)animated
Expand Down Expand Up @@ -3709,15 +3741,18 @@ - (void)notifyMapChange:(mbgl::MapChange)change
}

- (void)updateUserLocationAnnotationView
{
[self updateUserLocationAnnotationViewAnimatedWithDuration:0];
}

- (void)updateUserLocationAnnotationViewAnimatedWithDuration:(NSTimeInterval)duration
{
MGLUserLocationAnnotationView *annotationView = self.userLocationAnnotationView;
if ( ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate)) {
annotationView.hidden = YES;
return;
}

if ( ! annotationView.superview) [self.glView addSubview:annotationView];

CGPoint userPoint;
if (self.userTrackingMode != MGLUserTrackingModeNone
&& self.userTrackingState == MGLUserTrackingStateChanged)
Expand All @@ -3728,11 +3763,36 @@ - (void)updateUserLocationAnnotationView
{
userPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self];
}

if ( ! annotationView.superview)
{
[self.glView addSubview:annotationView];
// Prevents the view from sliding in from the origin.
annotationView.center = userPoint;
}

if (CGRectContainsPoint(CGRectInset(self.bounds, -MGLAnnotationUpdateViewportOutset.width,
-MGLAnnotationUpdateViewportOutset.height), userPoint))
{
annotationView.center = userPoint;
// Smoothly move the user location annotation view and callout view to
// the new location.
[UIView animateWithDuration:duration
delay:0
options:(UIViewAnimationOptionCurveLinear |
UIViewAnimationOptionAllowUserInteraction |
UIViewAnimationOptionBeginFromCurrentState)
animations:^{
if (self.selectedAnnotation == self.userLocation)
{
UIView <MGLCalloutView> *calloutView = self.calloutViewForSelectedAnnotation;
calloutView.frame = CGRectOffset(calloutView.frame,
userPoint.x - annotationView.center.x,
userPoint.y - annotationView.center.y);
}
annotationView.center = userPoint;
} completion:NULL];
_userLocationAnimationCompletionDate = [NSDate dateWithTimeIntervalSinceNow:duration];

annotationView.hidden = NO;
[annotationView setupLayers];

Expand Down

0 comments on commit 52abcb5

Please sign in to comment.