From 97b02c9759fd829f12a04369e8554862f065e47b Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Fri, 27 Sep 2019 12:11:33 -0700 Subject: [ios] Fix the contentInset value after adding padding to the camera. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed an issue that caused a discrepancy between the contentInset in MGLMapView and the padding in the transformation state. When padding is passed through methods such as setCamera it’s persisted. This fix resets the contentInsets. --- platform/ios/ios.xcodeproj/project.pbxproj | 8 +- platform/ios/src/MGLMapView.mm | 60 ++++- .../ios/test/MGLMapViewGestureRecognizerTests.mm | 280 +++++++++++++++++++++ platform/ios/test/MGLMockGestureRecognizers.h | 19 ++ platform/ios/test/MGLMockGestureRecognizers.m | 44 ++++ 5 files changed, 400 insertions(+), 11 deletions(-) create mode 100644 platform/ios/test/MGLMapViewGestureRecognizerTests.mm diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 447fb5976f..26c1979e32 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ 1F7454A91ED08AB400021D39 /* MGLLightTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F7454A61ED08AB400021D39 /* MGLLightTest.mm */; }; 1F8A59F72165326D004DFE75 /* sideload_sat.db in Resources */ = {isa = PBXBuildFile; fileRef = 1F8A59F62165326C004DFE75 /* sideload_sat.db */; }; 1F8A59F821653275004DFE75 /* sideload_sat.db in Resources */ = {isa = PBXBuildFile; fileRef = 1F8A59F62165326C004DFE75 /* sideload_sat.db */; }; + 1F8E8A81233A9FD9009B51ED /* MGLMapViewGestureRecognizerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F8E8A80233A9FD9009B51ED /* MGLMapViewGestureRecognizerTests.mm */; }; 1F95931D1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */; }; 1FC4817D2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC4817B2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h */; }; 1FC4817F2098CD80000D09B4 /* NSPredicate+MGLPrivateAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC4817B2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h */; }; @@ -910,6 +911,7 @@ 1F7454941ECD450D00021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = ""; }; 1F7454A61ED08AB400021D39 /* MGLLightTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLLightTest.mm; path = ../../darwin/test/MGLLightTest.mm; sourceTree = ""; }; 1F8A59F62165326C004DFE75 /* sideload_sat.db */ = {isa = PBXFileReference; lastKnownFileType = file; name = sideload_sat.db; path = ../../../test/fixtures/offline_database/sideload_sat.db; sourceTree = ""; }; + 1F8E8A80233A9FD9009B51ED /* MGLMapViewGestureRecognizerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapViewGestureRecognizerTests.mm; sourceTree = ""; }; 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLNSDateAdditionsTests.mm; path = ../../darwin/test/MGLNSDateAdditionsTests.mm; sourceTree = ""; }; 1FC4817B2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSPredicate+MGLPrivateAdditions.h"; sourceTree = ""; }; 1FCAE2A020B872A400C577DD /* MGLLocationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLLocationManager.h; sourceTree = ""; }; @@ -1074,8 +1076,8 @@ 5580B45A229570A10091291B /* MGLMapView+OpenGL.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "MGLMapView+OpenGL.mm"; sourceTree = ""; }; 558DE79E1E5615E400C7916D /* MGLFoundation_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFoundation_Private.h; sourceTree = ""; }; 558DE79F1E5615E400C7916D /* MGLFoundation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFoundation.mm; sourceTree = ""; }; - 55CF752E213ED92000ED86C4 /* libmbgl-vendor-icu.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libmbgl-vendor-icu.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 55CF7530213ED92A00ED86C4 /* libmbgl-vendor-icu.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libmbgl-vendor-icu.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 55CF752E213ED92000ED86C4 /* libmbgl-vendor-icu.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libmbgl-vendor-icu.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 55CF7530213ED92A00ED86C4 /* libmbgl-vendor-icu.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libmbgl-vendor-icu.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 55D120A71F791007004B6D81 /* libmbgl-loop-darwin.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libmbgl-loop-darwin.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 55D120A91F79100C004B6D81 /* libmbgl-filesource.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libmbgl-filesource.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 55D8C9941D0F133500F42F10 /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = config.xcconfig; path = ../../build/ios/config.xcconfig; sourceTree = ""; }; @@ -2085,6 +2087,7 @@ 96E6145522CC135200109F14 /* MGLMapViewCompassViewTests.mm */, 1F0196A923174B0700F5C819 /* MGLMapViewContentInsetTests.m */, 96ED34DD22374C0900E9FCA9 /* MGLMapViewDirectionTests.mm */, + 1F8E8A80233A9FD9009B51ED /* MGLMapViewGestureRecognizerTests.mm */, 16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */, 96381C0122C6F3950053497D /* MGLMapViewPitchTests.m */, 9658C154204761FC00D8A674 /* MGLMapViewScaleBarTests.m */, @@ -3325,6 +3328,7 @@ 357579831D502AE6000B822E /* MGLRasterStyleLayerTests.mm in Sources */, 3502D6CC22AE88D5006BDFCE /* MGLAccountManagerTests.m in Sources */, DAF25720201902BC00367EF5 /* MGLHillshadeStyleLayerTests.mm in Sources */, + 1F8E8A81233A9FD9009B51ED /* MGLMapViewGestureRecognizerTests.mm in Sources */, 353D23961D0B0DFE002BE09D /* MGLAnnotationViewTests.m in Sources */, DA0CD5901CF56F6A00A5F5A5 /* MGLFeatureTests.mm in Sources */, 556660D81E1D085500E2C41B /* MGLVersionNumber.m in Sources */, diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 27ffe5fba5..486e0ad002 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -1756,7 +1756,10 @@ public: if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { - self.mbglMap.jumpTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })); + self.mbglMap.jumpTo(mbgl::CameraOptions() + .withZoom(newZoom) + .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }) + .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset))); // The gesture recognizer only reports the gesture’s current center // point, so use the previous center point to anchor the transition. @@ -1814,7 +1817,10 @@ public: { if (drift) { - self.mbglMap.easeTo(mbgl::CameraOptions().withZoom(zoom).withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }), MGLDurationFromTimeInterval(duration)); + self.mbglMap.easeTo(mbgl::CameraOptions() + .withZoom(zoom) + .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }) + .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)), MGLDurationFromTimeInterval(duration)); } } @@ -1879,7 +1885,8 @@ public: { self.mbglMap.jumpTo(mbgl::CameraOptions() .withBearing(newDegrees) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y})); + .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y}) + .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset))); } [self cameraIsChanging]; @@ -1920,7 +1927,8 @@ public: { self.mbglMap.easeTo(mbgl::CameraOptions() .withBearing(newDegrees) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }), + .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }) + .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)), MGLDurationFromTimeInterval(decelerationRate)); [self notifyGestureDidEndWithDrift:YES]; @@ -2048,7 +2056,10 @@ public: if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y); - self.mbglMap.easeTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(center), MGLDurationFromTimeInterval(MGLAnimationDuration)); + self.mbglMap.easeTo(mbgl::CameraOptions() + .withZoom(newZoom) + .withAnchor(center) + .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)), MGLDurationFromTimeInterval(MGLAnimationDuration)); __weak MGLMapView *weakSelf = self; @@ -2086,7 +2097,10 @@ public: if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y); - self.mbglMap.easeTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(center), MGLDurationFromTimeInterval(MGLAnimationDuration)); + self.mbglMap.easeTo(mbgl::CameraOptions() + .withZoom(newZoom) + .withAnchor(center) + .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)), MGLDurationFromTimeInterval(MGLAnimationDuration)); __weak MGLMapView *weakSelf = self; @@ -2128,7 +2142,10 @@ public: if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { - self.mbglMap.jumpTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })); + self.mbglMap.jumpTo(mbgl::CameraOptions() + .withZoom(newZoom) + .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }) + .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset))); } [self cameraIsChanging]; @@ -2195,7 +2212,8 @@ public: { self.mbglMap.jumpTo(mbgl::CameraOptions() .withPitch(pitchNew) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })); + .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }) + .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset))); } [self cameraIsChanging]; @@ -3255,7 +3273,10 @@ public: centerPoint = self.userLocationAnnotationViewCenter; } double newZoom = round(self.zoomLevel) + log2(scaleFactor); - self.mbglMap.jumpTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })); + self.mbglMap.jumpTo(mbgl::CameraOptions() + .withZoom(newZoom) + .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }) + .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset))); [self unrotateIfNeededForGesture]; _accessibilityValueAnnouncementIsPending = YES; @@ -3854,6 +3875,27 @@ public: return [self cameraForCameraOptions:cameraOptions]; } +- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinate:(CLLocationCoordinate2D)coordinate edgePadding:(UIEdgeInsets)insets { + if (!_mbglMap) + { + return self.residualCamera; + } + + mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets); + padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset); + + MGLMapCamera *currentCamera = self.camera; + CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch; + CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading; + + std::vector latLngs; + latLngs.reserve(1); + latLngs.push_back({coordinate.latitude, coordinate.longitude}); + + mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForLatLngs(latLngs, padding, direction, pitch); + return [self cameraForCameraOptions:cameraOptions]; +} + - (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets { if (!_mbglMap) diff --git a/platform/ios/test/MGLMapViewGestureRecognizerTests.mm b/platform/ios/test/MGLMapViewGestureRecognizerTests.mm new file mode 100644 index 0000000000..58fbf2d03b --- /dev/null +++ b/platform/ios/test/MGLMapViewGestureRecognizerTests.mm @@ -0,0 +1,280 @@ +#import +#import + +#import "../../darwin/src/MGLGeometry_Private.h" +#import "MGLMockGestureRecognizers.h" + +#include +#include + +@interface MGLMapView (MGLMapViewGestureRecognizerTests) + +- (mbgl::Map &)mbglMap; + +- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch; +- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate; +- (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap; +- (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap; +- (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom; +- (void)handleTwoFingerDragGesture:(UIPanGestureRecognizer *)twoFingerDrag; + +@end + +@interface MGLMapViewGestureRecognizerTests : XCTestCase + +@property (nonatomic) MGLMapView *mapView; +@property (nonatomic) UIWindow *window; +@property (nonatomic) UIViewController *viewController; +@property (nonatomic) XCTestExpectation *styleLoadingExpectation; +@property (nonatomic) XCTestExpectation *twoFingerExpectation; +@property (nonatomic) XCTestExpectation *quickZoomExpectation; +@property (nonatomic) XCTestExpectation *doubleTapExpectation; +@property (nonatomic) XCTestExpectation *twoFingerDragExpectation; +@property (assign) CGRect screenBounds; + +@end + +@implementation MGLMapViewGestureRecognizerTests + +- (void)setUp { + [super setUp]; + + [MGLAccountManager setAccessToken:@"pk.feedcafedeadbeefbadebede"]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; + self.screenBounds = UIScreen.mainScreen.bounds; + self.mapView = [[MGLMapView alloc] initWithFrame:self.screenBounds styleURL:styleURL]; + self.mapView.zoomLevel = 16; + self.mapView.delegate = self; + + self.viewController = [[UIViewController alloc] init]; + self.viewController.view = [[UIView alloc] initWithFrame:self.screenBounds]; + [self.viewController.view addSubview:self.mapView]; + self.window = [[UIWindow alloc] initWithFrame:self.screenBounds]; + [self.window addSubview:self.viewController.view]; + [self.window makeKeyAndVisible]; + + if (!self.mapView.style) { + _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; + [self waitForExpectationsWithTimeout:10 handler:nil]; + } +} + +- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style { + XCTAssertNotNil(mapView.style); + XCTAssertEqual(mapView.style, style); + + [_styleLoadingExpectation fulfill]; +} + +- (void)testHandlePinchGestureContentInset { + UIEdgeInsets contentInset = UIEdgeInsetsZero; + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); + + UIPinchGestureRecognizerMock *pinchGesture = [[UIPinchGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + pinchGesture.state = UIGestureRecognizerStateBegan; + pinchGesture.scale = 1.0; + [self.mapView handlePinchGesture:pinchGesture]; + XCTAssertNotEqual(padding, cameraPadding); + + pinchGesture.state = UIGestureRecognizerStateChanged; + [self.mapView handlePinchGesture:pinchGesture]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match."); + + pinchGesture.state = UIGestureRecognizerStateEnded; + [self.mapView handlePinchGesture:pinchGesture]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match."); + +} + +- (void)testHandleRotateGestureContentInset { + UIEdgeInsets contentInset = UIEdgeInsetsZero; + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); + + UIRotationGestureRecognizerMock *rotateGesture = [[UIRotationGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + rotateGesture.state = UIGestureRecognizerStateBegan; + rotateGesture.rotation = 1; + [self.mapView handleRotateGesture:rotateGesture]; + XCTAssertNotEqual(padding, cameraPadding); + + rotateGesture.state = UIGestureRecognizerStateChanged; + [self.mapView handleRotateGesture:rotateGesture]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match."); + + rotateGesture.state = UIGestureRecognizerStateEnded; + [self.mapView handleRotateGesture:rotateGesture]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match."); + +} + +- (void)testHandleDoubleTapGestureContentInset { + UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); + + UITapGestureRecognizerMock *doubleTapGesture = [[UITapGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + doubleTapGesture.mockTappedView = self.mapView; + doubleTapGesture.mockTappedPoint = CGPointMake(1.0, 1.0); + + [self.mapView handleDoubleTapGesture:doubleTapGesture]; + _doubleTapExpectation = [self expectationWithDescription:@"Double tap gesture animation."]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self->_doubleTapExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; + + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match."); + +} + +- (void)testHandleTwoFingerTapGesture { + UIEdgeInsets contentInset = UIEdgeInsetsZero; + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); + + UITapGestureRecognizerMock *twoFingerTap = [[UITapGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + twoFingerTap.mockTappedView = self.mapView; + twoFingerTap.mockTappedPoint = CGPointMake(1.0, 1.0); + + [self.mapView handleTwoFingerTapGesture:twoFingerTap]; + _twoFingerExpectation = [self expectationWithDescription:@"Two Finger tap gesture animation."]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self->_twoFingerExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; + + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match."); +} + +- (void)testHandleQuickZoomGesture { + UIEdgeInsets contentInset = UIEdgeInsetsZero; + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); + + UILongPressGestureRecognizerMock *quickZoom = [[UILongPressGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + quickZoom.state = UIGestureRecognizerStateBegan; + [self.mapView handleQuickZoomGesture:quickZoom]; + XCTAssertNotEqual(padding, cameraPadding); + + quickZoom.state = UIGestureRecognizerStateChanged; + quickZoom.mockTappedPoint = CGPointMake(self.mapView.frame.size.width / 2, self.mapView.frame.size.height / 2); + [self.mapView handleQuickZoomGesture:quickZoom]; + _quickZoomExpectation = [self expectationWithDescription:@"Quick zoom gesture animation."]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self->_quickZoomExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; + + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match."); + + quickZoom.state = UIGestureRecognizerStateEnded; + [self.mapView handleQuickZoomGesture:quickZoom]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match."); +} + +- (void)testHandleTwoFingerDragGesture { + UIEdgeInsets contentInset = UIEdgeInsetsZero; + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); + + UIPanGestureRecognizerMock *twoFingerDrag = [[UIPanGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + twoFingerDrag.state = UIGestureRecognizerStateBegan; + twoFingerDrag.firstFingerPoint = CGPointMake(self.mapView.frame.size.width / 3, self.mapView.frame.size.height/2); + twoFingerDrag.secondFingerPoint = CGPointMake((self.mapView.frame.size.width / 2), self.mapView.frame.size.height/2); + twoFingerDrag.numberOfTouches = 2; + [self.mapView handleTwoFingerDragGesture:twoFingerDrag]; + XCTAssertNotEqual(padding, cameraPadding); + + twoFingerDrag.state = UIGestureRecognizerStateChanged; + twoFingerDrag.firstFingerPoint = CGPointMake(self.mapView.frame.size.width / 3, (self.mapView.frame.size.height/2)-10); + twoFingerDrag.secondFingerPoint = CGPointMake((self.mapView.frame.size.width / 2), (self.mapView.frame.size.height/2)-10); + [self.mapView handleTwoFingerDragGesture:twoFingerDrag]; + _twoFingerDragExpectation = [self expectationWithDescription:@"Quick zoom gesture animation."]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self->_twoFingerDragExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; + + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match."); + + twoFingerDrag.state = UIGestureRecognizerStateEnded; + [self.mapView handleTwoFingerDragGesture:twoFingerDrag]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match."); +} + +@end diff --git a/platform/ios/test/MGLMockGestureRecognizers.h b/platform/ios/test/MGLMockGestureRecognizers.h index aa5fbec494..29889e39f4 100644 --- a/platform/ios/test/MGLMockGestureRecognizers.h +++ b/platform/ios/test/MGLMockGestureRecognizers.h @@ -4,7 +4,26 @@ @interface UIPinchGestureRecognizerMock : UIPinchGestureRecognizer @property (nonatomic, readwrite) CGFloat velocity; @property (nonatomic) CGPoint locationInViewOverride; +@property(nonatomic, readwrite) UIGestureRecognizerState state; @end @interface UIRotationGestureRecognizerMock : UIRotationGestureRecognizer +@property(nonatomic, readwrite) UIGestureRecognizerState state; +@end + +@interface UITapGestureRecognizerMock : UITapGestureRecognizer +@property (strong, nonatomic) UIView *mockTappedView; +@property (assign) CGPoint mockTappedPoint; +@end + +@interface UILongPressGestureRecognizerMock : UILongPressGestureRecognizer +@property(nonatomic, readwrite) UIGestureRecognizerState state; +@property (assign) CGPoint mockTappedPoint; +@end + +@interface UIPanGestureRecognizerMock : UIPanGestureRecognizer +@property(nonatomic, readwrite) UIGestureRecognizerState state; +@property (assign) CGPoint firstFingerPoint; +@property (assign) CGPoint secondFingerPoint; +@property(nonatomic, readwrite) NSUInteger numberOfTouches; @end diff --git a/platform/ios/test/MGLMockGestureRecognizers.m b/platform/ios/test/MGLMockGestureRecognizers.m index 89df6750a9..c818805174 100644 --- a/platform/ios/test/MGLMockGestureRecognizers.m +++ b/platform/ios/test/MGLMockGestureRecognizers.m @@ -1,11 +1,55 @@ #import "MGLMockGestureRecognizers.h" +#import "objc/runtime.h" @implementation UIPinchGestureRecognizerMock @synthesize velocity; +@synthesize state; - (CGPoint)locationInView:(nullable UIView *)view { return self.locationInViewOverride; } @end @implementation UIRotationGestureRecognizerMock - (CGPoint)locationInView:(nullable UIView*)view { return view.center; } +@synthesize state; +@end + +@implementation UITapGestureRecognizerMock + ++ (void)load { + method_exchangeImplementations(class_getInstanceMethod(self, @selector(state)), + class_getInstanceMethod(self, @selector(mockState))); +} + +- (UIGestureRecognizerState)mockState { + return UIGestureRecognizerStateRecognized; +} + +- (UIView *)view { + return self.mockTappedView; +} + +- (CGPoint)locationInView:(UIView *)view { + return self.mockTappedPoint; +} + +@end + +@implementation UILongPressGestureRecognizerMock +@synthesize state; + +- (CGPoint)locationInView:(UIView *)view { + return self.mockTappedPoint; +} +@end + +@implementation UIPanGestureRecognizerMock +@synthesize state; +@synthesize numberOfTouches; + +- (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(UIView *)view { + if (touchIndex) { + return self.secondFingerPoint; + } + return self.firstFingerPoint; +} @end -- cgit v1.2.1