diff options
author | Julian Rex <julian.rex@mapbox.com> | 2019-08-20 10:51:14 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-20 10:51:14 -0400 |
commit | c0cd8d021d828e9c3b66d4e11e3a4c8df9e2b559 (patch) | |
tree | 0dac7cfcedaa09f9e5da3501fcadd3761194e652 /platform/ios | |
parent | 888d2b06ec95514aea02fa36569d9c84b3b2fa31 (diff) | |
download | qtlocation-mapboxgl-c0cd8d021d828e9c3b66d4e11e3a4c8df9e2b559.tar.gz |
[ios] Adds annotation selection tests with rotation & scaling of the map (#15123)
Diffstat (limited to 'platform/ios')
-rw-r--r-- | platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.mm (renamed from platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.m) | 272 | ||||
-rw-r--r-- | platform/ios/Integration Tests/MGLMapViewIntegrationTest.m | 3 | ||||
-rw-r--r-- | platform/ios/ios.xcodeproj/project.pbxproj | 8 | ||||
-rw-r--r-- | platform/ios/src/MGLMapView_Private.h | 1 |
4 files changed, 278 insertions, 6 deletions
diff --git a/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.m b/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.mm index f129277e87..eb3c85e30d 100644 --- a/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.m +++ b/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.mm @@ -4,6 +4,13 @@ #import "MGLTestLocationManager.h" #import "MGLCompactCalloutView.h" +#import "MGLGeometry_Private.h" +#import "MGLMapView_Private.h" + +#include <mbgl/util/geo.hpp> +#include <mbgl/map/camera.hpp> +#include <mbgl/map/map.hpp> + @interface MGLTestCalloutView : MGLCompactCalloutView @property (nonatomic) BOOL implementsMarginHints; @end @@ -21,6 +28,10 @@ @interface MGLMapView (Tests) - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)persist; +- (id <MGLAnnotation>)annotationWithTag:(MGLAnnotationTag)tag; +- (MGLMapCamera *)cameraByRotatingToDirection:(CLLocationDirection)degrees aroundAnchorPoint:(CGPoint)anchorPoint; +- (MGLMapCamera *)cameraByZoomingToZoomLevel:(double)zoom aroundAnchorPoint:(CGPoint)anchorPoint; +- (MGLMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions; @property (nonatomic) UIView<MGLCalloutView> *calloutViewForSelectedAnnotation; @end @@ -502,6 +513,267 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f }; XCTAssertEqual(originalFrame.origin.y + offset.y, offsetFrame.origin.y); } +#pragma mark - Rotating/zooming + +- (void)testSelectingAnnotationWhenMapIsRotated { + + CLLocationCoordinate2D coordinates[] = { + { 40.0, 40.0 }, + { NAN, NAN } + }; + + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + MGLPointAnnotation *annotation = annotations.firstObject; + + // Rotate + CLLocationDirection lastAngle = 0.0; + + srand48(0); + for (NSInteger iter = 0; iter < 10; iter++ ) { + + CLLocationDirection angle = (CLLocationDirection)((drand48()*1080.0) - 540.0); + + CGPoint anchor = CGPointMake(drand48()*CGRectGetWidth(self.mapView.bounds), drand48()*CGRectGetHeight(self.mapView.bounds)); + + NSString *activityTitle = [NSString stringWithFormat:@"Rotate to: %0.1f from: %0.1f", angle, lastAngle]; + [XCTContext runActivityNamed:activityTitle + block:^(id<XCTActivity> _Nonnull activity) { + + MGLMapCamera *toCamera = [self.mapView cameraByRotatingToDirection:angle aroundAnchorPoint:anchor]; + [self internalTestSelecting:annotation withCamera:toCamera]; + }]; + + lastAngle = angle; + } +} + +- (void)testSelectingAnnotationWhenMapIsScaled { + + CLLocationCoordinate2D coordinates[] = { + { 0.005, 0.005 }, + { NAN, NAN } + }; + + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + MGLPointAnnotation *annotation = annotations.firstObject; + + CGPoint anchor = CGPointMake(CGRectGetMidX(self.mapView.bounds), CGRectGetMidY(self.mapView.bounds)); + + srand48(0); + for (NSInteger iter = 0; iter < 10; iter++ ) { + + double zoom = (double)(drand48()*14.0); + + NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom]; + [XCTContext runActivityNamed:activityTitle + block:^(id<XCTActivity> _Nonnull activity) { + MGLMapCamera *toCamera = [self.mapView cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:anchor]; + [self internalTestSelecting:annotation withCamera:toCamera]; + }]; + } +} + +- (void)testSelectingAnnotationWhenMapIsScaledAndRotated { + + CLLocationCoordinate2D coordinates[] = { + { 0.005, 0.005 }, + { NAN, NAN } + }; + + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + MGLPointAnnotation *annotation = annotations.firstObject; + + srand48(0); + for (NSInteger iter = 0; iter < 10; iter++ ) { + + double zoom = (double)(7.0 + drand48()*7.0); + CLLocationDirection angle = (CLLocationDirection)((drand48()*1080.0) - 540.0); + + CGPoint anchor = CGPointMake(drand48()*CGRectGetWidth(self.mapView.bounds), drand48()*CGRectGetHeight(self.mapView.bounds)); + + NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom]; + [XCTContext runActivityNamed:activityTitle + block:^(id<XCTActivity> _Nonnull activity) + { + mbgl::CameraOptions currentCameraOptions; + + currentCameraOptions.bearing = angle; + currentCameraOptions.anchor = mbgl::ScreenCoordinate { anchor.x, anchor.y }; + currentCameraOptions.zoom = zoom; + MGLMapCamera *toCamera = [self.mapView cameraForCameraOptions:currentCameraOptions]; + + [self internalTestSelecting:annotation withCamera:toCamera]; + }]; + } +} + + +- (void)testShowingAnnotationsThenSelectingAnimated { + [self internalTestShowingAnnotationsThenSelectingAnimated:YES]; +} + +- (void)testShowingAnnotationsThenSelecting { + [self internalTestShowingAnnotationsThenSelectingAnimated:NO]; +} + +- (void)internalTestShowingAnnotationsThenSelectingAnimated:(BOOL)animated { + srand48(0); + + CGFloat maxXPadding = std::max(CGRectGetWidth(self.mapView.bounds)/5.0, 100.0); + CGFloat maxYPadding = std::max(CGRectGetHeight(self.mapView.bounds)/5.0, 100.0); + + for (int i = 0; i < 10; i++) { + UIEdgeInsets edgePadding; + edgePadding.top = floor(drand48()*maxYPadding); + edgePadding.bottom = floor(drand48()*maxYPadding); + edgePadding.left = floor(drand48()*maxXPadding); + edgePadding.right = floor(drand48()*maxXPadding); + + UIEdgeInsets contentInsets; + contentInsets.top = floor(drand48()*maxYPadding); + contentInsets.bottom = floor(drand48()*maxYPadding); + contentInsets.left = floor(drand48()*maxXPadding); + contentInsets.right = floor(drand48()*maxXPadding); + + [self internalTestShowingAnnotationsThenSelectingAnimated:animated edgePadding:edgePadding contentInsets:contentInsets]; + } +} + +- (void)internalTestShowingAnnotationsThenSelectingAnimated:(BOOL)animated edgePadding:(UIEdgeInsets)edgeInsets contentInsets:(UIEdgeInsets)contentInsets { + CLLocationCoordinate2D coordinates[21]; + + for (int i = 0; i < (int)(sizeof(coordinates)/sizeof(coordinates[0])); i++) + { + coordinates[i].latitude = drand48(); + coordinates[i].longitude = drand48(); + } + coordinates[20] = CLLocationCoordinate2DMake(NAN, NAN); + + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + + XCTestExpectation *showCompleted = [self expectationWithDescription:@"showCompleted"]; + + self.mapView.contentInset = contentInsets; + [self.mapView showAnnotations:annotations + edgePadding:edgeInsets + animated:animated + completionHandler:^{ + [showCompleted fulfill]; + }]; + + [self waitForExpectations:@[showCompleted] timeout:3.5]; + + // These tests will fail if this isn't here. But this isn't quite what we're + // seeing in https://github.com/mapbox/mapbox-gl-native/issues/15106 + [self waitForCollisionDetectionToRun]; + + for (MGLPointAnnotation *point in annotations) { + [self internalSelectDeselectAnnotation:point]; + } + + [self.mapView removeAnnotations:annotations]; + self.mapView.contentInset = UIEdgeInsetsZero; + [self waitForCollisionDetectionToRun]; +} + +- (NSArray*)internalAddAnnotationsAtCoordinates:(CLLocationCoordinate2D*)coordinates +{ + __block NSMutableArray *annotations = [NSMutableArray array]; + + [XCTContext runActivityNamed:@"Map setup" + block:^(id<XCTActivity> _Nonnull activity) + { + + NSString * const MGLTestAnnotationReuseIdentifer = @"MGLTestAnnotationReuseIdentifer"; + + CGSize annotationSize = CGSizeMake(40.0, 40.0); + + self.viewForAnnotation = ^MGLAnnotationView*(MGLMapView *view, id<MGLAnnotation> annotation2) { + + if (![annotation2 isKindOfClass:[MGLPointAnnotation class]]) { + return nil; + } + + // No dequeue + MGLAnnotationView *annotationView = [[MGLAnnotationView alloc] initWithAnnotation:annotation2 reuseIdentifier:MGLTestAnnotationReuseIdentifer]; + annotationView.bounds = (CGRect){ .origin = CGPointZero, .size = annotationSize }; + annotationView.backgroundColor = UIColor.redColor; + annotationView.enabled = YES; + + return annotationView; + }; + + CLLocationCoordinate2D *coordinatePtr = coordinates; + while (!isnan(coordinatePtr->latitude)) { + CLLocationCoordinate2D coordinate = *coordinatePtr++; + + MGLPointAnnotation *annotation = [[MGLPointAnnotation alloc] init]; + annotation.title = NSStringFromSelector(_cmd); + annotation.coordinate = coordinate; + [annotations addObject:annotation]; + } + + [self.mapView addAnnotations:annotations]; + + }]; + + NSArray *copiedAnnotations = [annotations copy]; + annotations = nil; + + return copiedAnnotations; +} + +- (void)internalTestSelecting:(MGLPointAnnotation*)point withCamera:(MGLMapCamera*)camera { + + // Rotate + XCTestExpectation *rotationCompleted = [self expectationWithDescription:@"rotationCompleted"]; + [self.mapView setCamera:camera withDuration:0.1 animationTimingFunction:nil completionHandler:^{ + [rotationCompleted fulfill]; + }]; + + [self waitForExpectations:@[rotationCompleted] timeout:1.5]; + + // Collision detection may not have completed, if not we may not get our annotation. + [self waitForCollisionDetectionToRun]; + + // Look up annotation at point + [self internalSelectDeselectAnnotation:point]; +} + +- (void)internalSelectDeselectAnnotation:(MGLPointAnnotation*)point { + [XCTContext runActivityNamed:[NSString stringWithFormat:@"Select annotation: %@", point] + block:^(id<XCTActivity> _Nonnull activity) + { + CGPoint annotationPoint = [self.mapView convertCoordinate:point.coordinate toPointToView:self.mapView]; + + MGLAnnotationTag tagAtPoint = [self.mapView annotationTagAtPoint:annotationPoint persistingResults:YES]; + if (tagAtPoint != UINT32_MAX) + { + id <MGLAnnotation> annotation = [self.mapView annotationWithTag:tagAtPoint]; + XCTAssertNotNil(annotation); + + // Select + XCTestExpectation *selectionCompleted = [self expectationWithDescription:@"Selection completed"]; + [self.mapView selectAnnotation:annotation moveIntoView:NO animateSelection:NO completionHandler:^{ + [selectionCompleted fulfill]; + }]; + + [self waitForExpectations:@[selectionCompleted] timeout:0.05]; + + XCTAssert(self.mapView.selectedAnnotations.count == 1, @"There should only be 1 selected annotation"); + XCTAssertEqualObjects(self.mapView.selectedAnnotations.firstObject, annotation, @"The annotation should be selected"); + + // Deselect + [self.mapView deselectAnnotation:annotation animated:NO]; + } + else + { + XCTFail(@"Should be an annotation at this point: %@", NSStringFromCGPoint(annotationPoint)); + } + }]; + +} + #pragma mark - Utilities - (void)runRunLoop { diff --git a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m index 2c89bb1c77..62e038855d 100644 --- a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m +++ b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m @@ -2,6 +2,7 @@ @interface MGLMapView (MGLMapViewIntegrationTest) - (void)updateFromDisplayLink:(CADisplayLink *)displayLink; +- (void)setNeedsRerender; @end @implementation MGLMapViewIntegrationTest @@ -133,7 +134,7 @@ - (void)waitForMapViewToBeRenderedWithTimeout:(NSTimeInterval)timeout { XCTAssertNil(self.renderFinishedExpectation); - [self.mapView setNeedsDisplay]; + [self.mapView setNeedsRerender]; self.renderFinishedExpectation = [self expectationWithDescription:@"Map view should be rendered"]; [self waitForExpectations:@[self.renderFinishedExpectation] timeout:timeout]; } diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index de2bec5d99..49043bdeee 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -510,7 +510,7 @@ CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA65C4F821E9BB080068B0D4 /* MGLCluster.h in Headers */ = {isa = PBXBuildFile; fileRef = CA65C4F721E9BB080068B0D4 /* MGLCluster.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA65C4F921E9BB080068B0D4 /* MGLCluster.h in Headers */ = {isa = PBXBuildFile; fileRef = CA65C4F721E9BB080068B0D4 /* MGLCluster.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CA6914B520E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m */; }; + CA6914B520E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.mm */; }; CA7766832229C10E0008DE9E /* MGLCompactCalloutView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8848451CBAFB9800AB86E3 /* MGLCompactCalloutView.m */; }; CA7766842229C11A0008DE9E /* SMCalloutView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA88488A1CBB037E00AB86E3 /* SMCalloutView.m */; }; CA86FF0E22D8D5A0009EB14A /* MGLNetworkConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CA86FF0D22D8D5A0009EB14A /* MGLNetworkConfigurationTests.m */; }; @@ -1197,8 +1197,8 @@ 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>"; }; CA65C4F721E9BB080068B0D4 /* MGLCluster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCluster.h; sourceTree = "<group>"; }; - CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MGLAnnotationViewIntegrationTests.m; path = "Annotation Tests/MGLAnnotationViewIntegrationTests.m"; sourceTree = "<group>"; }; CA86FF0D22D8D5A0009EB14A /* MGLNetworkConfigurationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLNetworkConfigurationTests.m; sourceTree = "<group>"; }; + CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLAnnotationViewIntegrationTests.mm; path = "Annotation Tests/MGLAnnotationViewIntegrationTests.mm"; sourceTree = "<group>"; }; CA88DC2F21C85D900059ED5A /* MGLStyleURLIntegrationTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLStyleURLIntegrationTest.m; sourceTree = "<group>"; }; CA8FBC0821A47BB100D1203C /* MGLRendererConfigurationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLRendererConfigurationTests.mm; path = ../../darwin/test/MGLRendererConfigurationTests.mm; sourceTree = "<group>"; }; CAD9D0A922A86D6F001B25EE /* MGLResourceTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLResourceTests.mm; path = ../../darwin/test/MGLResourceTests.mm; sourceTree = "<group>"; }; @@ -1920,7 +1920,7 @@ CA6914B320E67F07002DB0EE /* Annotations */ = { isa = PBXGroup; children = ( - CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m */, + CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.mm */, ); name = Annotations; sourceTree = "<group>"; @@ -3196,7 +3196,7 @@ CAE7AD5520F46EF5003B6782 /* MGLMapSnapshotterSwiftTests.swift in Sources */, CA0C27922076C804001CE5B7 /* MGLShapeSourceTests.m in Sources */, 077061DA215DA00E000FEF62 /* MGLTestLocationManager.m in Sources */, - CA6914B520E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m in Sources */, + CA6914B520E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.mm in Sources */, CA1B4A512099FB2200EDD491 /* MGLMapSnapshotterTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/platform/ios/src/MGLMapView_Private.h b/platform/ios/src/MGLMapView_Private.h index e53dc8519c..12840b3586 100644 --- a/platform/ios/src/MGLMapView_Private.h +++ b/platform/ios/src/MGLMapView_Private.h @@ -53,7 +53,6 @@ FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const _Nonnull MGLUnderlyingMapUna - (void)renderSync; - (nonnull mbgl::Map *)mbglMap; - - (nonnull mbgl::Renderer *)renderer; /** Returns whether the map view is currently loading or processing any assets required to render the map */ |