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

Commit

Permalink
[iOS] Update annotation view touch handling (with offsets) (#12234)
Browse files Browse the repository at this point in the history
  • Loading branch information
julianrex authored Jul 9, 2018
1 parent e08ee73 commit 3710ef8
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 20 deletions.
1 change: 1 addition & 0 deletions platform/ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
* Added `-[MGLMapView camera:fittingShape:edgePadding:]` and `-[MGLMapView camera:fittingCoordinateBounds:edgePadding:]` allowing you specify the pitch and direction for the calculated camera. ([#12213](https://github.com/mapbox/mapbox-gl-native/pull/12213))
* Added `-[MGLMapSnapshot coordinateForPoint:]` that returns a map coordinate for a specified snapshot image point. ([#12221](https://github.com/mapbox/mapbox-gl-native/pull/12221))
* Reduced memory usage when collision debug mode is disabled. ([#12294](https://github.com/mapbox/mapbox-gl-native/issues/12294))
* Fixed a bug with annotation view touch handling when a non-zero `centerOffset` is specified. ([#12234](https://github.com/mapbox/mapbox-gl-native/pull/12234))

## 4.0.3 - June 22, 2018

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#import "MGLMapViewIntegrationTest.h"
#import "MGLTestUtility.h"
#import "MGLMapAccessibilityElement.h"

@interface MGLMapView (Tests)
- (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)persist;
@end

@interface MGLAnnotationViewIntegrationTests : MGLMapViewIntegrationTest
@end

@implementation MGLAnnotationViewIntegrationTests

- (void)testSelectingAnnotationWithCenterOffset {

for (CGFloat dx = -100.0; dx <= 100.0; dx += 100.0 ) {
for (CGFloat dy = -100.0; dy <= 100.0; dy += 100.0 ) {
CGVector offset = CGVectorMake(dx, dy);
[self internalTestSelectingAnnotationWithCenterOffsetWithOffset:offset];
}
}
}

- (void)internalTestSelectingAnnotationWithCenterOffsetWithOffset:(CGVector)offset {

NSString * const MGLTestAnnotationReuseIdentifer = @"MGLTestAnnotationReuseIdentifer";

CGFloat epsilon = 0.0000001;
CGSize size = self.mapView.bounds.size;

CGSize annotationSize = CGSizeMake(40.0, 40.0);

self.viewForAnnotation = ^MGLAnnotationView*(MGLMapView *view, id<MGLAnnotation> annotation) {

if (![annotation isKindOfClass:[MGLPointAnnotation class]]) {
return nil;
}

// No dequeue
MGLAnnotationView *annotationView = [[MGLAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:MGLTestAnnotationReuseIdentifer];
annotationView.bounds = (CGRect){ .origin = CGPointZero, .size = annotationSize };
annotationView.backgroundColor = UIColor.redColor;
annotationView.enabled = YES;
annotationView.centerOffset = offset;

return annotationView;
};

MGLPointAnnotation *point = [[MGLPointAnnotation alloc] init];
point.title = NSStringFromSelector(_cmd);
point.coordinate = CLLocationCoordinate2DMake(0.0, 0.0);
[self.mapView addAnnotation:point];

// From https://github.com/mapbox/mapbox-gl-native/issues/12259#issuecomment-401414168
//
// queryRenderedFeatures depends on collision detection having been run
// before it shows results [...]. Collision detection runs asynchronously
// (at least every 300ms, sometimes more often), and therefore the results
// of queryRenderedFeatures are similarly asynchronous.
//
// So, we need to wait before `annotationTagAtPoint:persistingResults:` will
// return out newly added annotation

[self waitForCollisionDetectionToRun];

// Check that the annotation is in the center of the view
CGPoint annotationPoint = [self.mapView convertCoordinate:point.coordinate toPointToView:self.mapView];
XCTAssertEqualWithAccuracy(annotationPoint.x, size.width/2.0, epsilon);
XCTAssertEqualWithAccuracy(annotationPoint.y, size.height/2.0, epsilon);

// Now test taps around the annotation
CGPoint tapPoint = CGPointMake(annotationPoint.x + offset.dx, annotationPoint.y + offset.dy);

MGLAnnotationTag tagAtPoint = [self.mapView annotationTagAtPoint:tapPoint persistingResults:YES];
XCTAssert(tagAtPoint != UINT32_MAX, @"Should have tapped on annotation");

CGPoint testPoints[] = {
{ tapPoint.x - annotationSize.width, tapPoint.y },
{ tapPoint.x + annotationSize.width, tapPoint.y },
{ tapPoint.x, tapPoint.y - annotationSize.height },
{ tapPoint.x, tapPoint.y + annotationSize.height },
CGPointZero
};

CGPoint *testPoint = testPoints;

while (!CGPointEqualToPoint(*testPoint, CGPointZero)) {
tagAtPoint = [self.mapView annotationTagAtPoint:*testPoints persistingResults:YES];
XCTAssert(tagAtPoint == UINT32_MAX, @"Tap should to the side of the annotation");
testPoint++;
}
}

- (void)waitForCollisionDetectionToRun {
XCTAssertNil(self.renderFinishedExpectation, @"Incorrect test setup");

self.renderFinishedExpectation = [self expectationWithDescription:@"Map view should be rendered"];
XCTestExpectation *timerExpired = [self expectationWithDescription:@"Timer expires"];

// Wait 1/2 second
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC >> 1)), dispatch_get_main_queue(), ^{
[timerExpired fulfill];
});

[self waitForExpectations:@[timerExpired, self.renderFinishedExpectation] timeout:1.0];
}

@end
1 change: 1 addition & 0 deletions platform/ios/Integration Tests/MGLMapViewIntegrationTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
@property (nonatomic) MGLStyle *style;
@property (nonatomic) XCTestExpectation *styleLoadingExpectation;
@property (nonatomic) XCTestExpectation *renderFinishedExpectation;
@property (nonatomic) MGLAnnotationView * (^viewForAnnotation)(MGLMapView *mapView, id<MGLAnnotation> annotation);
@property (nonatomic) void (^regionWillChange)(MGLMapView *mapView, BOOL animated);
@property (nonatomic) void (^regionIsChanging)(MGLMapView *mapView);
@property (nonatomic) void (^regionDidChange)(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated);
Expand Down
8 changes: 8 additions & 0 deletions platform/ios/Integration Tests/MGLMapViewIntegrationTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ - (void)tearDown {

#pragma mark - MGLMapViewDelegate

- (MGLAnnotationView*)mapView:(MGLMapView *)mapView viewForAnnotation:(id<MGLAnnotation>)annotation {
if (self.viewForAnnotation) {
return self.viewForAnnotation(mapView, annotation);
}

return nil;
}

- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style {
XCTAssertNotNil(mapView.style);
XCTAssertEqual(mapView.style, style);
Expand Down
12 changes: 12 additions & 0 deletions platform/ios/ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@
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, ); }; };
CA6914B520E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m */; };
CAA69DA4206DCD0E007279CD /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA4A26961CB6E795000B7809 /* Mapbox.framework */; };
CAA69DA5206DCD0E007279CD /* Mapbox.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA4A26961CB6E795000B7809 /* Mapbox.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CABE5DAD2072FAB40003AF3C /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8847D21CBAF91600AB86E3 /* Mapbox.framework */; };
Expand Down Expand Up @@ -1005,6 +1006,7 @@
CA4EB8C620863487006AB465 /* MGLStyleLayerIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLStyleLayerIntegrationTests.m; sourceTree = "<group>"; };
CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCameraChangeReason.h; sourceTree = "<group>"; };
CA5E5042209BDC5F001A8A81 /* MGLTestUtility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MGLTestUtility.h; path = ../../darwin/test/MGLTestUtility.h; sourceTree = "<group>"; };
CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MGLAnnotationViewIntegrationTests.m; path = "Annotation Tests/MGLAnnotationViewIntegrationTests.m"; sourceTree = "<group>"; };
DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = "<group>"; };
DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = "<group>"; };
DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1352,6 +1354,7 @@
16376B081FFD9DAF0000563E /* Integration Tests */ = {
isa = PBXGroup;
children = (
CA6914B320E67F07002DB0EE /* Annotations */,
CA1B4A4F2099FA2800EDD491 /* Snapshotter Tests */,
16376B091FFD9DAF0000563E /* MBGLIntegrationTests.m */,
16376B0B1FFD9DAF0000563E /* Info.plist */,
Expand Down Expand Up @@ -1715,6 +1718,14 @@
path = "Snapshotter Tests";
sourceTree = "<group>";
};
CA6914B320E67F07002DB0EE /* Annotations */ = {
isa = PBXGroup;
children = (
CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m */,
);
name = Annotations;
sourceTree = "<group>";
};
DA1DC9411CB6C1C2006E619F = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2809,6 +2820,7 @@
16376B0A1FFD9DAF0000563E /* MBGLIntegrationTests.m in Sources */,
CA0C27942076CA19001CE5B7 /* MGLMapViewIntegrationTest.m in Sources */,
CA0C27922076C804001CE5B7 /* MGLShapeSourceTests.m in Sources */,
CA6914B520E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m in Sources */,
CA1B4A512099FB2200EDD491 /* MGLMapSnapshotterTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
33 changes: 13 additions & 20 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1658,23 +1658,6 @@ - (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap
}
}

// Handle the case of an offset annotation view by converting the tap point to be the geo location
// of the annotation itself that the view represents
for (MGLAnnotationView *view in self.annotationContainerView.annotationViews)
{
if (view.centerOffset.dx != 0 || view.centerOffset.dy != 0) {
if (CGRectContainsPoint(view.frame, tapPoint)) {
if (!view.annotation) {
[NSException raise:NSInvalidArgumentException
format:@"Annotation view's annotation property should not be nil."];
}

CGPoint annotationPoint = [self convertCoordinate:view.annotation.coordinate toPointToView:self];
tapPoint = annotationPoint;
}
}
}

MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:tapPoint persistingResults:persist];
if (hitAnnotationTag != MGLAnnotationTagNotFound)
{
Expand Down Expand Up @@ -3819,8 +3802,12 @@ - (MGLAnnotationView *)annotationViewForAnnotation:(id<MGLAnnotation>)annotation
annotationView.mapView = self;
CGRect bounds = UIEdgeInsetsInsetRect({ CGPointZero, annotationView.frame.size }, annotationView.alignmentRectInsets);

_largestAnnotationViewSize = CGSizeMake(MAX(_largestAnnotationViewSize.width, CGRectGetWidth(bounds)),
MAX(_largestAnnotationViewSize.height, CGRectGetHeight(bounds)));
// Take any offset into consideration
CGFloat adjustedAnnotationWidth = CGRectGetWidth(bounds) + fabs(annotationView.centerOffset.dx);
CGFloat adjustedAnnotationHeight = CGRectGetHeight(bounds) + fabs(annotationView.centerOffset.dx);

_largestAnnotationViewSize = CGSizeMake(MAX(_largestAnnotationViewSize.width, adjustedAnnotationWidth),
MAX(_largestAnnotationViewSize.height, adjustedAnnotationHeight));

_unionedAnnotationRepresentationSize = CGSizeMake(MAX(_unionedAnnotationRepresentationSize.width, _largestAnnotationViewSize.width),
MAX(_unionedAnnotationRepresentationSize.height, _largestAnnotationViewSize.height));
Expand Down Expand Up @@ -4089,9 +4076,15 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)
{
return true;
}

CGPoint calloutAnchorPoint = MGLPointRounded([self convertCoordinate:annotation.coordinate toPointToView:self]);
CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2);

// We need to take any offset into consideration. Note that a large offset will result in a
// large value for `_unionedAnnotationRepresentationSize` (and thus a larger feature query rect).
// Aim to keep the offset as small as possible.
frame = CGRectOffset(frame, annotationView.centerOffset.dx, annotationView.centerOffset.dy);

annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets);
}
else
Expand Down

0 comments on commit 3710ef8

Please sign in to comment.