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

[ios] Added annotation selection tests with rotation & scaling of the map #15123

Merged
merged 6 commits into from
Aug 20, 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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -21,6 +28,10 @@ - (BOOL)respondsToSelector:(SEL)aSelector {

@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

Expand Down Expand Up @@ -502,6 +513,267 @@ - (void)testUserLocationWithOffsetAnchorPoint {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

@interface MGLMapView (MGLMapViewIntegrationTest)
- (void)updateFromDisplayLink:(CADisplayLink *)displayLink;
- (void)setNeedsRerender;
@end

@implementation MGLMapViewIntegrationTest
Expand Down Expand Up @@ -133,7 +134,7 @@ - (void)waitForMapViewToFinishLoadingStyleWithTimeout:(NSTimeInterval)timeout {

- (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];
}
Expand Down
8 changes: 4 additions & 4 deletions platform/ios/ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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>"; };
Expand Down Expand Up @@ -1920,7 +1920,7 @@
CA6914B320E67F07002DB0EE /* Annotations */ = {
isa = PBXGroup;
children = (
CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m */,
CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.mm */,
);
name = Annotations;
sourceTree = "<group>";
Expand Down Expand Up @@ -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;
Expand Down
1 change: 0 additions & 1 deletion platform/ios/src/MGLMapView_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const _Nonnull MGLUnderlyingMapUna
- (void)renderSync;

- (nonnull mbgl::Map *)mbglMap;
julianrex marked this conversation as resolved.
Show resolved Hide resolved

- (nonnull mbgl::Renderer *)renderer;

/** Returns whether the map view is currently loading or processing any assets required to render the map */
Expand Down