diff options
Diffstat (limited to 'platform/ios')
29 files changed, 1223 insertions, 257 deletions
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 37612a89b8..fae5599439 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -4,10 +4,16 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ## master -### Styles and rendering -* Fixed rendering and collision detection issues with using `text-variable-anchor` and `icon-text-fit` properties on the same layer. ([#15367](https://github.com/mapbox/mapbox-gl-native/pull/15367)) +* Fixed an issue that caused the tilt gesture to trigger too easily and conflict with pinch or pan gestures. ([#15349](https://github.com/mapbox/mapbox-gl-native/pull/15349)) +* Fixed a bug with annotation view positions after camera transitions. ([#15122](https://github.com/mapbox/mapbox-gl-native/pull/15122/)) + + ### Performance improvements + + * Mark used offline region resources in batches. ([#15521](https://github.com/mapbox/mapbox-gl-native/pull/15521)) + +## 5.3.0 - August 28, 2019 -## 5.3.0 +This release changes how offline tile requests are billed — they are now billed on a pay-as-you-go basis and all developers are able raise the offline tile limit for their users. Offline requests were previously exempt from monthly active user (MAU) billing and increasing the offline per-user tile limit to more than 6,000 tiles required the purchase of an enterprise license. By upgrading to this release, you are opting into the changes outlined in [this blog post](https://blog.mapbox.com/offline-maps-for-all-bb0fc51827be) and [#15380](https://github.com/mapbox/mapbox-gl-native/pull/15380). ### Styles and rendering @@ -16,13 +22,18 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Fixed an issue where animated camera transitions zoomed in or out too dramatically. ([#15281](https://github.com/mapbox/mapbox-gl-native/pull/15281)) * Enabled variable label placement when `MGLSymbolStyleLayer.textAllowsOverlap` is set to true. ([#15354](https://github.com/mapbox/mapbox-gl-native/pull/15354)) * Added the `MGLSymbolStyleLayer.textWritingModes` layout property. This property can be set to `MGLTextWritingModeHorizontal` or `MGLTextWritingModeVertical`. ([#14932](https://github.com/mapbox/mapbox-gl-native/pull/14932)) +* Fixed rendering and collision detection issues with using `MGLSymbolStyleLayer.textVariableAnchor` and `MGLSymbolStyleLayer.iconTextFit` properties on the same layer. ([#15367](https://github.com/mapbox/mapbox-gl-native/pull/15367)) +* Fixed symbol overlap when zooming out quickly. ([15416](https://github.com/mapbox/mapbox-gl-native/pull/15416)) +* Fixed a rendering issue where non-SDF icons would be treated as SDF icons if they are in the same layer. ([#15456](https://github.com/mapbox/mapbox-gl-native/pull/15456)) ### Other changes -* Fixed use of objects after moving, potentially causing crashes. [#15408](https://github.com/mapbox/mapbox-gl-native/pull/1540) +* Fixed a bug where glyphs generated through the LocalGlyphRasterizer interface were changing fonts during some zoom changes. ([#15407](https://github.com/mapbox/mapbox-gl-native/pull/15407)) +* Fixed use of objects after moving, potentially causing crashes. ([#15408](https://github.com/mapbox/mapbox-gl-native/pull/15408)) * Fixed a possible crash that could be caused by invoking the wrong layer implementation casting function. ([#15398](https://github.com/mapbox/mapbox-gl-native/pull/15398)) * Fixed a rare crash when tile download requests returned “404 Not Found” errors. ([#15313](https://github.com/mapbox/mapbox-gl-native/pull/15313)) * `MGLLoggingLevel` has been updated to better match core log levels. You can now use `MGLLoggingConfiguration.loggingLevel` to filter logs from core. ([#15120](https://github.com/mapbox/mapbox-gl-native/pull/15120)) +* Fixed an issue where the scale bar could show `0 mm` instead of `0`. ([#15381](https://github.com/mapbox/mapbox-gl-native/pull/15381)) ## 4.11.2 - July 30, 2019 diff --git a/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.m b/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.mm index f129277e87..1b3603419e 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 { @@ -529,6 +801,7 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f }; - (void)waitForCollisionDetectionToRun { XCTAssertNil(self.renderFinishedExpectation, @"Incorrect test setup"); + [self.mapView setNeedsRerender]; self.renderFinishedExpectation = [self expectationWithDescription:@"Map view should be rendered"]; XCTestExpectation *timerExpired = [self expectationWithDescription:@"Timer expires"]; diff --git a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h index dedafdb83a..07d8698f38 100644 --- a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h +++ b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h @@ -26,6 +26,7 @@ @interface MGLMapViewIntegrationTest : XCTestCase <MGLMapViewDelegate> @property (nonatomic) MGLMapView *mapView; +@property (nonatomic) UIWindow *window; @property (nonatomic) MGLStyle *style; @property (nonatomic) XCTestExpectation *styleLoadingExpectation; @property (nonatomic) XCTestExpectation *renderFinishedExpectation; diff --git a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m index 2c89bb1c77..3fa191dfcd 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 @@ -43,9 +44,9 @@ UIView *superView = [[UIView alloc] initWithFrame:UIScreen.mainScreen.bounds]; [superView addSubview:self.mapView]; - UIWindow *window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; - [window addSubview:superView]; - [window makeKeyAndVisible]; + self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + [self.window addSubview:superView]; + [self.window makeKeyAndVisible]; if (!self.mapView.style) { [self waitForMapViewToFinishLoadingStyleWithTimeout:10]; @@ -57,6 +58,7 @@ self.renderFinishedExpectation = nil; self.mapView = nil; self.style = nil; + self.window = nil; [MGLAccountManager setAccessToken:nil]; [super tearDown]; @@ -133,9 +135,10 @@ - (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]; + self.renderFinishedExpectation = nil; } - (void)waitForExpectations:(NSArray<XCTestExpectation *> *)expectations timeout:(NSTimeInterval)seconds { diff --git a/platform/ios/Integration Tests/MGLMapViewPendingBlockTests.m b/platform/ios/Integration Tests/MGLMapViewPendingBlockTests.m new file mode 100644 index 0000000000..ad71dafd48 --- /dev/null +++ b/platform/ios/Integration Tests/MGLMapViewPendingBlockTests.m @@ -0,0 +1,367 @@ +#import "MGLMapViewIntegrationTest.h" +#import "MGLTestUtility.h" + +@interface MGLMapView (MGLMapViewPendingBlockTests) +@property (nonatomic) NSMutableArray *pendingCompletionBlocks; +- (void)pauseRendering:(__unused NSNotification *)notification; +@end + +@interface MGLMapViewPendingBlockTests : MGLMapViewIntegrationTest +@property (nonatomic, copy) void (^observation)(NSDictionary*); +@property (nonatomic) BOOL completionHandlerCalled; +@end + +@implementation MGLMapViewPendingBlockTests + +- (void)testSetCenterCoordinate { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + [strongSelf.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0) + zoomLevel:10.0 + direction:0 + animated:NO + completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testSetCenterCoordinateAnimated { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + [strongSelf.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0) + zoomLevel:10.0 + direction:0 + animated:YES + completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testSetVisibleCoordinateBounds { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + [strongSelf.mapView setVisibleCoordinateBounds:unitBounds + edgePadding:UIEdgeInsetsZero + animated:NO + completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testSetVisibleCoordinateBoundsAnimated { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + [strongSelf.mapView setVisibleCoordinateBounds:unitBounds + edgePadding:UIEdgeInsetsZero + animated:YES + completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testSetCamera { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + MGLMapCamera *camera = [strongSelf.mapView cameraThatFitsCoordinateBounds:unitBounds]; + + [strongSelf.mapView setCamera:camera withDuration:0.0 animationTimingFunction:nil completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testSetCameraAnimated { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + MGLMapCamera *camera = [strongSelf.mapView cameraThatFitsCoordinateBounds:unitBounds]; + + [strongSelf.mapView setCamera:camera withDuration:0.3 animationTimingFunction:nil completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +// Marked as pending due to https://github.com/mapbox/mapbox-gl-native/issues/15471 +- (void)testFlyToCameraPENDING { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + MGLMapCamera *camera = [strongSelf.mapView cameraThatFitsCoordinateBounds:unitBounds]; + + [strongSelf.mapView flyToCamera:camera withDuration:0.0 completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testFlyToCameraAnimated { + + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + MGLMapCamera *camera = [strongSelf.mapView cameraThatFitsCoordinateBounds:unitBounds]; + + [strongSelf.mapView flyToCamera:camera withDuration:0.3 completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + + +#pragma mark - test interrupting regular rendering + +- (void)testSetCenterCoordinateSetHidden { + + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + [strongSelf.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0) + zoomLevel:10.0 + direction:0 + animated:NO + completionHandler:completion]; + } + else { + completion(); + } + }; + + dispatch_block_t addedToPending = ^{ + __typeof__(self) strongSelf = weakSelf; + + MGLTestAssert(strongSelf, !strongSelf.completionHandlerCalled); + + // Now hide the mapview + strongSelf.mapView.hidden = YES; + + MGLTestAssert(strongSelf, strongSelf.completionHandlerCalled); + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:addedToPending]; +} + +- (void)testSetCenterCoordinatePauseRendering { + + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + [strongSelf.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0) + zoomLevel:10.0 + direction:0 + animated:NO + completionHandler:completion]; + } + else { + completion(); + } + }; + + dispatch_block_t addedToPending = ^{ + __typeof__(self) strongSelf = weakSelf; + + MGLTestAssert(strongSelf, !strongSelf.completionHandlerCalled); + + // Pause rendering, stopping display link + [strongSelf.mapView pauseRendering:nil]; + + MGLTestAssert(strongSelf, strongSelf.completionHandlerCalled); + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:addedToPending]; +} + +- (void)testSetCenterCoordinateRemoveFromSuperview { + + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + [strongSelf.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0) + zoomLevel:10.0 + direction:0 + animated:NO + completionHandler:completion]; + } + else { + completion(); + } + }; + + dispatch_block_t addedToPending = ^{ + __typeof__(self) strongSelf = weakSelf; + + MGLTestAssert(strongSelf, !strongSelf.completionHandlerCalled); + + // Remove from window, triggering validateDisplayLink + [strongSelf.mapView removeFromSuperview]; + + MGLTestAssert(strongSelf, strongSelf.completionHandlerCalled); + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:addedToPending]; +} + +#pragma mark - Shared utility methods + +- (void)internalTestCompletionBlockAddedToPendingForTestName:(NSString *)testName + transition:(void (^)(dispatch_block_t))transition + addToPendingCallback:(dispatch_block_t)addToPendingCallback { + + XCTestExpectation *expectation = [self expectationWithDescription:testName]; + + __weak __typeof__(self) myself = self; + + dispatch_block_t block = ^{ + myself.completionHandlerCalled = YES; + [expectation fulfill]; + }; + + XCTAssertNotNil(transition); + transition(block); + + XCTAssert(!self.completionHandlerCalled); + XCTAssert(self.mapView.pendingCompletionBlocks.count == 0); + + __block BOOL blockAddedToPendingBlocks = NO; + + // Observes changes to pendingCompletionBlocks (including additions) + self.observation = ^(NSDictionary *change){ + + NSLog(@"change = %@ count = %lu", change, myself.mapView.pendingCompletionBlocks.count); + + NSArray *value = change[NSKeyValueChangeNewKey]; + + MGLTestAssert(myself, [value isKindOfClass:[NSArray class]]); + + if (value.count > 0) { + MGLTestAssert(myself, [value containsObject:block]); + MGLTestAssert(myself, !blockAddedToPendingBlocks); + if ([myself.mapView.pendingCompletionBlocks containsObject:block]) { + blockAddedToPendingBlocks = YES; + + if (addToPendingCallback) { + addToPendingCallback(); + } + } + } + }; + + [self.mapView addObserver:self forKeyPath:@"pendingCompletionBlocks" options:NSKeyValueObservingOptionNew context:_cmd]; + + [self waitForExpectations:@[expectation] timeout:0.5]; + + XCTAssert(blockAddedToPendingBlocks); + XCTAssert(self.completionHandlerCalled); + XCTAssert(self.mapView.pendingCompletionBlocks.count == 0); + + [self.mapView removeObserver:self forKeyPath:@"pendingCompletionBlocks" context:_cmd]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { + if (self.observation) { + self.observation(change); + } +} +@end diff --git a/platform/ios/Mapbox-iOS-SDK-snapshot-dynamic.podspec b/platform/ios/Mapbox-iOS-SDK-snapshot-dynamic.podspec index 77ff18c104..4a781ba688 100644 --- a/platform/ios/Mapbox-iOS-SDK-snapshot-dynamic.podspec +++ b/platform/ios/Mapbox-iOS-SDK-snapshot-dynamic.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '5.3.0-alpha.3' + version = '5.3.0' m.name = 'Mapbox-iOS-SDK-snapshot-dynamic' m.version = "#{version}-snapshot" diff --git a/platform/ios/Mapbox-iOS-SDK-stripped.podspec b/platform/ios/Mapbox-iOS-SDK-stripped.podspec index 5c8b30220a..3d37e26c69 100644 --- a/platform/ios/Mapbox-iOS-SDK-stripped.podspec +++ b/platform/ios/Mapbox-iOS-SDK-stripped.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '5.3.0-alpha.3' + version = '5.3.0' m.name = 'Mapbox-iOS-SDK-stripped' m.version = "#{version}-stripped" diff --git a/platform/ios/Mapbox-iOS-SDK.podspec b/platform/ios/Mapbox-iOS-SDK.podspec index c356a3c12d..2901dd6720 100644 --- a/platform/ios/Mapbox-iOS-SDK.podspec +++ b/platform/ios/Mapbox-iOS-SDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '5.3.0-alpha.3' + version = '5.3.0' m.name = 'Mapbox-iOS-SDK' m.version = version diff --git a/platform/ios/app/MBXState.h b/platform/ios/app/MBXState.h index 7cf064acd8..346c31d586 100644 --- a/platform/ios/app/MBXState.h +++ b/platform/ios/app/MBXState.h @@ -12,7 +12,6 @@ FOUNDATION_EXTERN NSString *const MBXShowsZoomLevelOrnament; FOUNDATION_EXTERN NSString *const MBXShowsTimeFrameGraph; FOUNDATION_EXTERN NSString *const MBXMapFramerateMeasurementEnabled; FOUNDATION_EXTERN NSString *const MBXDebugMaskValue; -FOUNDATION_EXTERN NSString *const MBXDebugLoggingEnabled; FOUNDATION_EXTERN NSString *const MBXReuseQueueStatsEnabled; @interface MBXState : NSObject <NSSecureCoding> @@ -26,7 +25,6 @@ FOUNDATION_EXTERN NSString *const MBXReuseQueueStatsEnabled; @property (nonatomic) BOOL showsTimeFrameGraph; @property (nonatomic) BOOL framerateMeasurementEnabled; @property (nonatomic) MGLMapDebugMaskOptions debugMask; -@property (nonatomic) BOOL debugLoggingEnabled; @property (nonatomic) BOOL reuseQueueStatsEnabled; @property (nonatomic, readonly) NSString *debugDescription; diff --git a/platform/ios/app/MBXState.m b/platform/ios/app/MBXState.m index 0365306637..455961e88a 100644 --- a/platform/ios/app/MBXState.m +++ b/platform/ios/app/MBXState.m @@ -6,7 +6,6 @@ NSString *const MBXShowsUserLocation = @"MBXShowsUserLocation"; NSString *const MBXDebugMaskValue = @"MBXDebugMaskValue"; NSString *const MBXShowsZoomLevelOrnament = @"MBXShowsZoomLevelOrnament"; NSString *const MBXShowsTimeFrameGraph = @"MBXShowsFrameTimeGraph"; -NSString *const MBXDebugLoggingEnabled = @"MGLMapboxMetricsDebugLoggingEnabled"; NSString *const MBXShowsMapScale = @"MBXMapShowsScale"; NSString *const MBXMapShowsHeadingIndicator = @"MBXMapShowsHeadingIndicator"; NSString *const MBXMapFramerateMeasurementEnabled = @"MBXMapFramerateMeasurementEnabled"; @@ -26,7 +25,6 @@ NSString *const MBXReuseQueueStatsEnabled = @"MBXReuseQueueStatsEnabled"; [coder encodeObject:[NSNumber numberWithUnsignedInteger:_debugMask] forKey:MBXDebugMaskValue]; [coder encodeBool:_showsZoomLevelOrnament forKey:MBXShowsZoomLevelOrnament]; [coder encodeBool:_showsTimeFrameGraph forKey:MBXShowsTimeFrameGraph]; - [coder encodeBool:_debugLoggingEnabled forKey:MBXDebugLoggingEnabled]; [coder encodeBool:_showsMapScale forKey:MBXShowsMapScale]; [coder encodeBool:_showsUserHeadingIndicator forKey:MBXMapShowsHeadingIndicator]; [coder encodeBool:_framerateMeasurementEnabled forKey:MBXMapFramerateMeasurementEnabled]; @@ -41,7 +39,6 @@ NSString *const MBXReuseQueueStatsEnabled = @"MBXReuseQueueStatsEnabled"; NSNumber *decodedDebugMaskOptions = [decoder decodeObjectForKey:MBXDebugMaskValue]; BOOL decodedZoomLevelOrnament = [decoder decodeBoolForKey:MBXShowsZoomLevelOrnament]; BOOL decodedShowsTimeFrameGraph = [decoder decodeBoolForKey:MBXShowsTimeFrameGraph]; - BOOL decodedDebugLoggingEnabled = [decoder decodeBoolForKey:MBXDebugLoggingEnabled]; BOOL decodedShowsMapScale = [decoder decodeBoolForKey:MBXShowsMapScale]; BOOL decodedShowsUserHeadingIndicator = [decoder decodeBoolForKey:MBXMapShowsHeadingIndicator]; BOOL decodedFramerateMeasurementEnabled = [decoder decodeBoolForKey:MBXMapFramerateMeasurementEnabled]; @@ -53,7 +50,6 @@ NSString *const MBXReuseQueueStatsEnabled = @"MBXReuseQueueStatsEnabled"; _debugMask = decodedDebugMaskOptions.intValue; _showsZoomLevelOrnament = decodedZoomLevelOrnament; _showsTimeFrameGraph = decodedShowsTimeFrameGraph; - _debugLoggingEnabled = decodedDebugLoggingEnabled; _showsMapScale = decodedShowsMapScale; _showsUserHeadingIndicator = decodedShowsUserHeadingIndicator; _framerateMeasurementEnabled = decodedFramerateMeasurementEnabled; @@ -67,15 +63,14 @@ NSString *const MBXReuseQueueStatsEnabled = @"MBXReuseQueueStatsEnabled"; return YES; } -- (NSString*) debugDescription { - return [NSString stringWithFormat:@"Camera: %@\nTracking mode: %lu\nShows user location: %@\nDebug mask value: %lu\nShows zoom level ornament: %@\nShows time frame graph: %@\nDebug logging enabled: %@\nShows map scale: %@\nShows user heading indicator: %@\nFramerate measurement enabled: %@\nReuse queue stats enabled: %@", +- (NSString *)debugDescription { + return [NSString stringWithFormat:@"Camera: %@\nTracking mode: %lu\nShows user location: %@\nDebug mask value: %lu\nShows zoom level ornament: %@\nShows time frame graph: %@\nShows map scale: %@\nShows user heading indicator: %@\nFramerate measurement enabled: %@\nReuse queue stats enabled: %@", self.camera, (unsigned long)self.userTrackingMode, self.showsUserLocation ? @"YES" : @"NO", (unsigned long)self.debugMask, self.showsZoomLevelOrnament ? @"YES" : @"NO", self.showsTimeFrameGraph ? @"YES" : @"NO", - self.debugLoggingEnabled ? @"YES" : @"NO", self.showsMapScale ? @"YES" : @"NO", self.showsUserHeadingIndicator ? @"YES" : @"NO", self.framerateMeasurementEnabled ? @"YES" : @"NO", diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 2fb95e1b17..346a182a88 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -211,7 +211,6 @@ CLLocationCoordinate2D randomWorldCoordinate() { @property (nonatomic) BOOL shouldLimitCameraChanges; @property (nonatomic) BOOL randomWalk; @property (nonatomic) BOOL zoomLevelOrnamentEnabled; -@property (nonatomic) BOOL debugLoggingEnabled; @property (nonatomic) NSMutableArray<UIWindow *> *helperWindows; @property (nonatomic) NSMutableArray<UIView *> *contentInsetsOverlays; @@ -246,7 +245,6 @@ CLLocationCoordinate2D randomWorldCoordinate() { self.mapView.showsScale = YES; self.zoomLevelOrnamentEnabled = NO; self.frameTimeGraphEnabled = NO; - self.debugLoggingEnabled = YES; } else { // Revert to the previously saved state [self restoreMapState:nil]; @@ -441,14 +439,6 @@ CLLocationCoordinate2D randomWorldCoordinate() { @"Ornaments Placement", ]]; - if (self.currentState.debugLoggingEnabled) - { - [settingsTitles addObjectsFromArray:@[ - @"Print Telemetry Logfile", - @"Delete Telemetry Logfile", - ]]; - }; - break; default: NSAssert(NO, @"All settings sections should be implemented"); @@ -558,15 +548,12 @@ CLLocationCoordinate2D randomWorldCoordinate() { case MBXSettingsAnnotationSelectRandomOffscreenPointAnnotation: [self selectAnOffscreenPointAnnotation]; break; - case MBXSettingsAnnotationCenterSelectedAnnotation: [self centerSelectedAnnotation]; break; - case MBXSettingsAnnotationAddVisibleAreaPolyline: [self addVisibleAreaPolyline]; break; - default: NSAssert(NO, @"All annotations setting rows should be implemented"); break; @@ -667,13 +654,6 @@ CLLocationCoordinate2D randomWorldCoordinate() { case MBXSettingsMiscellaneousRandomTour: [self randomWorldTour]; break; - - case MBXSettingsMiscellaneousPrintLogFile: - [self printTelemetryLogFile]; - break; - case MBXSettingsMiscellaneousDeleteLogFile: - [self deleteTelemetryLogFile]; - break; case MBXSettingsMiscellaneousScrollView: { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; @@ -1734,37 +1714,6 @@ CLLocationCoordinate2D randomWorldCoordinate() { return backupImage; } -- (void)printTelemetryLogFile -{ - NSString *fileContents = [NSString stringWithContentsOfFile:[self telemetryDebugLogFilePath] encoding:NSUTF8StringEncoding error:nil]; - NSLog(@"%@", fileContents); -} - -- (void)deleteTelemetryLogFile -{ - NSString *filePath = [self telemetryDebugLogFilePath]; - if ([[NSFileManager defaultManager] isDeletableFileAtPath:filePath]) - { - NSError *error; - BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; - if (success) { - NSLog(@"Deleted telemetry log."); - } else { - NSLog(@"Error deleting telemetry log: %@", error.localizedDescription); - } - } -} - -- (NSString *)telemetryDebugLogFilePath -{ - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd"]; - [dateFormatter setTimeZone:[NSTimeZone systemTimeZone]]; - NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"telemetry_log-%@.json", [dateFormatter stringFromDate:[NSDate date]]]]; - - return filePath; -} - #pragma mark - Random World Tour - (void)addAnnotations:(NSInteger)numAnnotations aroundCoordinate:(CLLocationCoordinate2D)coordinate radius:(CLLocationDistance)radius { @@ -2396,7 +2345,6 @@ CLLocationCoordinate2D randomWorldCoordinate() { self.currentState.showsZoomLevelOrnament = self.zoomLevelOrnamentEnabled; self.currentState.showsTimeFrameGraph = self.frameTimeGraphEnabled; self.currentState.debugMask = self.mapView.debugMask; - self.currentState.debugLoggingEnabled = self.debugLoggingEnabled; self.currentState.reuseQueueStatsEnabled = self.reuseQueueStatsEnabled; [[MBXStateManager sharedManager] saveState:self.currentState]; @@ -2413,7 +2361,6 @@ CLLocationCoordinate2D randomWorldCoordinate() { self.zoomLevelOrnamentEnabled = currentState.showsZoomLevelOrnament; self.frameTimeGraphEnabled = currentState.showsTimeFrameGraph; self.mapView.debugMask = currentState.debugMask; - self.debugLoggingEnabled = currentState.debugLoggingEnabled; self.reuseQueueStatsEnabled = currentState.reuseQueueStatsEnabled; self.currentState = currentState; diff --git a/platform/ios/app/Main.storyboard b/platform/ios/app/Main.storyboard index 28316745a1..ac83bd968f 100644 --- a/platform/ios/app/Main.storyboard +++ b/platform/ios/app/Main.storyboard @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14810.11" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="PSe-Ot-7Ff"> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="PSe-Ot-7Ff"> <device id="retina4_7" orientation="portrait" appearance="light"/> <dependencies> <deployment identifier="iOS"/> - <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14766.13"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14819.2"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> @@ -13,11 +13,11 @@ <objects> <viewController id="WaX-pd-UZQ" userLabel="Map View Controller" customClass="MBXViewController" sceneMemberID="viewController"> <view key="view" contentMode="scaleToFill" id="Z9X-fc-PUC"> - <rect key="frame" x="0.0" y="0.0" width="203" height="33"/> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kNe-zV-9ha" customClass="MGLMapView"> - <rect key="frame" x="0.0" y="0.0" width="203" height="33"/> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <subviews> <button hidden="YES" opaque="NO" userInteractionEnabled="NO" alpha="0.69999999999999996" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="58y-pX-YyB"> <rect key="frame" x="8" y="82" width="40" height="20"/> @@ -38,7 +38,7 @@ </userDefinedRuntimeAttributes> </button> <view hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="BHE-Wn-x69" customClass="MBXFrameTimeGraphView"> - <rect key="frame" x="0.0" y="-167" width="203" height="200"/> + <rect key="frame" x="0.0" y="467" width="375" height="200"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <accessibility key="accessibilityConfiguration"> <accessibilityTraits key="traits" notEnabled="YES"/> @@ -84,7 +84,7 @@ </connections> </barButtonItem> <button key="titleView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="KsN-ny-Hou"> - <rect key="frame" x="65" y="5.5" width="203" height="33"/> + <rect key="frame" x="89" y="5.5" width="148" height="33"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/> <state key="normal" title="Streets"/> @@ -198,7 +198,7 @@ <action selector="addCurrentRegion:" destination="7q0-lI-zqb" id="G2O-3V-aEA"/> </connections> </barButtonItem> - <barButtonItem style="plain" systemItem="refresh" id="2fx-iS-Veb"> + <barButtonItem systemItem="refresh" id="2fx-iS-Veb"> <connections> <action selector="invalidatePacks:" destination="7q0-lI-zqb" id="5lx-FY-aTt"/> </connections> diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index de2bec5d99..018a5f9368 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -388,7 +388,7 @@ 9680274022653B84006BA4A1 /* MBXSKUToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 9680273E22653B84006BA4A1 /* MBXSKUToken.h */; }; 9680276422655696006BA4A1 /* libmbxaccounts.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9680274122653C3E006BA4A1 /* libmbxaccounts.a */; }; 96802766226556C5006BA4A1 /* libmbxaccounts.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9680274122653C3E006BA4A1 /* libmbxaccounts.a */; }; - 9686D1BD22D9357700194EA0 /* MGLMapViewZoomTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9686D1BC22D9357700194EA0 /* MGLMapViewZoomTests.m */; }; + 9686D1BD22D9357700194EA0 /* MGLMapViewZoomTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9686D1BC22D9357700194EA0 /* MGLMapViewZoomTests.mm */; }; 968F36B51E4D128D003A5522 /* MGLDistanceFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3557F7AE1E1D27D300CCA5E6 /* MGLDistanceFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 96E027231E57C76E004B8E66 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E027251E57C76E004B8E66 /* Localizable.strings */; }; 96E516DC2000547000A02306 /* MGLPolyline_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9654C1251FFC1AB900DB6A19 /* MGLPolyline_Private.h */; }; @@ -481,6 +481,8 @@ 9C6E284522A982670056B7BE /* MMEUINavigation.h in Headers */ = {isa = PBXBuildFile; fileRef = 406E99B31FFEFED600D9FFCC /* MMEUINavigation.h */; }; 9C6E284622A982670056B7BE /* MMEUniqueIdentifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 40834BBF1FE05D6E00C1BD0D /* MMEUniqueIdentifier.h */; }; 9C6E284722A982670056B7BE /* MMEDispatchManager.h in Headers */ = {isa = PBXBuildFile; fileRef = ACA65F552140696B00537748 /* MMEDispatchManager.h */; }; + A4DE3DCB23038C98005B3473 /* MGLMockGestureRecognizers.h in Sources */ = {isa = PBXBuildFile; fileRef = A4DE3DCA23038A7F005B3473 /* MGLMockGestureRecognizers.h */; }; + A4DE3DCC23038CCA005B3473 /* MGLMockGestureRecognizers.m in Sources */ = {isa = PBXBuildFile; fileRef = A4DE3DC823038A07005B3473 /* MGLMockGestureRecognizers.m */; }; A4F3FB1D2254865900A30170 /* missing_icon.json in Resources */ = {isa = PBXBuildFile; fileRef = A4F3FB1C2254865900A30170 /* missing_icon.json */; }; AC46EB59225E600A0039C013 /* MMECertPin.h in Headers */ = {isa = PBXBuildFile; fileRef = AC46EB57225E60090039C013 /* MMECertPin.h */; }; AC46EB5A225E600A0039C013 /* MMECertPin.h in Headers */ = {isa = PBXBuildFile; fileRef = AC46EB57225E60090039C013 /* MMECertPin.h */; }; @@ -506,11 +508,12 @@ CA1B4A512099FB2200EDD491 /* MGLMapSnapshotterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CA1B4A502099FB2200EDD491 /* MGLMapSnapshotterTest.m */; }; CA34C9C3207FD272005C1A06 /* MGLCameraTransitionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CA34C9C2207FD272005C1A06 /* MGLCameraTransitionTests.mm */; }; CA4EB8C720863487006AB465 /* MGLStyleLayerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CA4EB8C620863487006AB465 /* MGLStyleLayerIntegrationTests.m */; }; + CA4F3BDE230F74C3008BAFEA /* MGLMapViewPendingBlockTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CA4F3BDD230F74C3008BAFEA /* MGLMapViewPendingBlockTests.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, ); }; }; 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 */; }; @@ -1129,7 +1132,7 @@ 967C864A210A9D3C004DF794 /* UIDevice+MGLAdditions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIDevice+MGLAdditions.m"; sourceTree = "<group>"; }; 9680273E22653B84006BA4A1 /* MBXSKUToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MBXSKUToken.h; path = "../vendor/mapbox-accounts-ios/MBXSKUToken.h"; sourceTree = "<group>"; }; 9680274122653C3E006BA4A1 /* libmbxaccounts.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmbxaccounts.a; path = "vendor/mapbox-accounts-ios/libmbxaccounts.a"; sourceTree = SOURCE_ROOT; }; - 9686D1BC22D9357700194EA0 /* MGLMapViewZoomTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewZoomTests.m; sourceTree = "<group>"; }; + 9686D1BC22D9357700194EA0 /* MGLMapViewZoomTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapViewZoomTests.mm; sourceTree = "<group>"; }; 968F36B41E4D0FC6003A5522 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; }; 96E027241E57C76E004B8E66 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; }; 96E027271E57C77A004B8E66 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; }; @@ -1173,6 +1176,8 @@ 9C6E286522A9849E0056B7BE /* release-notes-jazzy.md.ejs */ = {isa = PBXFileReference; lastKnownFileType = text; path = "release-notes-jazzy.md.ejs"; sourceTree = "<group>"; }; 9C6E286622A9849E0056B7BE /* deploy-snapshot.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "deploy-snapshot.sh"; sourceTree = "<group>"; }; 9C6E286722A9849E0056B7BE /* release-notes.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "release-notes.js"; sourceTree = "<group>"; }; + A4DE3DC823038A07005B3473 /* MGLMockGestureRecognizers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLMockGestureRecognizers.m; sourceTree = "<group>"; }; + A4DE3DCA23038A7F005B3473 /* MGLMockGestureRecognizers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMockGestureRecognizers.h; sourceTree = "<group>"; }; A4F3FB1C2254865900A30170 /* missing_icon.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = missing_icon.json; sourceTree = "<group>"; }; AC46EB57225E60090039C013 /* MMECertPin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMECertPin.h; sourceTree = "<group>"; }; AC46EB58225E60090039C013 /* MMECertPin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMECertPin.m; sourceTree = "<group>"; }; @@ -1194,10 +1199,11 @@ CA1B4A502099FB2200EDD491 /* MGLMapSnapshotterTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapSnapshotterTest.m; sourceTree = "<group>"; }; CA34C9C2207FD272005C1A06 /* MGLCameraTransitionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLCameraTransitionTests.mm; sourceTree = "<group>"; }; CA4EB8C620863487006AB465 /* MGLStyleLayerIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLStyleLayerIntegrationTests.m; sourceTree = "<group>"; }; + CA4F3BDD230F74C3008BAFEA /* MGLMapViewPendingBlockTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewPendingBlockTests.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>"; }; 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>"; }; + CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLAnnotationViewIntegrationTests.mm; path = "Annotation Tests/MGLAnnotationViewIntegrationTests.mm"; sourceTree = "<group>"; }; CA86FF0D22D8D5A0009EB14A /* MGLNetworkConfigurationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLNetworkConfigurationTests.m; 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>"; }; @@ -1550,6 +1556,7 @@ CA88DC2F21C85D900059ED5A /* MGLStyleURLIntegrationTest.m */, 077061DB215DA11F000FEF62 /* MGLTestLocationManager.h */, 077061D9215DA00E000FEF62 /* MGLTestLocationManager.m */, + CA4F3BDD230F74C3008BAFEA /* MGLMapViewPendingBlockTests.m */, ); path = "Integration Tests"; sourceTree = "<group>"; @@ -1744,6 +1751,8 @@ children = ( CA5E5042209BDC5F001A8A81 /* MGLTestUtility.h */, 4031ACFE1E9FD29F00A3EA26 /* MGLSDKTestHelpers.swift */, + A4DE3DCA23038A7F005B3473 /* MGLMockGestureRecognizers.h */, + A4DE3DC823038A07005B3473 /* MGLMockGestureRecognizers.m */, ); name = "Test Helpers"; sourceTree = "<group>"; @@ -1920,7 +1929,7 @@ CA6914B320E67F07002DB0EE /* Annotations */ = { isa = PBXGroup; children = ( - CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m */, + CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.mm */, ); name = Annotations; sourceTree = "<group>"; @@ -2057,7 +2066,7 @@ 96381C0122C6F3950053497D /* MGLMapViewPitchTests.m */, 9658C154204761FC00D8A674 /* MGLMapViewScaleBarTests.m */, 076171C22139C70900668A35 /* MGLMapViewTests.m */, - 9686D1BC22D9357700194EA0 /* MGLMapViewZoomTests.m */, + 9686D1BC22D9357700194EA0 /* MGLMapViewZoomTests.mm */, 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */, 96036A0520059BBA00510F3D /* MGLNSOrthographyAdditionsTests.m */, DAE7DEC11E245455007505A6 /* MGLNSStringAdditionsTests.m */, @@ -3190,13 +3199,14 @@ CA7766842229C11A0008DE9E /* SMCalloutView.m in Sources */, CA34C9C3207FD272005C1A06 /* MGLCameraTransitionTests.mm in Sources */, 16376B0A1FFD9DAF0000563E /* MBGLIntegrationTests.mm in Sources */, + CA4F3BDE230F74C3008BAFEA /* MGLMapViewPendingBlockTests.m in Sources */, CA88DC3021C85D900059ED5A /* MGLStyleURLIntegrationTest.m in Sources */, CA0C27942076CA19001CE5B7 /* MGLMapViewIntegrationTest.m in Sources */, CA7766832229C10E0008DE9E /* MGLCompactCalloutView.m in Sources */, 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; @@ -3236,6 +3246,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A4DE3DCC23038CCA005B3473 /* MGLMockGestureRecognizers.m in Sources */, + A4DE3DCB23038C98005B3473 /* MGLMockGestureRecognizers.h in Sources */, 6407D6701E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift in Sources */, DA2E88631CC0382C00F24E7B /* MGLOfflineRegionTests.m in Sources */, 409F43FD1E9E781C0048729D /* MGLMapViewDelegateIntegrationTests.swift in Sources */, @@ -3273,7 +3285,7 @@ 3575798B1D502B0C000B822E /* MGLBackgroundStyleLayerTests.mm in Sources */, 9658C155204761FC00D8A674 /* MGLMapViewScaleBarTests.m in Sources */, 409D0A0D1ED614CE00C95D0C /* MGLAnnotationViewIntegrationTests.swift in Sources */, - 9686D1BD22D9357700194EA0 /* MGLMapViewZoomTests.m in Sources */, + 9686D1BD22D9357700194EA0 /* MGLMapViewZoomTests.mm in Sources */, DA2E88621CC0382C00F24E7B /* MGLOfflinePackTests.m in Sources */, 55E2AD131E5B125400E8C587 /* MGLOfflineStorageTests.mm in Sources */, 07D8C6FF1F67562C00381808 /* MGLComputedShapeSourceTests.m in Sources */, diff --git a/platform/ios/scripts/deploy-packages.sh b/platform/ios/scripts/deploy-packages.sh index a0efbd622e..b33a25df86 100755 --- a/platform/ios/scripts/deploy-packages.sh +++ b/platform/ios/scripts/deploy-packages.sh @@ -7,12 +7,10 @@ set -u # dynamic environment variables: # VERSION_TAG={determined automatically}: Version tag in format ios-vX.X.X-pre.X # GITHUB_RELEASE=true: Upload to github -# BINARY_DIRECTORY=build/ios/deploy: Directory in which to save test packages # environment variables and dependencies: # - You must run "mbx auth ..." before running # - Set GITHUB_TOKEN to a GitHub API access token in your environment to use GITHUB_RELEASE -# - "wget" is required for downloading the zip files from s3 # - The "github-release" command is required to use GITHUB_RELEASE function step { >&2 echo -e "\033[1m\033[36m* $@\033[0m"; } @@ -36,8 +34,6 @@ buildPackageStyle() { ./platform/ios/scripts/publish.sh "${PUBLISH_VERSION}" ${style} file_name=mapbox-ios-sdk-${PUBLISH_VERSION}-${style}.zip fi - step "Downloading ${file_name} from s3 to ${BINARY_DIRECTORY}" - wget -O ${BINARY_DIRECTORY}/${file_name} http://mapbox.s3.amazonaws.com/mapbox-gl-native/ios/builds/${file_name} if [[ "${GITHUB_RELEASE}" == true ]]; then step "Uploading ${file_name} to GitHub" github-release upload \ @@ -54,7 +50,7 @@ export BUILDTYPE=Release VERSION_TAG=${VERSION_TAG:-''} PUBLISH_VERSION= -BINARY_DIRECTORY=${BINARY_DIRECTORY:-build/ios/deploy} +BINARY_DIRECTORY='build/ios' GITHUB_RELEASE=${GITHUB_RELEASE:-true} PUBLISH_PRE_FLAG='' diff --git a/platform/ios/scripts/install-packaging-dependencies.sh b/platform/ios/scripts/install-packaging-dependencies.sh index 3d87a93f20..3d7f2fa4ea 100755 --- a/platform/ios/scripts/install-packaging-dependencies.sh +++ b/platform/ios/scripts/install-packaging-dependencies.sh @@ -22,15 +22,6 @@ else fi ## -## wget -## -if [ -z `which wget` ]; then - brew install wget -else - echo "Found brew" -fi - -## ## cocoapods ## if [[ -z `which pod` || $(pod --version) != "${COCOAPODS_VERSION}" ]]; then diff --git a/platform/ios/scripts/publish.sh b/platform/ios/scripts/publish.sh index b4e6f30257..1a4652a509 100755 --- a/platform/ios/scripts/publish.sh +++ b/platform/ios/scripts/publish.sh @@ -24,18 +24,19 @@ GITHUB_REPO=${GITHUB_REPO:-'mapbox-gl-native'} # zip # cd build/ios/pkg -SNAPSHOT_FILENAME="mapbox-ios-sdk-${PUBLISH_VERSION}${PUBLISH_STYLE}.zip" -step "Compressing ${SNAPSHOT_FILENAME}…" -rm -f ../${SNAPSHOT_FILENAME} -zip -yr ../${SNAPSHOT_FILENAME} * +ZIP_FILENAME="mapbox-ios-sdk-${PUBLISH_VERSION}${PUBLISH_STYLE}.zip" +step "Compressing ${ZIP_FILENAME}…" +rm -f ../${ZIP_FILENAME} +zip -yr ../${ZIP_FILENAME} * +cd .. # # report file sizes # step "Echoing file sizes…" -du -sh ../${SNAPSHOT_FILENAME} -du -sch * -du -sch dynamic/* +du -sh ${ZIP_FILENAME} +du -sch pkg/* +du -sch pkg/dynamic/* # # upload @@ -45,17 +46,34 @@ if [ -n "${CI:-}" ]; then PROGRESS="--no-progress" fi -step "Uploading ${SNAPSHOT_FILENAME} to s3…" -aws s3 cp ../${SNAPSHOT_FILENAME} s3://mapbox/$GITHUB_REPO/ios/builds/ --acl public-read ${PROGRESS} -echo "URL: https://mapbox.s3.amazonaws.com/$GITHUB_REPO/ios/builds/${SNAPSHOT_FILENAME}" +step "Uploading ${ZIP_FILENAME} to s3…" +aws s3 cp ${ZIP_FILENAME} s3://mapbox/${GITHUB_REPO}/ios/builds/ --acl public-read ${PROGRESS} +S3_URL=https://mapbox.s3.amazonaws.com/${GITHUB_REPO}/ios/builds/${ZIP_FILENAME} +echo "URL: ${S3_URL}" # # upload & update snapshot # if [[ ${PUBLISH_VERSION} =~ "snapshot" ]]; then step "Updating ${PUBLISH_VERSION} to ${PUBLISH_STYLE}…" - GENERIC_SNAPSHOT_FILENAME="mapbox-ios-sdk-${PUBLISH_VERSION}.zip" + GENERIC_ZIP_FILENAME="mapbox-ios-sdk-${PUBLISH_VERSION}.zip" aws s3 cp \ - s3://mapbox/$GITHUB_REPO/ios/builds/${SNAPSHOT_FILENAME} \ - s3://mapbox/$GITHUB_REPO/ios/builds/${GENERIC_SNAPSHOT_FILENAME} --acl public-read ${PROGRESS} + s3://mapbox/$GITHUB_REPO/ios/builds/${ZIP_FILENAME} \ + s3://mapbox/$GITHUB_REPO/ios/builds/${GENERIC_ZIP_FILENAME} --acl public-read ${PROGRESS} +fi + +# +# verify upload integrity +# + +step "Validating local and remote checksums…" +curl --output remote-${ZIP_FILENAME} ${S3_URL} +LOCAL_CHECKSUM=$( shasum -a 256 -b ${ZIP_FILENAME} | cut -d ' ' -f 1 ) +REMOTE_CHECKSUM=$( shasum -a 256 -b remote-${ZIP_FILENAME} | cut -d ' ' -f 1 ) + +if [ "${LOCAL_CHECKSUM}" == "${REMOTE_CHECKSUM}" ]; then + echo "Checksums match: ${LOCAL_CHECKSUM}" +else + echo "Checksums did not match: ${LOCAL_CHECKSUM} != ${REMOTE_CHECKSUM}" + exit 1 fi diff --git a/platform/ios/src/MGLMapView+Impl.h b/platform/ios/src/MGLMapView+Impl.h index 0a62b7da82..66dc408274 100644 --- a/platform/ios/src/MGLMapView+Impl.h +++ b/platform/ios/src/MGLMapView+Impl.h @@ -62,7 +62,7 @@ public: void onDidFinishLoadingMap() override; void onDidFailLoadingMap(mbgl::MapLoadError mapError, const std::string& what) override; void onWillStartRenderingFrame() override; - void onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode) override; + void onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode, bool) override; void onWillStartRenderingMap() override; void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode) override; void onDidFinishLoadingStyle() override; diff --git a/platform/ios/src/MGLMapView+Impl.mm b/platform/ios/src/MGLMapView+Impl.mm index 1bccfa662f..76c9c0f9ba 100644 --- a/platform/ios/src/MGLMapView+Impl.mm +++ b/platform/ios/src/MGLMapView+Impl.mm @@ -68,7 +68,7 @@ void MGLMapViewImpl::onWillStartRenderingFrame() { [mapView mapViewWillStartRenderingFrame]; } -void MGLMapViewImpl::onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode mode) { +void MGLMapViewImpl::onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode mode, bool) { bool fullyRendered = mode == mbgl::MapObserver::RenderMode::Full; [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered]; } diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h index e6f66e39ae..6d9b30e467 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -748,7 +748,8 @@ MGL_EXPORT Changes the center coordinate of the map and optionally animates the change. Changing the center coordinate centers the map on the new coordinate without - changing the current zoom level. + changing the current zoom level. For animated changes, wait until the map view has + finished loading before calling this method. @param coordinate The new center coordinate for the map. @param animated Specify `YES` if you want the map view to scroll to the new @@ -762,7 +763,8 @@ MGL_EXPORT /** Changes the center coordinate and zoom level of the map and optionally animates - the change. + the change. For animated changes, wait until the map view has + finished loading before calling this method. @param centerCoordinate The new center coordinate for the map. @param zoomLevel The new zoom level for the map. @@ -777,7 +779,8 @@ MGL_EXPORT /** Changes the center coordinate, zoom level, and direction of the map and - optionally animates the change. + optionally animates the change. For animated changes, wait until the map view has + finished loading before calling this method. @param centerCoordinate The new center coordinate for the map. @param zoomLevel The new zoom level for the map. @@ -794,7 +797,8 @@ MGL_EXPORT /** Changes the center coordinate, zoom level, and direction of the map, calling a - completion handler at the end of an optional animation. + completion handler at the end of an optional animation. For animated changes, + wait until the map view has finished loading before calling this method. @param centerCoordinate The new center coordinate for the map. @param zoomLevel The new zoom level for the map. @@ -1065,7 +1069,8 @@ MGL_EXPORT /** Moves the viewpoint to a different location with respect to the map with an - optional transition animation. + optional transition animation. For animated changes, wait until the map view has + finished loading before calling this method. @param camera The new viewpoint. @param animated Specify `YES` if you want the map view to animate the change to @@ -1081,7 +1086,8 @@ MGL_EXPORT /** Moves the viewpoint to a different location with respect to the map with an - optional transition duration and timing function. + optional transition duration and timing function. For animated changes, wait + until the map view has finished loading before calling this method. @param camera The new viewpoint. @param duration The amount of time, measured in seconds, that the transition @@ -1100,7 +1106,8 @@ MGL_EXPORT /** Moves the viewpoint to a different location with respect to the map with an - optional transition duration and timing function. + optional transition duration and timing function. For animated changes, wait + until the map view has finished loading before calling this method. @param camera The new viewpoint. @param duration The amount of time, measured in seconds, that the transition @@ -1116,7 +1123,8 @@ MGL_EXPORT /** Moves the viewpoint to a different location with respect to the map with an optional transition duration and timing function, and optionally some additional - padding on each side. + padding on each side. For animated changes, wait until the map view has + finished loading before calling this method. @param camera The new viewpoint. @param duration The amount of time, measured in seconds, that the transition diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index d77f94d8ba..2eafea1dd9 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -128,6 +128,9 @@ const CLLocationDirection MGLToleranceForSnappingToNorth = 7; /// Distance threshold to stop the camera while animating. const CLLocationDistance MGLDistanceThresholdForCameraPause = 500; +/// Rotation threshold while a pinch gesture is occurring. +static NSString * const MGLRotationThresholdWhileZoomingKey = @"MGLRotationThresholdWhileZooming"; + /// Reuse identifier and file name of the default point annotation image. static NSString * const MGLDefaultStyleMarkerSymbolName = @"default_marker"; @@ -154,6 +157,9 @@ static const NSUInteger MGLPresentsWithTransactionAnnotationCount = 0; /// An indication that the requested annotation was not found or is nonexistent. enum { MGLAnnotationTagNotFound = UINT32_MAX }; +/// The threshold used to consider when a tilt gesture should start. +const CLLocationDegrees MGLHorizontalTiltToleranceDegrees = 45.0; + /// Mapping from an annotation tag to metadata about that annotation, including /// the annotation itself. typedef std::unordered_map<MGLAnnotationTag, MGLAnnotationContext> MGLAnnotationTagContextMap; @@ -240,6 +246,10 @@ public: @property (nonatomic) CGFloat quickZoomStart; @property (nonatomic, getter=isDormant) BOOL dormant; @property (nonatomic, readonly, getter=isRotationAllowed) BOOL rotationAllowed; +@property (nonatomic) CGFloat rotationThresholdWhileZooming; +@property (nonatomic) CGFloat rotationBeforeThresholdMet; +@property (nonatomic) BOOL isZooming; +@property (nonatomic) BOOL isRotating; @property (nonatomic) BOOL shouldTriggerHapticFeedbackForCompass; @property (nonatomic) MGLMapViewProxyAccessibilityElement *mapViewProxyAccessibilityElement; @property (nonatomic) MGLAnnotationContainerView *annotationContainerView; @@ -247,6 +257,7 @@ public: @property (nonatomic) NSMutableDictionary<NSString *, NSMutableArray<MGLAnnotationView *> *> *annotationViewReuseQueueByIdentifier; @property (nonatomic, readonly) BOOL enablePresentsWithTransaction; @property (nonatomic) UIImage *lastSnapshotImage; +@property (nonatomic) NSMutableArray *pendingCompletionBlocks; /// Experimental rendering performance measurement. @property (nonatomic) BOOL experimental_enableFrameRateMeasurement; @@ -260,6 +271,9 @@ public: @property (nonatomic) MGLMapDebugMaskOptions residualDebugMask; @property (nonatomic, copy) NSURL *residualStyleURL; +/// Tilt gesture recognizer helper +@property (nonatomic, assign) CGPoint dragGestureMiddlePoint; + - (mbgl::Map &)mbglMap; @end @@ -457,7 +471,7 @@ public: // setup mbgl map MGLRendererConfiguration *config = [MGLRendererConfiguration currentConfiguration]; - auto renderer = std::make_unique<mbgl::Renderer>(_mbglView->getRendererBackend(), config.scaleFactor, config.cacheDir, config.localFontFamilyName); + auto renderer = std::make_unique<mbgl::Renderer>(_mbglView->getRendererBackend(), config.scaleFactor, config.localFontFamilyName); BOOL enableCrossSourceCollisions = !config.perSourceCollisions; _rendererFrontend = std::make_unique<MGLRenderFrontend>(std::move(renderer), self, _mbglView->getRendererBackend()); @@ -612,6 +626,11 @@ public: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; + + // Pending completion blocks are called *after* annotation views have been updated + // in updateFromDisplayLink. + _pendingCompletionBlocks = [NSMutableArray array]; + // As of 3.7.5, we intentionally do not listen for `UIApplicationWillResignActiveNotification` or call `pauseRendering:` in response to it, as doing // so causes a loop when asking for location permission. See: https://github.com/mapbox/mapbox-gl-native/issues/11225 @@ -1033,6 +1052,34 @@ public: return CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame)); } +#pragma mark - Pending completion blocks + +- (void)processPendingBlocks +{ + NSArray *blocks = self.pendingCompletionBlocks; + self.pendingCompletionBlocks = [NSMutableArray array]; + + for (dispatch_block_t block in blocks) + { + block(); + } +} + +- (BOOL)scheduleTransitionCompletion:(dispatch_block_t)block +{ + // Only add a block if the display link (that calls processPendingBlocks) is + // running, otherwise fall back to calling immediately. + if (_displayLink && !_displayLink.isPaused) + { + [self willChangeValueForKey:@"pendingCompletionBlocks"]; + [self.pendingCompletionBlocks addObject:block]; + [self didChangeValueForKey:@"pendingCompletionBlocks"]; + return YES; + } + + return NO; +} + #pragma mark - Life Cycle - - (void)updateFromDisplayLink:(CADisplayLink *)displayLink @@ -1057,7 +1104,7 @@ public: return; } - if (_needsDisplayRefresh) + if (_needsDisplayRefresh || (self.pendingCompletionBlocks.count > 0)) { _needsDisplayRefresh = NO; @@ -1066,6 +1113,13 @@ public: [self updateAnnotationViews]; [self updateCalloutView]; + // Call any pending completion blocks. This is primarily to ensure + // that annotations are in the expected position after core rendering + // and map update. + // + // TODO: Consider using this same mechanism for delegate callbacks. + [self processPendingBlocks]; + _mbglView->display(); } @@ -1132,6 +1186,7 @@ public: { [_displayLink invalidate]; _displayLink = nil; + [self processPendingBlocks]; } } @@ -1415,6 +1470,7 @@ public: [MGLMapboxEvents flush]; _displayLink.paused = YES; + [self processPendingBlocks]; if ( ! self.glSnapshotView) { @@ -1467,6 +1523,11 @@ public: { super.hidden = hidden; _displayLink.paused = hidden; + + if (hidden) + { + [self processPendingBlocks]; + } } - (void)tintColorDidChange @@ -1622,6 +1683,9 @@ public: { self.scale = powf(2, [self zoomLevel]); + if (abs(pinch.velocity) > abs(self.rotate.velocity)) { + self.isZooming = YES; + } [self notifyGestureDidBegin]; } else if (pinch.state == UIGestureRecognizerStateChanged) @@ -1697,6 +1761,7 @@ public: } } + self.isZooming = NO; [self notifyGestureDidEndWithDrift:drift]; [self unrotateIfNeededForGesture]; } @@ -1709,6 +1774,106 @@ public: { if ( ! self.isRotateEnabled) return; + if ([[NSUserDefaults standardUserDefaults] objectForKey:MGLRotationThresholdWhileZoomingKey]) { + [self handleRotateGestureRecognizerWithThreshold:rotate]; + } else { + [self cancelTransitions]; + + CGPoint centerPoint = [self anchorPointForGesture:rotate]; + MGLMapCamera *oldCamera = self.camera; + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureRotate; + + if (rotate.state == UIGestureRecognizerStateBegan) + { + self.angle = MGLRadiansFromDegrees(*self.mbglMap.getCameraOptions().bearing) * -1; + + self.isRotating = YES; + if (self.userTrackingMode != MGLUserTrackingModeNone) + { + self.userTrackingMode = MGLUserTrackingModeFollow; + } + + self.shouldTriggerHapticFeedbackForCompass = NO; + [self notifyGestureDidBegin]; + } + if (rotate.state == UIGestureRecognizerStateChanged) + { + CGFloat newDegrees = MGLDegreesFromRadians(self.angle + rotate.rotation) * -1; + + // constrain to +/-30 degrees when merely rotating like Apple does + // + if ( ! self.isRotationAllowed && std::abs(self.pinch.scale) < 10) + { + newDegrees = fminf(newDegrees, 30); + newDegrees = fmaxf(newDegrees, -30); + } + + MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint]; + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) + { + self.mbglMap.jumpTo(mbgl::CameraOptions() + .withBearing(newDegrees) + .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y})); + } + + [self cameraIsChanging]; + + // Trigger a light haptic feedback event when the user rotates to due north. + if (@available(iOS 10.0, *)) + { + if (self.isHapticFeedbackEnabled && fabs(newDegrees) <= 1 && self.shouldTriggerHapticFeedbackForCompass) + { + UIImpactFeedbackGenerator *hapticFeedback = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; + [hapticFeedback impactOccurred]; + + self.shouldTriggerHapticFeedbackForCompass = NO; + } + else if (fabs(newDegrees) > 1) + { + self.shouldTriggerHapticFeedbackForCompass = YES; + } + } + } + else if ((rotate.state == UIGestureRecognizerStateEnded || rotate.state == UIGestureRecognizerStateCancelled)) + { + CGFloat velocity = rotate.velocity; + CGFloat decelerationRate = self.decelerationRate; + if (decelerationRate != MGLMapViewDecelerationRateImmediate && fabs(velocity) > 3) + { + CGFloat radians = self.angle + rotate.rotation; + CGFloat newRadians = radians + velocity * decelerationRate * 0.1; + CGFloat newDegrees = MGLDegreesFromRadians(newRadians) * -1; + + MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint]; + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) + { + self.mbglMap.easeTo(mbgl::CameraOptions() + .withBearing(newDegrees) + .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }), + MGLDurationFromTimeInterval(decelerationRate)); + + [self notifyGestureDidEndWithDrift:YES]; + __weak MGLMapView *weakSelf = self; + + [self animateWithDelay:decelerationRate animations:^ + { + [weakSelf unrotateIfNeededForGesture]; + }]; + } + } + else + { + [self notifyGestureDidEndWithDrift:NO]; + [self unrotateIfNeededForGesture]; + } + } + } +} + +- (void)handleRotateGestureRecognizerWithThreshold:(UIRotationGestureRecognizer *)rotate { [self cancelTransitions]; CGPoint centerPoint = [self anchorPointForGesture:rotate]; @@ -1716,20 +1881,29 @@ public: self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureRotate; - if (rotate.state == UIGestureRecognizerStateBegan) + _rotationThresholdWhileZooming = [[[NSUserDefaults standardUserDefaults] objectForKey:MGLRotationThresholdWhileZoomingKey] floatValue]; + + // Check whether a zoom triggered by a pinch gesture is occurring and if the rotation threshold has been met. + if (MGLDegreesFromRadians(self.rotationBeforeThresholdMet) < self.rotationThresholdWhileZooming && self.isZooming && !self.isRotating) { + self.rotationBeforeThresholdMet += fabs(rotate.rotation); + rotate.rotation = 0; + return; + } + + if (rotate.state == UIGestureRecognizerStateBegan || ! self.isRotating) { self.angle = MGLRadiansFromDegrees(*self.mbglMap.getCameraOptions().bearing) * -1; + self.isRotating = YES; if (self.userTrackingMode != MGLUserTrackingModeNone) { self.userTrackingMode = MGLUserTrackingModeFollow; } self.shouldTriggerHapticFeedbackForCompass = NO; - [self notifyGestureDidBegin]; } - else if (rotate.state == UIGestureRecognizerStateChanged) + if (rotate.state == UIGestureRecognizerStateChanged) { CGFloat newDegrees = MGLDegreesFromRadians(self.angle + rotate.rotation) * -1; @@ -1740,14 +1914,14 @@ public: newDegrees = fminf(newDegrees, 30); newDegrees = fmaxf(newDegrees, -30); } - + MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint]; if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { - self.mbglMap.jumpTo(mbgl::CameraOptions() - .withBearing(newDegrees) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y})); + self.mbglMap.jumpTo(mbgl::CameraOptions() + .withBearing(newDegrees) + .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y})); } [self cameraIsChanging]; @@ -1768,8 +1942,12 @@ public: } } } - else if (rotate.state == UIGestureRecognizerStateEnded || rotate.state == UIGestureRecognizerStateCancelled) + else if ((rotate.state == UIGestureRecognizerStateEnded || rotate.state == UIGestureRecognizerStateCancelled)) { + self.rotationBeforeThresholdMet = 0; + if (! self.isRotating) { return; } + self.isRotating = NO; + CGFloat velocity = rotate.velocity; CGFloat decelerationRate = self.decelerationRate; if (decelerationRate != MGLMapViewDecelerationRateImmediate && fabs(velocity) > 3) @@ -1783,14 +1961,13 @@ public: if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { self.mbglMap.easeTo(mbgl::CameraOptions() - .withBearing(newDegrees) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }), + .withBearing(newDegrees) + .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }), MGLDurationFromTimeInterval(decelerationRate)); [self notifyGestureDidEndWithDrift:YES]; - __weak MGLMapView *weakSelf = self; - + [self animateWithDelay:decelerationRate animations:^ { [weakSelf unrotateIfNeededForGesture]; @@ -2016,6 +2193,14 @@ public: if (twoFingerDrag.state == UIGestureRecognizerStateBegan) { + CGPoint midPoint = [twoFingerDrag translationInView:twoFingerDrag.view]; + // In the following if and for the first execution middlePoint + // will be equal to dragGestureMiddlePoint and the resulting + // gestureSlopeAngle will be 0º causing a small delay, + // initializing dragGestureMiddlePoint with the current midPoint + // but substracting one point from 'y' forces an initial 90º angle + // making the gesture avoid the delay + self.dragGestureMiddlePoint = CGPointMake(midPoint.x, midPoint.y-1); initialPitch = *self.mbglMap.getCameraOptions().pitch; [self notifyGestureDidBegin]; } @@ -2027,30 +2212,45 @@ public: twoFingerDrag.state = UIGestureRecognizerStateEnded; return; } - - CGFloat gestureDistance = CGPoint([twoFingerDrag translationInView:twoFingerDrag.view]).y; - CGFloat slowdown = 2.0; - - CGFloat pitchNew = initialPitch - (gestureDistance / slowdown); - - CGPoint centerPoint = [self anchorPointForGesture:twoFingerDrag]; - - MGLMapCamera *oldCamera = self.camera; - MGLMapCamera *toCamera = [self cameraByTiltingToPitch:pitchNew]; - - if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) - { - self.mbglMap.jumpTo(mbgl::CameraOptions() + + CGPoint leftTouchPoint = [twoFingerDrag locationOfTouch:0 inView:twoFingerDrag.view]; + CGPoint rightTouchPoint = [twoFingerDrag locationOfTouch:1 inView:twoFingerDrag.view]; + CLLocationDegrees fingerSlopeAngle = [self angleBetweenPoints:leftTouchPoint endPoint:rightTouchPoint]; + + CGPoint middlePoint = [twoFingerDrag translationInView:twoFingerDrag.view]; + + CLLocationDegrees gestureSlopeAngle = [self angleBetweenPoints:self.dragGestureMiddlePoint endPoint:middlePoint]; + self.dragGestureMiddlePoint = middlePoint; + if (fabs(fingerSlopeAngle) < MGLHorizontalTiltToleranceDegrees && fabs(gestureSlopeAngle) > 60.0 ) { + + CGFloat gestureDistance = middlePoint.y; + CGFloat slowdown = 2.0; + + CGFloat pitchNew = initialPitch - (gestureDistance / slowdown); + + CGPoint centerPoint = [self anchorPointForGesture:twoFingerDrag]; + + MGLMapCamera *oldCamera = self.camera; + MGLMapCamera *toCamera = [self cameraByTiltingToPitch:pitchNew]; + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) + { + self.mbglMap.jumpTo(mbgl::CameraOptions() .withPitch(pitchNew) .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })); + } + + [self cameraIsChanging]; + } - [self cameraIsChanging]; + } else if (twoFingerDrag.state == UIGestureRecognizerStateEnded || twoFingerDrag.state == UIGestureRecognizerStateCancelled) { [self notifyGestureDidEndWithDrift:NO]; [self unrotateIfNeededForGesture]; + self.dragGestureMiddlePoint = CGPointZero; } } @@ -2173,23 +2373,17 @@ public: - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { - if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) + if (gestureRecognizer == _twoFingerDrag) { UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer; if (panGesture.minimumNumberOfTouches == 2) { - CGPoint west = [panGesture locationOfTouch:0 inView:panGesture.view]; - CGPoint east = [panGesture locationOfTouch:1 inView:panGesture.view]; - - if (west.x > east.x) { - CGPoint swap = west; - west = east; - east = swap; - } + CGPoint leftTouchPoint = [panGesture locationOfTouch:0 inView:panGesture.view]; + CGPoint rightTouchPoint = [panGesture locationOfTouch:1 inView:panGesture.view]; - CLLocationDegrees horizontalToleranceDegrees = 60.0; - if ([self angleBetweenPoints:west east:east] > horizontalToleranceDegrees) { + CLLocationDegrees degrees = [self angleBetweenPoints:leftTouchPoint endPoint:rightTouchPoint]; + if (fabs(degrees) > MGLHorizontalTiltToleranceDegrees) { return NO; } } @@ -2211,18 +2405,24 @@ public: - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { NSArray *validSimultaneousGestures = @[ self.pan, self.pinch, self.rotate ]; - return ([validSimultaneousGestures containsObject:gestureRecognizer] && [validSimultaneousGestures containsObject:otherGestureRecognizer]); } -- (CLLocationDegrees)angleBetweenPoints:(CGPoint)west east:(CGPoint)east +- (CLLocationDegrees)angleBetweenPoints:(CGPoint)originPoint endPoint:(CGPoint)endPoint { - CGFloat slope = (west.y - east.y) / (west.x - east.x); + if (originPoint.x > endPoint.x) { + CGPoint swap = originPoint; + originPoint = endPoint; + endPoint = swap; + } + + CGFloat x = (endPoint.x - originPoint.x); + CGFloat y = (endPoint.y - originPoint.y); - CGFloat angle = atan(fabs(slope)); - CLLocationDegrees degrees = MGLDegreesFromRadians(angle); + CGFloat angleInRadians = atan2(y, x); + CLLocationDegrees angleInDegrees = MGLDegreesFromRadians(angleInRadians); - return degrees; + return angleInDegrees; } #pragma mark - Attribution - @@ -2302,7 +2502,7 @@ public: NSString *message; NSString *participateTitle; NSString *declineTitle; - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"MGLMapboxMetricsEnabled"]) + if ([[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsEnabledKey]) { message = NSLocalizedStringWithDefaultValue(@"TELEMETRY_ENABLED_MSG", nil, nil, @"You are helping to make OpenStreetMap and Mapbox maps better by contributing anonymous usage data.", @"Telemetry prompt message"); participateTitle = NSLocalizedStringWithDefaultValue(@"TELEMETRY_ENABLED_ON", nil, nil, @"Keep Participating", @"Telemetry prompt button"); @@ -2330,14 +2530,14 @@ public: UIAlertAction *declineAction = [UIAlertAction actionWithTitle:declineTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"MGLMapboxMetricsEnabled"]; + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:MGLMapboxMetricsEnabledKey]; }]; [alertController addAction:declineAction]; UIAlertAction *participateAction = [UIAlertAction actionWithTitle:participateTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"MGLMapboxMetricsEnabled"]; + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:MGLMapboxMetricsEnabledKey]; }]; [alertController addAction:participateAction]; @@ -3188,26 +3388,35 @@ public: animationOptions.duration.emplace(MGLDurationFromTimeInterval(duration)); animationOptions.easing.emplace(MGLUnitBezierForMediaTimingFunction(function)); } + + dispatch_block_t pendingCompletion; + if (completion) { - animationOptions.transitionFinishFn = [completion]() { + __weak __typeof__(self) weakSelf = self; + + pendingCompletion = ^{ + if (![weakSelf scheduleTransitionCompletion:completion]) + { + completion(); + } + }; + + animationOptions.transitionFinishFn = [pendingCompletion]() { // Must run asynchronously after the transition is completely over. // Otherwise, a call to -setCenterCoordinate: within the completion // handler would reenter the completion handler’s caller. - dispatch_async(dispatch_get_main_queue(), ^{ - completion(); - }); + + dispatch_async(dispatch_get_main_queue(), pendingCompletion); }; } MGLMapCamera *camera = [self cameraForCameraOptions:cameraOptions]; if ([self.camera isEqualToMapCamera:camera] && UIEdgeInsetsEqualToEdgeInsets(_contentInset, insets)) { - if (completion) + if (pendingCompletion) { - [self animateWithDelay:duration animations:^{ - completion(); - }]; + [self animateWithDelay:duration animations:pendingCompletion]; } return; } @@ -3374,12 +3583,22 @@ public: animationOptions.duration.emplace(MGLDurationFromTimeInterval(duration)); animationOptions.easing.emplace(MGLUnitBezierForMediaTimingFunction(function)); } + + dispatch_block_t pendingCompletion; + if (completion) { - animationOptions.transitionFinishFn = [completion]() { - dispatch_async(dispatch_get_main_queue(), ^{ + __weak __typeof__(self) weakSelf = self; + + pendingCompletion = ^{ + if (![weakSelf scheduleTransitionCompletion:completion]) + { completion(); - }); + } + }; + + animationOptions.transitionFinishFn = [pendingCompletion]() { + dispatch_async(dispatch_get_main_queue(), pendingCompletion); }; } @@ -3389,11 +3608,9 @@ public: MGLMapCamera *camera = [self cameraForCameraOptions:cameraOptions]; if ([self.camera isEqualToMapCamera:camera]) { - if (completion) + if (pendingCompletion) { - [self animateWithDelay:duration animations:^{ - completion(); - }]; + [self animateWithDelay:duration animations:pendingCompletion]; } return; } @@ -3534,22 +3751,30 @@ public: animationOptions.duration.emplace(MGLDurationFromTimeInterval(duration)); animationOptions.easing.emplace(MGLUnitBezierForMediaTimingFunction(function)); } + + dispatch_block_t pendingCompletion; + if (completion) { - animationOptions.transitionFinishFn = [completion]() { - dispatch_async(dispatch_get_main_queue(), ^{ + __weak __typeof__(self) weakSelf = self; + + pendingCompletion = ^{ + if (![weakSelf scheduleTransitionCompletion:completion]) + { completion(); - }); + } + }; + + animationOptions.transitionFinishFn = [pendingCompletion]() { + dispatch_async(dispatch_get_main_queue(), pendingCompletion); }; } if ([self.camera isEqualToMapCamera:camera] && UIEdgeInsetsEqualToEdgeInsets(_contentInset, edgePadding)) { - if (completion) + if (pendingCompletion) { - [self animateWithDelay:duration animations:^{ - completion(); - }]; + [self animateWithDelay:duration animations:pendingCompletion]; } return; } @@ -3605,22 +3830,30 @@ public: animationOptions.minZoom = MGLZoomLevelForAltitude(peakAltitude, peakPitch, peakLatitude, self.frame.size); } + + dispatch_block_t pendingCompletion; + if (completion) { - animationOptions.transitionFinishFn = [completion]() { - dispatch_async(dispatch_get_main_queue(), ^{ + __weak __typeof__(self) weakSelf = self; + + pendingCompletion = ^{ + if (![weakSelf scheduleTransitionCompletion:completion]) + { completion(); - }); + } + }; + + animationOptions.transitionFinishFn = [pendingCompletion]() { + dispatch_async(dispatch_get_main_queue(), pendingCompletion); }; } if ([self.camera isEqualToMapCamera:camera] && UIEdgeInsetsEqualToEdgeInsets(_contentInset, insets)) { - if (completion) + if (pendingCompletion) { - [self animateWithDelay:duration animations:^{ - completion(); - }]; + [self animateWithDelay:duration animations:pendingCompletion]; } return; } diff --git a/platform/ios/src/MGLMapView_Private.h b/platform/ios/src/MGLMapView_Private.h index e53dc8519c..155527000f 100644 --- a/platform/ios/src/MGLMapView_Private.h +++ b/platform/ios/src/MGLMapView_Private.h @@ -52,8 +52,6 @@ FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const _Nonnull MGLUnderlyingMapUna /// Synchronously render a frame of the map. - (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 */ diff --git a/platform/ios/src/MGLMapboxEvents.h b/platform/ios/src/MGLMapboxEvents.h index cb3132656f..a7d316cc06 100644 --- a/platform/ios/src/MGLMapboxEvents.h +++ b/platform/ios/src/MGLMapboxEvents.h @@ -3,6 +3,9 @@ NS_ASSUME_NONNULL_BEGIN +/// NSUserDefaults key that controls telemetry user opt-out status +FOUNDATION_EXTERN NSString * const MGLMapboxMetricsEnabledKey; + @interface MGLMapboxEvents : NSObject + (nullable instancetype)sharedInstance; diff --git a/platform/ios/src/MGLMapboxEvents.m b/platform/ios/src/MGLMapboxEvents.m index cc7390ac61..808c3a88bf 100644 --- a/platform/ios/src/MGLMapboxEvents.m +++ b/platform/ios/src/MGLMapboxEvents.m @@ -3,14 +3,16 @@ #import "NSBundle+MGLAdditions.h" #import "MGLAccountManager_Private.h" +// NSUserDefaults and Info.plist keys +NSString * const MGLMapboxMetricsEnabledKey = @"MGLMapboxMetricsEnabled"; +static NSString * const MGLMapboxMetricsDebugLoggingEnabledKey = @"MGLMapboxMetricsDebugLoggingEnabled"; +static NSString * const MGLMapboxMetricsEnabledSettingShownInAppKey = @"MGLMapboxMetricsEnabledSettingShownInApp"; +static NSString * const MGLTelemetryAccessTokenKey = @"MGLTelemetryAccessToken"; +static NSString * const MGLTelemetryBaseURLKey = @"MGLTelemetryBaseURL"; +static NSString * const MGLEventsProfileKey = @"MMEEventsProfile"; +static NSString * const MGLVariableGeofenceKey = @"VariableGeofence"; + static NSString * const MGLAPIClientUserAgentBase = @"mapbox-maps-ios"; -static NSString * const MGLMapboxAccountType = @"MGLMapboxAccountType"; -static NSString * const MGLMapboxMetricsEnabled = @"MGLMapboxMetricsEnabled"; -static NSString * const MGLMapboxMetricsDebugLoggingEnabled = @"MGLMapboxMetricsDebugLoggingEnabled"; -static NSString * const MGLTelemetryAccessToken = @"MGLTelemetryAccessToken"; -static NSString * const MGLTelemetryBaseURL = @"MGLTelemetryBaseURL"; -static NSString * const MGLEventsProfile = @"MMEEventsProfile"; -static NSString * const MGLVariableGeofence = @"VariableGeofence"; @interface MGLMapboxEvents () @@ -19,16 +21,16 @@ static NSString * const MGLVariableGeofence = @"VariableGeofence"; @property (nonatomic, copy) NSString *accessToken; @end -@implementation MGLMapboxEvents +@implementation MGLMapboxEvents + (void)initialize { if (self == [MGLMapboxEvents class]) { NSBundle *bundle = [NSBundle mainBundle]; - NSNumber *accountTypeNumber = [bundle objectForInfoDictionaryKey:MGLMapboxAccountType]; - [[NSUserDefaults standardUserDefaults] registerDefaults:@{MGLMapboxAccountType: accountTypeNumber ?: @0, - MGLMapboxMetricsEnabled: @YES, - MGLMapboxMetricsDebugLoggingEnabled: @NO}]; + NSNumber *accountTypeNumber = [bundle objectForInfoDictionaryKey:MGLMapboxAccountTypeKey]; + [[NSUserDefaults standardUserDefaults] registerDefaults:@{MGLMapboxAccountTypeKey: accountTypeNumber ?: @0, + MGLMapboxMetricsEnabledKey: @YES, + MGLMapboxMetricsDebugLoggingEnabledKey: @NO}]; } } @@ -46,9 +48,9 @@ static NSString * const MGLVariableGeofence = @"VariableGeofence"; self = [super init]; if (self) { _eventsManager = MMEEventsManager.sharedManager; - _eventsManager.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsDebugLoggingEnabled]; - _eventsManager.accountType = [[NSUserDefaults standardUserDefaults] integerForKey:MGLMapboxAccountType]; - _eventsManager.metricsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsEnabled]; + _eventsManager.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsDebugLoggingEnabledKey]; + _eventsManager.accountType = [[NSUserDefaults standardUserDefaults] integerForKey:MGLMapboxAccountTypeKey]; + _eventsManager.metricsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsEnabledKey]; // It is possible for the shared instance of this class to be created because of a call to // +[MGLAccountManager load] early on in the app lifecycle of the host application. @@ -57,11 +59,11 @@ static NSString * const MGLVariableGeofence = @"VariableGeofence"; // (once -[MMEEventsManager initializeWithAccessToken:userAgentBase:hostSDKVersion:] is called. // Normally, the telem access token and base URL are not set this way. However, overriding these values // with user defaults can be useful for testing with an alternative (test) backend system. - if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryAccessToken]) { - self.accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryAccessToken]; + if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryAccessTokenKey]) { + self.accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryAccessTokenKey]; } - if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryBaseURL]) { - self.baseURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryBaseURL]]; + if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryBaseURLKey]) { + self.baseURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryBaseURLKey]]; } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChange:) name:NSUserDefaultsDidChangeNotification object:nil]; @@ -81,22 +83,22 @@ static NSString * const MGLVariableGeofence = @"VariableGeofence"; } - (void)updateNonDisablingConfigurationValues { - self.eventsManager.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"MGLMapboxMetricsDebugLoggingEnabled"]; + self.eventsManager.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsDebugLoggingEnabledKey]; - // It is possible for `MGLTelemetryAccessToken` to have been set yet `userDefaultsDidChange:` + // It is possible for the telemetry access token key to have been set yet `userDefaultsDidChange:` // is called before `setupWithAccessToken:` is called. // In that case, setting the access token here will have no effect. In practice, that's fine // because the access token value will be resolved when `setupWithAccessToken:` is called eventually - if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryAccessToken]) { - self.eventsManager.accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryAccessToken]; + if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryAccessTokenKey]) { + self.eventsManager.accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryAccessTokenKey]; } - // It is possible for `MGLTelemetryBaseURL` to have been set yet `userDefaultsDidChange:` + // It is possible for the telemetry base URL key to have been set yet `userDefaultsDidChange:` // is called before setupWithAccessToken: is called. // In that case, setting the base URL here will have no effect. In practice, that's fine // because the base URL value will be resolved when `setupWithAccessToken:` is called eventually - if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryBaseURL]) { - NSURL *baseURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryBaseURL]]; + if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryBaseURLKey]) { + NSURL *baseURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryBaseURLKey]]; self.eventsManager.baseURL = baseURL; } } @@ -109,8 +111,8 @@ static NSString * const MGLVariableGeofence = @"VariableGeofence"; if ([[notification object] respondsToSelector:@selector(objectForKey:)]) { NSUserDefaults *userDefaults = [notification object]; - NSInteger accountType = [userDefaults integerForKey:MGLMapboxAccountType]; - BOOL metricsEnabled = [userDefaults boolForKey:MGLMapboxMetricsEnabled]; + NSInteger accountType = [userDefaults integerForKey:MGLMapboxAccountTypeKey]; + BOOL metricsEnabled = [userDefaults boolForKey:MGLMapboxMetricsEnabledKey]; if (accountType != self.eventsManager.accountType || metricsEnabled != self.eventsManager.metricsEnabled) { self.eventsManager.accountType = accountType; @@ -124,7 +126,7 @@ static NSString * const MGLVariableGeofence = @"VariableGeofence"; + (void)setupWithAccessToken:(NSString *)accessToken { int64_t delayTime = 0; - if ([[[NSBundle mainBundle] objectForInfoDictionaryKey:MGLEventsProfile] isEqualToString:MGLVariableGeofence]) { + if ([[[NSBundle mainBundle] objectForInfoDictionaryKey:MGLEventsProfileKey] isEqualToString:MGLVariableGeofenceKey]) { delayTime = 10; } @@ -164,11 +166,11 @@ static NSString * const MGLVariableGeofence = @"VariableGeofence"; } + (void)ensureMetricsOptoutExists { - NSNumber *shownInAppNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLMapboxMetricsEnabledSettingShownInApp"]; + NSNumber *shownInAppNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:MGLMapboxMetricsEnabledSettingShownInAppKey]; BOOL metricsEnabledSettingShownInAppFlag = [shownInAppNumber boolValue]; if (!metricsEnabledSettingShownInAppFlag && - [[NSUserDefaults standardUserDefaults] integerForKey:MGLMapboxAccountType] == 0) { + [[NSUserDefaults standardUserDefaults] integerForKey:MGLMapboxAccountTypeKey] == 0) { // Opt-out is not configured in UI, so check for Settings.bundle id defaultEnabledValue; NSString *appSettingsBundle = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"]; @@ -178,7 +180,7 @@ static NSString * const MGLVariableGeofence = @"VariableGeofence"; NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[appSettingsBundle stringByAppendingPathComponent:@"Root.plist"]]; NSArray *preferences = settings[@"PreferenceSpecifiers"]; for (NSDictionary *prefSpecification in preferences) { - if ([prefSpecification[@"Key"] isEqualToString:MGLMapboxMetricsEnabled]) { + if ([prefSpecification[@"Key"] isEqualToString:MGLMapboxMetricsEnabledKey]) { defaultEnabledValue = prefSpecification[@"DefaultValue"]; } } diff --git a/platform/ios/src/MGLScaleBar.mm b/platform/ios/src/MGLScaleBar.mm index 9590a99438..993852d8b9 100644 --- a/platform/ios/src/MGLScaleBar.mm +++ b/platform/ios/src/MGLScaleBar.mm @@ -84,7 +84,7 @@ static const MGLRow MGLImperialTable[] ={ @property (nonatomic) UIColor *secondaryColor; @property (nonatomic) CALayer *borderLayer; @property (nonatomic, assign) CGFloat borderWidth; -@property (nonatomic) NSCache* labelImageCache; +@property (nonatomic) NSMutableDictionary* labelImageCache; @property (nonatomic) MGLScaleBarLabel* prototypeLabel; @property (nonatomic) CGFloat lastLabelWidth; @@ -159,7 +159,7 @@ static const CGFloat MGLFeetPerMeter = 3.28084; _formatter = [[MGLDistanceFormatter alloc] init]; // Image labels are now images - _labelImageCache = [[NSCache alloc] init]; + _labelImageCache = [[NSMutableDictionary alloc] init]; _prototypeLabel = [[MGLScaleBarLabel alloc] init]; _prototypeLabel.font = [UIFont systemFontOfSize:8 weight:UIFontWeightMedium]; _prototypeLabel.clipsToBounds = NO; @@ -180,6 +180,17 @@ static const CGFloat MGLFeetPerMeter = 3.28084; // Zero is a special case (no formatting) [self addZeroLabel]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resetLabelImageCache) name:NSCurrentLocaleDidChangeNotification object:nil]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)resetLabelImageCache { + self.labelImageCache = [[NSMutableDictionary alloc] init]; + [self addZeroLabel]; } #pragma mark - Dimensions diff --git a/platform/ios/test/MGLMapViewDirectionTests.mm b/platform/ios/test/MGLMapViewDirectionTests.mm index 8a724a06bc..81e169432b 100644 --- a/platform/ios/test/MGLMapViewDirectionTests.mm +++ b/platform/ios/test/MGLMapViewDirectionTests.mm @@ -1,5 +1,6 @@ #import <Mapbox/Mapbox.h> #import <XCTest/XCTest.h> +#import "MGLMockGestureRecognizers.h" #import <mbgl/math/wrap.hpp> @@ -8,12 +9,6 @@ - (void)resetNorthAnimated:(BOOL)animated; @end -@interface UIRotationGestureRecognizerMock : UIRotationGestureRecognizer -@end - -@implementation UIRotationGestureRecognizerMock -- (CGPoint)locationInView:(nullable UIView*)view { return view.center; } -@end @interface MGLMapViewDirectionTests : XCTestCase @property (nonatomic) MGLMapView *mapView; diff --git a/platform/ios/test/MGLMapViewPitchTests.m b/platform/ios/test/MGLMapViewPitchTests.m index b7b18973a1..3e9311dbd4 100644 --- a/platform/ios/test/MGLMapViewPitchTests.m +++ b/platform/ios/test/MGLMapViewPitchTests.m @@ -2,23 +2,35 @@ #import <XCTest/XCTest.h> @interface MockUIPanGestureRecognizer : UIPanGestureRecognizer -@property CGFloat mbx_tiltGestureYTranslation; @property NSUInteger mbx_numberOfFingersForGesture; +@property CGPoint mbx_middlePoint; +@property CGPoint mbx_westPoint; +@property CGPoint mbx_eastPoint; @end @implementation MockUIPanGestureRecognizer - (instancetype)initWithTarget:(id)target action:(SEL)action { if (self = [super initWithTarget:target action:action]) { - self.mbx_tiltGestureYTranslation = 0; self.mbx_numberOfFingersForGesture = 2; + self.mbx_westPoint = CGPointMake(100, 0); + self.mbx_eastPoint = CGPointMake(200, 0); } return self; } - (NSUInteger)numberOfTouches { return self.mbx_numberOfFingersForGesture; } -- (CGPoint)translationInView:(UIView *)view { return CGPointMake(0, self.mbx_tiltGestureYTranslation); } +- (CGPoint)translationInView:(UIView *)view { return self.mbx_middlePoint; } +- (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(UIView *)view { + if (touchIndex == 0) { + return self.mbx_westPoint; + } + return self.mbx_eastPoint; +} - (void)setTiltGestureYTranslationForPitchDegrees:(CGFloat)degrees { // The tilt gesture takes the number of screen points the fingers have moved and then divides them by a "slowdown" factor, which happens to be set to 2.0 in -[MGLMapView handleTwoFingerDragGesture:]. - self.mbx_tiltGestureYTranslation = -(degrees * 2.0); + CGFloat mbx_tiltGestureYTranslation = -(degrees * 2.0); + self.mbx_westPoint = CGPointMake(self.mbx_westPoint.x, mbx_tiltGestureYTranslation); + self.mbx_eastPoint = CGPointMake(self.mbx_eastPoint.x, mbx_tiltGestureYTranslation); + self.mbx_middlePoint = CGPointMake(self.mbx_middlePoint.x, mbx_tiltGestureYTranslation); } @end @@ -112,6 +124,25 @@ } } +- (void)testHorizontalTiltGesture { + MockUIPanGestureRecognizer *gesture = [[MockUIPanGestureRecognizer alloc] initWithTarget:self.mapView action:nil]; + gesture.state = UIGestureRecognizerStateBegan; + [self.mapView handleTwoFingerDragGesture:gesture]; + XCTAssertEqual(self.mapView.camera.pitch, 0, @"Pitch should initially be set to 0°."); + + // Tilt gestures should not be triggered on horizontal dragging. + for (NSInteger deltaX = 0; deltaX < 5; deltaX++) { + gesture.mbx_westPoint = CGPointMake(100 - deltaX, 100); + gesture.mbx_eastPoint = CGPointMake(100 - deltaX, 50); + gesture.mbx_middlePoint = CGPointMake((gesture.mbx_westPoint.x + gesture.mbx_westPoint.x)/2, (gesture.mbx_westPoint.y + gesture.mbx_westPoint.y)/2); + + gesture.state = UIGestureRecognizerStateChanged; + + [self.mapView handleTwoFingerDragGesture:gesture]; + XCTAssertEqual(self.mapView.camera.pitch, 0, @"Horizontal dragging should not tilt the map."); + } +} + - (void)testTiltGestureFromInitialTilt { CGFloat initialTilt = 20; CGFloat additionalTilt = 30; diff --git a/platform/ios/test/MGLMapViewZoomTests.m b/platform/ios/test/MGLMapViewZoomTests.mm index bd617857fd..360af72d02 100644 --- a/platform/ios/test/MGLMapViewZoomTests.m +++ b/platform/ios/test/MGLMapViewZoomTests.mm @@ -1,16 +1,14 @@ #import <Mapbox/Mapbox.h> #import <XCTest/XCTest.h> +#import "MGLMockGestureRecognizers.h" + +#import <mbgl/math/wrap.hpp> @interface MGLMapView (MGLMapViewZoomTests) +@property (nonatomic) BOOL isZooming; +@property (nonatomic) CGFloat rotationThresholdWhileZooming; - (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch; -@end - -@interface UIPinchGestureRecognizerMock : UIPinchGestureRecognizer -@property (nonatomic) CGPoint locationInViewOverride; -@end - -@implementation UIPinchGestureRecognizerMock -- (CGPoint)locationInView:(nullable UIView *)view { return self.locationInViewOverride; } +- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate; @end @interface MGLMapViewZoomTests : XCTestCase @@ -129,6 +127,60 @@ XCTAssertEqualWithAccuracy(centerCoordinateBeforeReset.longitude, self.mapView.centerCoordinate.longitude, 0.0000001, @"Map center coordinate longitude should remain constant after resetting to north."); } +- (void)testPinchAndZoom { + + [[NSUserDefaults standardUserDefaults] setObject:@3 forKey:@"MGLRotationThresholdWhileZooming"]; + self.mapView.rotationThresholdWhileZooming = 3; + self.mapView.zoomLevel = 15; + UIPinchGestureRecognizerMock *pinch = [[UIPinchGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; + [self.mapView addGestureRecognizer:pinch]; + pinch.state = UIGestureRecognizerStateBegan; + pinch.velocity = 5.0; + pinch.locationInViewOverride = CGPointMake(0, 0); + [self.mapView handlePinchGesture:pinch]; + + XCTAssertTrue(self.mapView.isZooming); + + UIRotationGestureRecognizerMock *rotate = [[UIRotationGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; + rotate.state = UIGestureRecognizerStateBegan; + rotate.rotation = MGLRadiansFromDegrees(1); + [self.mapView addGestureRecognizer:rotate]; + [self.mapView handleRotateGesture:rotate]; + + // Both the rotation and direction should be zero since the rotation threshold hasn't been met. + XCTAssertEqual(rotate.rotation, 0); + XCTAssertEqual(self.mapView.direction, 0); + + // The direction should be `0`. The default rotation threshold is `3`. + XCTAssertEqual(self.mapView.direction, 0); + rotate.state = UIGestureRecognizerStateChanged; + rotate.rotation = MGLRadiansFromDegrees(2); + [self.mapView handleRotateGesture:rotate]; + + // The direction should be `0`. The default rotation threshold is `3`. + XCTAssertEqual(self.mapView.direction, 0); + + for (NSNumber *degrees in @[@-90, @-10, @10, @10, @30, @90, @180, @240, @460, @500, @590, @800]) { + rotate.state = UIGestureRecognizerStateChanged; + rotate.rotation = MGLRadiansFromDegrees([degrees doubleValue]); + [self.mapView handleRotateGesture:rotate]; + + CGFloat wrappedRotation = mbgl::util::wrap(-MGLDegreesFromRadians(rotate.rotation), 0., 360.); + + + // Check that the direction property now matches the gesture's rotation. + XCTAssertEqualWithAccuracy(self.mapView.direction, wrappedRotation, 0.001, @"Map direction should match gesture rotation for input of %@°.", degrees); + } + + rotate.state = UIGestureRecognizerStateEnded; + pinch.state = UIGestureRecognizerStateEnded; + + [self.mapView handleRotateGesture:rotate]; + [self.mapView handlePinchGesture:pinch]; + + XCTAssertFalse(self.mapView.isZooming); +} + NS_INLINE CGFloat MGLScaleFromZoomLevel(double zoom) { return pow(2, zoom); } diff --git a/platform/ios/test/MGLMockGestureRecognizers.h b/platform/ios/test/MGLMockGestureRecognizers.h new file mode 100644 index 0000000000..aa5fbec494 --- /dev/null +++ b/platform/ios/test/MGLMockGestureRecognizers.h @@ -0,0 +1,10 @@ + +#import <UIKit/UIKit.h> + +@interface UIPinchGestureRecognizerMock : UIPinchGestureRecognizer +@property (nonatomic, readwrite) CGFloat velocity; +@property (nonatomic) CGPoint locationInViewOverride; +@end + +@interface UIRotationGestureRecognizerMock : UIRotationGestureRecognizer +@end diff --git a/platform/ios/test/MGLMockGestureRecognizers.m b/platform/ios/test/MGLMockGestureRecognizers.m new file mode 100644 index 0000000000..89df6750a9 --- /dev/null +++ b/platform/ios/test/MGLMockGestureRecognizers.m @@ -0,0 +1,11 @@ + +#import "MGLMockGestureRecognizers.h" + +@implementation UIPinchGestureRecognizerMock +@synthesize velocity; +- (CGPoint)locationInView:(nullable UIView *)view { return self.locationInViewOverride; } +@end + +@implementation UIRotationGestureRecognizerMock +- (CGPoint)locationInView:(nullable UIView*)view { return view.center; } +@end |