diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index a5e8916c99c..887802928d7 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 8f9bd8ed400..11ce86e76a5 100644 --- a/platform/ios/app/MBXCustomCalloutView.m +++ b/platform/ios/app/MBXCustomCalloutView.m @@ -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)]) diff --git a/platform/ios/include/MGLCalloutView.h b/platform/ios/include/MGLCalloutView.h index 8e72ee9d689..59f52adb6d9 100644 --- a/platform/ios/include/MGLCalloutView.h +++ b/platform/ios/include/MGLCalloutView.h @@ -62,6 +62,11 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)calloutViewTapped:(UIView *)calloutView; +/** + Called before the callout view appears on screen, or before the appearance animation will start. + */ +- (void)calloutViewWillAppear:(UIView *)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 195635ef1d0..a3db891efa8 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -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 _annotationsNearbyLastTap; + CGPoint _initialImplicitCalloutViewOffset; + NSDate *_userLocationAnimationCompletionDate; BOOL _isWaitingForRedundantReachableNotification; BOOL _isTargetingInterfaceBuilder; @@ -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]; @@ -2830,9 +2825,12 @@ - (void)selectAnnotation:(id )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 @@ -2940,7 +2938,7 @@ - (void)deselectAnnotation:(id )annotation animated:(BOOL)animate { if ( ! annotation) return; - if ([self.selectedAnnotation isEqual:annotation]) + if (self.selectedAnnotation == annotation) { // dismiss popup [self.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated]; @@ -2957,6 +2955,35 @@ - (void)deselectAnnotation:(id )annotation animated:(BOOL)animate } } +- (void)calloutViewWillAppear:(UIView *)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 ) *)annotations animated:(BOOL)animated { CGFloat maximumPadding = 100; @@ -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 @@ -3709,6 +3741,11 @@ - (void)notifyMapChange:(mbgl::MapChange)change } - (void)updateUserLocationAnnotationView +{ + [self updateUserLocationAnnotationViewAnimatedWithDuration:0]; +} + +- (void)updateUserLocationAnnotationViewAnimatedWithDuration:(NSTimeInterval)duration { MGLUserLocationAnnotationView *annotationView = self.userLocationAnnotationView; if ( ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate)) { @@ -3716,8 +3753,6 @@ - (void)updateUserLocationAnnotationView return; } - if ( ! annotationView.superview) [self.glView addSubview:annotationView]; - CGPoint userPoint; if (self.userTrackingMode != MGLUserTrackingModeNone && self.userTrackingState == MGLUserTrackingStateChanged) @@ -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 *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];