diff options
author | Julian Rex <julian.rex@gmail.com> | 2018-03-21 13:51:10 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-03-21 13:51:10 -0400 |
commit | d4f2bc07ca424fe6c4a596dac91ab247634e7aab (patch) | |
tree | c41ac7519c8c0ddca43faa61b345a56b0fa32058 /platform/macos/src | |
parent | 4b3d0638b8029421c0fb3f380f4d482cfd74f00e (diff) | |
download | qtlocation-mapboxgl-d4f2bc07ca424fe6c4a596dac91ab247634e7aab.tar.gz |
[ios,macos] Selecting offscreen annotation pans map to fit annotation & callout view (#3249, #9790)
Diffstat (limited to 'platform/macos/src')
-rw-r--r-- | platform/macos/src/MGLMapView.h | 15 | ||||
-rw-r--r-- | platform/macos/src/MGLMapView.mm | 99 |
2 files changed, 106 insertions, 8 deletions
diff --git a/platform/macos/src/MGLMapView.h b/platform/macos/src/MGLMapView.h index 050145b80b..74224622d4 100644 --- a/platform/macos/src/MGLMapView.h +++ b/platform/macos/src/MGLMapView.h @@ -721,16 +721,27 @@ MGL_EXPORT IB_DESIGNABLE Assigning a new array to this property selects only the first annotation in the array. + + If the annotation is of type `MGLPointAnnotation` and is offscreen, the map is + panned so that the annotation and its callout are brought just onscreen. The + annotation is *not* centered within the viewport. + + @note In versions prior to `4.0.0` if the annotation was offscreen it was not + selected. */ @property (nonatomic, copy) NS_ARRAY_OF(id <MGLAnnotation>) *selectedAnnotations; /** Selects an annotation and displays a callout popover for it. - If the given annotation is not visible within the current viewport, this method - has no effect. + If the annotation is of type `MGLPointAnnotation` and is offscreen, the map is + panned so that the annotation and its callout are brought just onscreen. The + annotation is *not* centered within the viewport. @param annotation The annotation object to select. + + @note In versions prior to `4.0.0` selecting an offscreen annotation did not + change the camera. */ - (void)selectAnnotation:(id <MGLAnnotation>)annotation; diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm index 353b2bf2f1..9cab9a76da 100644 --- a/platform/macos/src/MGLMapView.mm +++ b/platform/macos/src/MGLMapView.mm @@ -97,6 +97,9 @@ const CGFloat MGLAnnotationImagePaddingForHitTest = 4; /// Distance from the callout’s anchor point to the annotation it points to. const CGFloat MGLAnnotationImagePaddingForCallout = 4; +/// Padding to edge of view that an offscreen annotation must have when being brought onscreen (by being selected) +const NSEdgeInsets MGLMapViewOffscreenAnnotationPadding = NSEdgeInsetsMake(-30.0f, -30.0f, -30.0f, -30.0f); + /// Unique identifier representing a single annotation in mbgl. typedef uint32_t MGLAnnotationTag; @@ -2205,10 +2208,12 @@ public: return; } - // Select the annotation if it’s visible. - if (MGLCoordinateInCoordinateBounds(firstAnnotation.coordinate, self.visibleCoordinateBounds)) { - [self selectAnnotation:firstAnnotation]; - } + [self selectAnnotation:firstAnnotation]; +} + +- (BOOL)isBringingAnnotationOnscreenSupportedForAnnotation:(id<MGLAnnotation>)annotation animated:(BOOL)animated { + // Consider delegating + return animated && [annotation isKindOfClass:[MGLPointAnnotation class]]; } - (void)selectAnnotation:(id <MGLAnnotation>)annotation @@ -2218,6 +2223,11 @@ public: - (void)selectAnnotation:(id <MGLAnnotation>)annotation atPoint:(NSPoint)gesturePoint { + [self selectAnnotation:annotation atPoint:gesturePoint moveOnscreen:YES animateSelection:YES]; +} + +- (void)selectAnnotation:(id <MGLAnnotation>)annotation atPoint:(NSPoint)gesturePoint moveOnscreen:(BOOL)moveOnscreen animateSelection:(BOOL)animateSelection +{ id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation; if (annotation == selectedAnnotation) { return; @@ -2232,9 +2242,14 @@ public: [self addAnnotation:annotation]; } + if (moveOnscreen) { + moveOnscreen = [self isBringingAnnotationOnscreenSupportedForAnnotation:annotation animated:animateSelection]; + } + // The annotation's anchor will bounce to the current click. NSRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag]; - if (NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) { + + if (!moveOnscreen && NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) { positioningRect = CGRectMake(gesturePoint.x, gesturePoint.y, positioningRect.size.width, positioningRect.size.height); } @@ -2254,11 +2269,65 @@ public: // alignment rect, or off the left edge in a right-to-left UI. callout.delegate = self; self.calloutForSelectedAnnotation = callout; + NSRectEdge edge = (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft ? NSMinXEdge : NSMaxXEdge); + + // The following will do nothing if the positioning rect is not on-screen. See + // `-[MGLMapView updateAnnotationCallouts]` for presenting the callout when the selected + // annotation comes back on-screen. [callout showRelativeToRect:positioningRect ofView:self preferredEdge:edge]; } + + if (moveOnscreen) + { + moveOnscreen = NO; + + NSRect (^edgeInsetsInsetRect)(NSRect, NSEdgeInsets) = ^(NSRect rect, NSEdgeInsets insets) { + return NSMakeRect(rect.origin.x + insets.left, + rect.origin.y + insets.top, + rect.size.width - insets.left - insets.right, + rect.size.height - insets.top - insets.bottom); + }; + + // Add padding around the positioning rect (in essence an inset from the edge of the viewport + NSRect expandedPositioningRect = edgeInsetsInsetRect(positioningRect, MGLMapViewOffscreenAnnotationPadding); + + // Used for callout positioning, and moving offscreen annotations onscreen. + CGRect constrainedRect = edgeInsetsInsetRect(self.bounds, self.contentInsets); + CGRect bounds = constrainedRect; + + // Any one of these cases should trigger a move onscreen + if (CGRectGetMinX(positioningRect) < CGRectGetMinX(bounds)) + { + constrainedRect.origin.x = expandedPositioningRect.origin.x; + moveOnscreen = YES; + } + else if (CGRectGetMaxX(positioningRect) > CGRectGetMaxX(bounds)) + { + constrainedRect.origin.x = CGRectGetMaxX(expandedPositioningRect) - constrainedRect.size.width; + moveOnscreen = YES; + } + + if (CGRectGetMinY(positioningRect) < CGRectGetMinY(bounds)) + { + constrainedRect.origin.y = expandedPositioningRect.origin.y; + moveOnscreen = YES; + } + else if (CGRectGetMaxY(positioningRect) > CGRectGetMaxY(bounds)) + { + constrainedRect.origin.y = CGRectGetMaxY(expandedPositioningRect) - constrainedRect.size.height; + moveOnscreen = YES; + } + + if (moveOnscreen) + { + CGPoint center = CGPointMake(CGRectGetMidX(constrainedRect), CGRectGetMidY(constrainedRect)); + CLLocationCoordinate2D centerCoord = [self convertPoint:center toCoordinateFromView:self]; + [self setCenterCoordinate:centerCoord animated:animateSelection]; + } + } } - (void)showAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations animated:(BOOL)animated { @@ -2393,7 +2462,25 @@ public: - (void)updateAnnotationCallouts { NSPopover *callout = self.calloutForSelectedAnnotation; if (callout) { - callout.positioningRect = [self positioningRectForCalloutForAnnotationWithTag:_selectedAnnotationTag]; + NSRect rect = [self positioningRectForCalloutForAnnotationWithTag:_selectedAnnotationTag]; + + if (!NSIsEmptyRect(NSIntersectionRect(rect, self.bounds))) { + + // It's possible that the current callout hasn't been presented (since the original + // positioningRect was offscreen). We can check that the callout has a valid window + // This results in the callout being presented just as the annotation comes on screen + // which matches MapKit, but (currently) not iOS. + if (!callout.contentViewController.view.window) { + NSRectEdge edge = (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft + ? NSMinXEdge + : NSMaxXEdge); + // Re-present the callout + [callout showRelativeToRect:rect ofView:self preferredEdge:edge]; + } + else { + callout.positioningRect = rect; + } + } } } |