[ios] Animate user dot between user location updates

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.
This commit is contained in:
Minh Nguyễn
2015-05-17 11:14:55 -07:00
parent f936295b76
commit 52abcb5a73
4 changed files with 91 additions and 20 deletions

View File

@@ -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))

View File

@@ -40,6 +40,11 @@ static CGFloat const tipWidth = 10.0;
- (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)])

View File

@@ -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

View File

@@ -200,6 +200,8 @@ public:
/// 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;
@@ -1268,21 +1270,14 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
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];
@@ -2830,9 +2825,12 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
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
@@ -2940,7 +2938,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
{
if ( ! annotation) return;
if ([self.selectedAnnotation isEqual:annotation])
if (self.selectedAnnotation == annotation)
{
// dismiss popup
[self.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated];
@@ -2957,6 +2955,35 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
}
}
- (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;
@@ -3269,7 +3296,12 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
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
@@ -3709,6 +3741,11 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
}
- (void)updateUserLocationAnnotationView
{
[self updateUserLocationAnnotationViewAnimatedWithDuration:0];
}
- (void)updateUserLocationAnnotationViewAnimatedWithDuration:(NSTimeInterval)duration
{
MGLUserLocationAnnotationView *annotationView = self.userLocationAnnotationView;
if ( ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate)) {
@@ -3716,8 +3753,6 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
return;
}
if ( ! annotationView.superview) [self.glView addSubview:annotationView];
CGPoint userPoint;
if (self.userTrackingMode != MGLUserTrackingModeNone
&& self.userTrackingState == MGLUserTrackingStateChanged)
@@ -3728,11 +3763,36 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
{
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];