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

[ios] Partially offscreen annotations (without callouts) are no longer moved on-screen #13727

Merged
merged 8 commits into from
Jan 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions platform/ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
* Reinstates version 11 as the default Mapbox Streets style (as introduced in 4.7.0). ([#13690](https://github.com/mapbox/mapbox-gl-native/pull/13690))
* Added the `-[MGLShapeSource leavesOfCluster:offset:limit:]`, `-[MGLShapeSource childrenOfCluster:]`, `-[MGLShapeSource zoomLevelForExpandingCluster:]` methods for inspecting a cluster in an `MGLShapeSource`s created with the `MGLShapeSourceOptionClustered` option. Feature querying now returns clusters represented by `MGLPointFeatureCluster` objects (that conform to the `MGLCluster` protocol). ([#12952](https://github.com/mapbox/mapbox-gl-native/pull/12952)
* `MGLMapView` no longer freezes on external displays connected through AirPlay or CarPlay when the main device’s screen goes to sleep or the user manually locks the screen. ([#13701](https://github.com/mapbox/mapbox-gl-native/pull/13701))
* Fixed a bug where selecting partially on-screen annotations (without a callout) would move the map. ([#13727](https://github.com/mapbox/mapbox-gl-native/pull/13727))

## 4.7.1 - December 21, 2018

Expand Down

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions platform/ios/Integration Tests/MGLMapViewIntegrationTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
#define MGLTestAssertNotNil(myself, expression, ...) \
_XCTPrimitiveAssertNotNil(myself, expression, @#expression, __VA_ARGS__)

#define MGLTestWarning(expression, format, ...) \
({ \
if (!(expression)) { \
NSString *message = [NSString stringWithFormat:format, ##__VA_ARGS__]; \
printf("warning: Test Case '%s' at line %d: '%s' %s\n", __PRETTY_FUNCTION__, __LINE__, #expression, message.UTF8String); \
} \
})

@interface MGLMapViewIntegrationTest : XCTestCase <MGLMapViewDelegate>
@property (nonatomic) MGLMapView *mapView;
Expand All @@ -27,6 +34,8 @@
@property (nonatomic) void (^regionIsChanging)(MGLMapView *mapView);
@property (nonatomic) void (^regionDidChange)(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated);
@property (nonatomic) CGPoint (^mapViewUserLocationAnchorPoint)(MGLMapView *mapView);
@property (nonatomic) BOOL (^mapViewAnnotationCanShowCalloutForAnnotation)(MGLMapView *mapView, id<MGLAnnotation> annotation);
@property (nonatomic) id<MGLCalloutView> (^mapViewCalloutViewForAnnotation)(MGLMapView *mapView, id<MGLAnnotation> annotation);

// Utility methods
- (NSString*)validAccessToken;
Expand Down
14 changes: 14 additions & 0 deletions platform/ios/Integration Tests/MGLMapViewIntegrationTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,20 @@ - (CGPoint)mapViewUserLocationAnchorPoint:(MGLMapView *)mapView {
return CGPointZero;
}

- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id<MGLAnnotation>)annotation {
if (self.mapViewAnnotationCanShowCalloutForAnnotation) {
return self.mapViewAnnotationCanShowCalloutForAnnotation(mapView, annotation);
}
return NO;
}

- (id<MGLCalloutView>)mapView:(MGLMapView *)mapView calloutViewForAnnotation:(id<MGLAnnotation>)annotation {
if (self.mapViewCalloutViewForAnnotation) {
return self.mapViewCalloutViewForAnnotation(mapView, annotation);
}
return nil;
}

#pragma mark - Utilities

- (void)waitForMapViewToFinishLoadingStyleWithTimeout:(NSTimeInterval)timeout {
Expand Down
4 changes: 4 additions & 0 deletions platform/ios/ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@
CA0C27942076CA19001CE5B7 /* MGLMapViewIntegrationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CA0C27932076CA19001CE5B7 /* MGLMapViewIntegrationTest.m */; };
CA1B4A512099FB2200EDD491 /* MGLMapSnapshotterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CA1B4A502099FB2200EDD491 /* MGLMapSnapshotterTest.m */; };
CA34C9C3207FD272005C1A06 /* MGLCameraTransitionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CA34C9C2207FD272005C1A06 /* MGLCameraTransitionTests.mm */; };
CA45105A21EE2989000C37D5 /* MGLCompactCalloutView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8848451CBAFB9800AB86E3 /* MGLCompactCalloutView.m */; };
CA45105B21EE29B3000C37D5 /* SMCalloutView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA88488A1CBB037E00AB86E3 /* SMCalloutView.m */; };
CA4EB8C720863487006AB465 /* MGLStyleLayerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CA4EB8C620863487006AB465 /* MGLStyleLayerIntegrationTests.m */; };
CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; };
CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -3034,10 +3036,12 @@
buildActionMask = 2147483647;
files = (
CA4EB8C720863487006AB465 /* MGLStyleLayerIntegrationTests.m in Sources */,
CA45105B21EE29B3000C37D5 /* SMCalloutView.m in Sources */,
CA34C9C3207FD272005C1A06 /* MGLCameraTransitionTests.mm in Sources */,
16376B0A1FFD9DAF0000563E /* MBGLIntegrationTests.m in Sources */,
CA88DC3021C85D900059ED5A /* MGLStyleURLIntegrationTest.m in Sources */,
CA0C27942076CA19001CE5B7 /* MGLMapViewIntegrationTest.m in Sources */,
CA45105A21EE2989000C37D5 /* MGLCompactCalloutView.m in Sources */,
CAE7AD5520F46EF5003B6782 /* MGLMapSnapshotterSwiftTests.swift in Sources */,
CA0C27922076C804001CE5B7 /* MGLShapeSourceTests.m in Sources */,
077061DA215DA00E000FEF62 /* MGLTestLocationManager.m in Sources */,
Expand Down
101 changes: 74 additions & 27 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,6 @@ typedef NS_ENUM(NSUInteger, MGLUserTrackingState) {

const CGSize MGLAnnotationAccessibilityElementMinimumSize = CGSizeMake(10, 10);

/// Padding to edge of view that an offscreen annotation must have when being brought onscreen (by being selected)
const UIEdgeInsets MGLMapViewOffscreenAnnotationPadding = UIEdgeInsetsMake(-20.0f, -20.0f, -20.0f, -20.0f);

/// An indication that the requested annotation was not found or is nonexistent.
enum { MGLAnnotationTagNotFound = UINT32_MAX };

Expand Down Expand Up @@ -229,6 +226,11 @@ @interface MGLMapView () <UIGestureRecognizerDelegate,

/// Currently shown popover representing the selected annotation.
@property (nonatomic) UIView<MGLCalloutView> *calloutViewForSelectedAnnotation;

/// Anchor coordinate from which to present callout views (for example, for shapes this
/// could be the touch point rather than its centroid)
@property (nonatomic) CLLocationCoordinate2D anchorCoordinateForSelectedAnnotation;

@property (nonatomic) MGLUserLocationAnnotationView *userLocationAnnotationView;

/// Indicates how thoroughly the map view is tracking the user location.
Expand Down Expand Up @@ -4537,7 +4539,7 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)

- (BOOL)isMovingAnnotationIntoViewSupportedForAnnotation:(id<MGLAnnotation>)annotation animated:(BOOL)animated {
// Consider delegating
return animated && [annotation isKindOfClass:[MGLPointAnnotation class]];
return [annotation isKindOfClass:[MGLPointAnnotation class]];
}

- (id <MGLAnnotation>)selectedAnnotation
Expand Down Expand Up @@ -4644,12 +4646,15 @@ - (void)selectAnnotation:(id <MGLAnnotation>)annotation moveIntoView:(BOOL)moveI
CGPoint originPoint = [self convertCoordinate:origin toPointToView:self];
calloutPositioningRect = { .origin = originPoint, .size = CGSizeZero };
}

CGRect expandedPositioningRect = UIEdgeInsetsInsetRect(calloutPositioningRect, MGLMapViewOffscreenAnnotationPadding);
CGRect expandedPositioningRect = calloutPositioningRect;

// Used for callout positioning, and moving offscreen annotations onscreen.
CGRect constrainedRect = UIEdgeInsetsInsetRect(self.bounds, self.contentInset);
CGRect constrainedRect = self.contentFrame;
CGRect bounds = constrainedRect;

BOOL expandedPositioningRectToMoveCalloutIntoViewWithMargins = NO;

UIView <MGLCalloutView> *calloutView = nil;

if ([annotation respondsToSelector:@selector(title)] &&
Expand Down Expand Up @@ -4720,37 +4725,55 @@ - (void)selectAnnotation:(id <MGLAnnotation>)annotation moveIntoView:(BOOL)moveI
if (moveIntoView && [calloutView respondsToSelector:@selector(marginInsetsHintForPresentationFromRect:)]) {
UIEdgeInsets margins = [calloutView marginInsetsHintForPresentationFromRect:calloutPositioningRect];
expandedPositioningRect = UIEdgeInsetsInsetRect(expandedPositioningRect, margins);
expandedPositioningRectToMoveCalloutIntoViewWithMargins = YES;
}
}

if (!expandedPositioningRectToMoveCalloutIntoViewWithMargins)
{
// We don't have a callout (OR our callout didn't implement
// marginInsetsHintForPresentationFromRect: - in this case we need to
// ensure that partially off-screen annotations are NOT moved into view.
//
// We may want to create (and fallback to) an `MGLMapViewDelegate` version
// of the `-[MGLCalloutView marginInsetsHintForPresentationFromRect:]
// protocol method.
bounds = CGRectInset(bounds, -calloutPositioningRect.size.width, -calloutPositioningRect.size.height);
}

if (moveIntoView)
{
moveIntoView = NO;

// Need to consider the content insets.
CGRect bounds = UIEdgeInsetsInsetRect(self.bounds, self.contentInset);

// Any one of these cases should trigger a move onscreen
if (CGRectGetMinX(calloutPositioningRect) < CGRectGetMinX(bounds))
{
constrainedRect.origin.x = expandedPositioningRect.origin.x;
CGFloat minX = CGRectGetMinX(expandedPositioningRect);

if (minX < CGRectGetMinX(bounds)) {
constrainedRect.origin.x = minX;
moveIntoView = YES;
}
else if (CGRectGetMaxX(calloutPositioningRect) > CGRectGetMaxX(bounds))
{
constrainedRect.origin.x = CGRectGetMaxX(expandedPositioningRect) - constrainedRect.size.width;
moveIntoView = YES;
else {
CGFloat maxX = CGRectGetMaxX(expandedPositioningRect);

if (maxX > CGRectGetMaxX(bounds)) {
constrainedRect.origin.x = maxX - CGRectGetWidth(constrainedRect);
moveIntoView = YES;
}
}

if (CGRectGetMinY(calloutPositioningRect) < CGRectGetMinY(bounds))
{
constrainedRect.origin.y = expandedPositioningRect.origin.y;
CGFloat minY = CGRectGetMinY(expandedPositioningRect);

if (minY < CGRectGetMinY(bounds)) {
constrainedRect.origin.y = minY;
moveIntoView = YES;
}
else if (CGRectGetMaxY(calloutPositioningRect) > CGRectGetMaxY(bounds))
{
constrainedRect.origin.y = CGRectGetMaxY(expandedPositioningRect) - constrainedRect.size.height;
moveIntoView = YES;
else {
CGFloat maxY = CGRectGetMaxY(expandedPositioningRect);

if (maxY > CGRectGetMaxY(bounds)) {
constrainedRect.origin.y = maxY - CGRectGetHeight(constrainedRect);
moveIntoView = YES;
}
}
}

Expand All @@ -4760,6 +4783,17 @@ - (void)selectAnnotation:(id <MGLAnnotation>)annotation moveIntoView:(BOOL)moveI
constrainedToRect:constrainedRect
animated:animateSelection];

// Save the anchor coordinate
if ([annotation isKindOfClass:[MGLPointAnnotation class]]) {
self.anchorCoordinateForSelectedAnnotation = annotation.coordinate;
}
else {
// This is used for features like polygons, so that if the map is dragged
// the callout doesn't ping to its coordinate.
CGPoint anchorPoint = CGPointMake(CGRectGetMidX(calloutPositioningRect), CGRectGetMidY(calloutPositioningRect));
self.anchorCoordinateForSelectedAnnotation = [self convertPoint:anchorPoint toCoordinateFromView:self];
}

// notify delegate
if ([self.delegate respondsToSelector:@selector(mapView:didSelectAnnotation:)])
{
Expand Down Expand Up @@ -4824,8 +4858,18 @@ - (CGRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annota
return CGRectNull;
}

CLLocationCoordinate2D coordinate;

if ((annotation == self.selectedAnnotation) &&
CLLocationCoordinate2DIsValid(self.anchorCoordinateForSelectedAnnotation)) {
coordinate = self.anchorCoordinateForSelectedAnnotation;
}
else {
coordinate = annotation.coordinate;
}

if ([annotation isKindOfClass:[MGLMultiPoint class]]) {
CLLocationCoordinate2D origin = annotation.coordinate;
CLLocationCoordinate2D origin = coordinate;
CGPoint originPoint = [self convertCoordinate:origin toPointToView:self];
return CGRectMake(originPoint.x, originPoint.y, MGLAnnotationImagePaddingForHitTest, MGLAnnotationImagePaddingForHitTest);
}
Expand All @@ -4840,7 +4884,7 @@ - (CGRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annota
return CGRectZero;
}

CGRect positioningRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate];
CGRect positioningRect = [self frameOfImage:image centeredAtCoordinate:coordinate];
positioningRect.origin.x -= 0.5;

return CGRectInset(positioningRect, -MGLAnnotationImagePaddingForCallout,
Expand Down Expand Up @@ -4895,6 +4939,7 @@ - (void)deselectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animate
// clean up
self.calloutViewForSelectedAnnotation = nil;
self.selectedAnnotation = nil;
self.anchorCoordinateForSelectedAnnotation = kCLLocationCoordinate2DInvalid;

// notify delegate
if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)])
Expand Down Expand Up @@ -6158,7 +6203,9 @@ - (void)updateCalloutView
annotationView = self.userLocationAnnotationView;
}

CGRect positioningRect = annotationView ? annotationView.frame : [self positioningRectForCalloutForAnnotationWithTag:tag];
CGRect positioningRect = annotationView ?
annotationView.frame :
[self positioningRectForCalloutForAnnotationWithTag:tag];

MGLAssert( ! CGRectIsNull(positioningRect), @"Positioning rect should not be CGRectNull by this point");

Expand Down
12 changes: 7 additions & 5 deletions platform/ios/vendor/SMCalloutView/SMCalloutView.m
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ - (CGSize)offsetToContainRect:(CGRect)innerRect inRect:(CGRect)outerRect {

- (UIEdgeInsets)marginInsetsHintForPresentationFromRect:(CGRect)rect {

const CGFloat defaultMargin = 20.0f;

// form our subviews based on our content set so far
[self rebuildSubviews];

Expand All @@ -281,16 +283,16 @@ - (UIEdgeInsets)marginInsetsHintForPresentationFromRect:(CGRect)rect {
CGFloat horizontalMargin = fmaxf(0, ceilf((CALLOUT_MIN_WIDTH-rect.size.width)/2));

UIEdgeInsets insets = {
.top = 0.0f,
.right = -horizontalMargin,
.top = 0.0f,
.right = -defaultMargin - horizontalMargin,
.bottom = 0.0f,
.left = -horizontalMargin
.left = -defaultMargin - horizontalMargin
};

if (self.permittedArrowDirection == MGLSMCalloutArrowDirectionUp)
insets.bottom -= size.height;
insets.bottom -= (defaultMargin + size.height);
else
insets.top -= size.height;
insets.top -= (defaultMargin + size.height);

return insets;
}
Expand Down
2 changes: 1 addition & 1 deletion platform/macos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Fixed a crash when casting large numbers in `NSExpression`. ([#13580](https://github.com/mapbox/mapbox-gl-native/pull/13580))
* Added the `-[MGLShapeSource leavesOfCluster:offset:limit:]`, `-[MGLShapeSource childrenOfCluster:]`, `-[MGLShapeSource zoomLevelForExpandingCluster:]` methods for inspecting a cluster in an `MGLShapeSource`s created with the `MGLShapeSourceOptionClustered` option. Feature querying now returns clusters represented by `MGLPointFeatureCluster` objects (that conform to the `MGLCluster` protocol). ([#12952](https://github.com/mapbox/mapbox-gl-native/pull/12952)
* Fixed a bug with `MGLMapView.visibleAnnotations` that resulted in incorrect results and performance degradation. ([#13745](https://github.com/mapbox/mapbox-gl-native/pull/13745))

* Fixed a bug where selecting partially on-screen annotations (without a callout) would move the map. ([#13727](https://github.com/mapbox/mapbox-gl-native/pull/13727))

## 0.13.0 - December 20, 2018

Expand Down
Loading