diff options
author | Julian Rex <julian.rex@mapbox.com> | 2019-08-25 01:02:29 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-25 01:02:29 -0400 |
commit | 4010b1d256fd33a677ab851fa6faa171ec64fbed (patch) | |
tree | d5b62cfc3ca148e21a0c5a646e3d072ce625d32e | |
parent | 5c066f874c1dbf699d58aea967b13a7daea1513e (diff) | |
download | qtlocation-mapboxgl-4010b1d256fd33a677ab851fa6faa171ec64fbed.tar.gz |
[ios] Added pending completion blocks (#15122)
-rw-r--r-- | platform/ios/CHANGELOG.md | 1 | ||||
-rw-r--r-- | platform/ios/Integration Tests/MGLMapViewPendingBlockTests.m | 367 | ||||
-rw-r--r-- | platform/ios/ios.xcodeproj/project.pbxproj | 6 | ||||
-rw-r--r-- | platform/ios/src/MGLMapView.mm | 141 |
4 files changed, 484 insertions, 31 deletions
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 5dc73de6d3..8edd8adc02 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -5,6 +5,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ## master * 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/)) ## 5.3.0 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/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 5a5cb00b49..018a5f9368 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -508,6 +508,7 @@ 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, ); }; }; @@ -1198,11 +1199,12 @@ 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>"; }; - CA86FF0D22D8D5A0009EB14A /* MGLNetworkConfigurationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLNetworkConfigurationTests.m; sourceTree = "<group>"; }; CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLAnnotationViewIntegrationTests.mm; path = "Annotation Tests/MGLAnnotationViewIntegrationTests.mm"; sourceTree = "<group>"; }; + 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>"; }; CAD9D0A922A86D6F001B25EE /* MGLResourceTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLResourceTests.mm; path = ../../darwin/test/MGLResourceTests.mm; sourceTree = "<group>"; }; @@ -1554,6 +1556,7 @@ CA88DC2F21C85D900059ED5A /* MGLStyleURLIntegrationTest.m */, 077061DB215DA11F000FEF62 /* MGLTestLocationManager.h */, 077061D9215DA00E000FEF62 /* MGLTestLocationManager.m */, + CA4F3BDD230F74C3008BAFEA /* MGLMapViewPendingBlockTests.m */, ); path = "Integration Tests"; sourceTree = "<group>"; @@ -3196,6 +3199,7 @@ 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 */, diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 3fc692f4e0..6d466bf2cf 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -257,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; @@ -625,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 @@ -1046,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 @@ -1070,7 +1104,7 @@ public: return; } - if (_needsDisplayRefresh) + if (_needsDisplayRefresh || (self.pendingCompletionBlocks.count > 0)) { _needsDisplayRefresh = NO; @@ -1079,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(); } @@ -1145,6 +1186,7 @@ public: { [_displayLink invalidate]; _displayLink = nil; + [self processPendingBlocks]; } } @@ -1428,6 +1470,7 @@ public: [MGLMapboxEvents flush]; _displayLink.paused = YES; + [self processPendingBlocks]; if ( ! self.glSnapshotView) { @@ -1480,6 +1523,11 @@ public: { super.hidden = hidden; _displayLink.paused = hidden; + + if (hidden) + { + [self processPendingBlocks]; + } } - (void)tintColorDidChange @@ -3340,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; } @@ -3526,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); }; } @@ -3541,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; } @@ -3686,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; } @@ -3757,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; } |