diff options
Diffstat (limited to 'platform/ios/src/MGLMapView.mm')
-rw-r--r-- | platform/ios/src/MGLMapView.mm | 141 |
1 files changed, 111 insertions, 30 deletions
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; } |