summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2015-05-17 11:14:55 -0700
committerMinh Nguyễn <mxn@1ec5.org>2016-04-17 22:44:18 -0700
commit52abcb5a735471dc4bf5eead0f3682a332bf34d1 (patch)
tree32f66459f13c3c5ac8dde5b5d1dba28eaecf71c9 /platform
parentf936295b76df6f998f8bf9508e7f370fe92cd0cf (diff)
downloadqtlocation-mapboxgl-52abcb5a735471dc4bf5eead0f3682a332bf34d1.tar.gz
[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.
Diffstat (limited to 'platform')
-rw-r--r--platform/ios/CHANGELOG.md1
-rw-r--r--platform/ios/app/MBXCustomCalloutView.m5
-rw-r--r--platform/ios/include/MGLCalloutView.h5
-rw-r--r--platform/ios/src/MGLMapView.mm100
4 files changed, 91 insertions, 20 deletions
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index a5e8916c99..887802928d 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -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))
diff --git a/platform/ios/app/MBXCustomCalloutView.m b/platform/ios/app/MBXCustomCalloutView.m
index 8f9bd8ed40..11ce86e76a 100644
--- a/platform/ios/app/MBXCustomCalloutView.m
+++ b/platform/ios/app/MBXCustomCalloutView.m
@@ -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)])
diff --git a/platform/ios/include/MGLCalloutView.h b/platform/ios/include/MGLCalloutView.h
index 8e72ee9d68..59f52adb6d 100644
--- a/platform/ios/include/MGLCalloutView.h
+++ b/platform/ios/include/MGLCalloutView.h
@@ -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 \ No newline at end of file
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 195635ef1d..a3db891efa 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -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
@@ -3710,14 +3742,17 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
- (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)
@@ -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];