@@ -247,6 +247,7 @@ @interface MGLMapView () <UIGestureRecognizerDelegate,
247247@property (nonatomic ) NSMutableDictionary <NSString *, NSMutableArray<MGLAnnotationView *> *> *annotationViewReuseQueueByIdentifier;
248248@property (nonatomic , readonly ) BOOL enablePresentsWithTransaction;
249249@property (nonatomic ) UIImage *lastSnapshotImage;
250+ @property (nonatomic ) NSMutableArray *pendingCompletionBlocks;
250251
251252// / Experimental rendering performance measurement.
252253@property (nonatomic ) BOOL experimental_enableFrameRateMeasurement;
@@ -612,6 +613,11 @@ - (void)commonInit
612613 [[NSNotificationCenter defaultCenter ] addObserver: self selector: @selector (didEnterBackground: ) name: UIApplicationDidEnterBackgroundNotification object: nil ];
613614 [[NSNotificationCenter defaultCenter ] addObserver: self selector: @selector (willEnterForeground: ) name: UIApplicationWillEnterForegroundNotification object: nil ];
614615 [[NSNotificationCenter defaultCenter ] addObserver: self selector: @selector (didBecomeActive: ) name: UIApplicationDidBecomeActiveNotification object: nil ];
616+
617+ // Pending completion blocks are called *after* annotation views have been updated
618+ // in updateFromDisplayLink.
619+ _pendingCompletionBlocks = [NSMutableArray array ];
620+
615621
616622 // As of 3.7.5, we intentionally do not listen for `UIApplicationWillResignActiveNotification` or call `pauseRendering:` in response to it, as doing
617623 // so causes a loop when asking for location permission. See: https://github.com/mapbox/mapbox-gl-native/issues/11225
@@ -1033,6 +1039,33 @@ - (CGPoint)contentCenter
10331039 return CGPointMake (CGRectGetMidX (contentFrame), CGRectGetMidY (contentFrame));
10341040}
10351041
1042+ #pragma mark - Pending completion blocks
1043+
1044+ - (void )processPendingBlocks
1045+ {
1046+ NSArray *blocks = self.pendingCompletionBlocks ;
1047+ self.pendingCompletionBlocks = [NSMutableArray array ];
1048+
1049+ for (dispatch_block_t block in blocks)
1050+ {
1051+ block ();
1052+ }
1053+ }
1054+
1055+ - (BOOL )addPendingBlock : (dispatch_block_t )block
1056+ {
1057+ // Only add a block if the display link (that calls processPendingBlocks) is
1058+ // running, otherwise fall back to calling immediately.
1059+ BOOL addBlock = (_displayLink && !_displayLink.isPaused );
1060+
1061+ if (addBlock)
1062+ {
1063+ [self .pendingCompletionBlocks addObject: block];
1064+ }
1065+
1066+ return addBlock;
1067+ }
1068+
10361069#pragma mark - Life Cycle -
10371070
10381071- (void )updateFromDisplayLink : (CADisplayLink *)displayLink
@@ -1057,7 +1090,7 @@ - (void)updateFromDisplayLink:(CADisplayLink *)displayLink
10571090 return ;
10581091 }
10591092
1060- if (_needsDisplayRefresh)
1093+ if (_needsDisplayRefresh || (self. pendingCompletionBlocks . count > 0 ) )
10611094 {
10621095 _needsDisplayRefresh = NO ;
10631096
@@ -1066,6 +1099,13 @@ - (void)updateFromDisplayLink:(CADisplayLink *)displayLink
10661099 [self updateAnnotationViews ];
10671100 [self updateCalloutView ];
10681101
1102+ // Call any pending completion blocks. This is primarily to ensure
1103+ // that annotations are in the expected position after core rendering
1104+ // and map update.
1105+ //
1106+ // TODO: Consider using this same mechanism for delegate callbacks.
1107+ [self processPendingBlocks ];
1108+
10691109 _mbglView->display ();
10701110 }
10711111
@@ -1132,6 +1172,7 @@ - (void)validateDisplayLink
11321172 {
11331173 [_displayLink invalidate ];
11341174 _displayLink = nil ;
1175+ [self processPendingBlocks ];
11351176 }
11361177}
11371178
@@ -1415,6 +1456,7 @@ - (void)pauseRendering:(__unused NSNotification *)notification
14151456 [MGLMapboxEvents flush ];
14161457
14171458 _displayLink.paused = YES ;
1459+ [self processPendingBlocks ];
14181460
14191461 if ( ! self.glSnapshotView )
14201462 {
@@ -1467,6 +1509,11 @@ - (void)setHidden:(BOOL)hidden
14671509{
14681510 super.hidden = hidden;
14691511 _displayLink.paused = hidden;
1512+
1513+ if (hidden)
1514+ {
1515+ [self processPendingBlocks ];
1516+ }
14701517}
14711518
14721519- (void )tintColorDidChange
@@ -3188,26 +3235,35 @@ - (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate edgePaddin
31883235 animationOptions.duration .emplace (MGLDurationFromTimeInterval (duration));
31893236 animationOptions.easing .emplace (MGLUnitBezierForMediaTimingFunction (function));
31903237 }
3238+
3239+ dispatch_block_t pendingCompletion;
3240+
31913241 if (completion)
31923242 {
3193- animationOptions.transitionFinishFn = [completion]() {
3243+ __weak __typeof__ (self) weakSelf = self;
3244+
3245+ pendingCompletion = ^{
3246+ if (![weakSelf addPendingBlock: completion])
3247+ {
3248+ completion ();
3249+ }
3250+ };
3251+
3252+ animationOptions.transitionFinishFn = [pendingCompletion]() {
31943253 // Must run asynchronously after the transition is completely over.
31953254 // Otherwise, a call to -setCenterCoordinate: within the completion
31963255 // handler would reenter the completion handler’s caller.
3197- dispatch_async (dispatch_get_main_queue (), ^{
3198- completion ();
3199- });
3256+
3257+ dispatch_async (dispatch_get_main_queue (), pendingCompletion);
32003258 };
32013259 }
32023260
32033261 MGLMapCamera *camera = [self cameraForCameraOptions: cameraOptions];
32043262 if ([self .camera isEqualToMapCamera: camera] && UIEdgeInsetsEqualToEdgeInsets (_contentInset, insets))
32053263 {
3206- if (completion )
3264+ if (pendingCompletion )
32073265 {
3208- [self animateWithDelay: duration animations: ^{
3209- completion ();
3210- }];
3266+ [self animateWithDelay: duration animations: pendingCompletion];
32113267 }
32123268 return ;
32133269 }
@@ -3374,12 +3430,22 @@ - (void)_setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count
33743430 animationOptions.duration .emplace (MGLDurationFromTimeInterval (duration));
33753431 animationOptions.easing .emplace (MGLUnitBezierForMediaTimingFunction (function));
33763432 }
3433+
3434+ dispatch_block_t pendingCompletion;
3435+
33773436 if (completion)
33783437 {
3379- animationOptions.transitionFinishFn = [completion]() {
3380- dispatch_async (dispatch_get_main_queue (), ^{
3438+ __weak __typeof__ (self) weakSelf = self;
3439+
3440+ pendingCompletion = ^{
3441+ if (![weakSelf addPendingBlock: completion])
3442+ {
33813443 completion ();
3382- });
3444+ }
3445+ };
3446+
3447+ animationOptions.transitionFinishFn = [pendingCompletion]() {
3448+ dispatch_async (dispatch_get_main_queue (), pendingCompletion);
33833449 };
33843450 }
33853451
@@ -3389,11 +3455,9 @@ - (void)_setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count
33893455 MGLMapCamera *camera = [self cameraForCameraOptions: cameraOptions];
33903456 if ([self .camera isEqualToMapCamera: camera])
33913457 {
3392- if (completion )
3458+ if (pendingCompletion )
33933459 {
3394- [self animateWithDelay: duration animations: ^{
3395- completion ();
3396- }];
3460+ [self animateWithDelay: duration animations: pendingCompletion];
33973461 }
33983462 return ;
33993463 }
@@ -3534,22 +3598,30 @@ - (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration a
35343598 animationOptions.duration .emplace (MGLDurationFromTimeInterval (duration));
35353599 animationOptions.easing .emplace (MGLUnitBezierForMediaTimingFunction (function));
35363600 }
3601+
3602+ dispatch_block_t pendingCompletion;
3603+
35373604 if (completion)
35383605 {
3539- animationOptions.transitionFinishFn = [completion]() {
3540- dispatch_async (dispatch_get_main_queue (), ^{
3606+ __weak __typeof__ (self) weakSelf = self;
3607+
3608+ pendingCompletion = ^{
3609+ if (![weakSelf addPendingBlock: completion])
3610+ {
35413611 completion ();
3542- });
3612+ }
3613+ };
3614+
3615+ animationOptions.transitionFinishFn = [pendingCompletion]() {
3616+ dispatch_async (dispatch_get_main_queue (), pendingCompletion);
35433617 };
35443618 }
35453619
35463620 if ([self .camera isEqualToMapCamera: camera] && UIEdgeInsetsEqualToEdgeInsets (_contentInset, edgePadding))
35473621 {
3548- if (completion )
3622+ if (pendingCompletion )
35493623 {
3550- [self animateWithDelay: duration animations: ^{
3551- completion ();
3552- }];
3624+ [self animateWithDelay: duration animations: pendingCompletion];
35533625 }
35543626 return ;
35553627 }
@@ -3605,22 +3677,30 @@ - (void)_flyToCamera:(MGLMapCamera *)camera edgePadding:(UIEdgeInsets)insets wit
36053677 animationOptions.minZoom = MGLZoomLevelForAltitude (peakAltitude, peakPitch,
36063678 peakLatitude, self.frame .size );
36073679 }
3680+
3681+ dispatch_block_t pendingCompletion;
3682+
36083683 if (completion)
36093684 {
3610- animationOptions.transitionFinishFn = [completion]() {
3611- dispatch_async (dispatch_get_main_queue (), ^{
3685+ __weak __typeof__ (self) weakSelf = self;
3686+
3687+ pendingCompletion = ^{
3688+ if (![weakSelf addPendingBlock: completion])
3689+ {
36123690 completion ();
3613- });
3691+ }
3692+ };
3693+
3694+ animationOptions.transitionFinishFn = [pendingCompletion]() {
3695+ dispatch_async (dispatch_get_main_queue (), pendingCompletion);
36143696 };
36153697 }
36163698
36173699 if ([self .camera isEqualToMapCamera: camera] && UIEdgeInsetsEqualToEdgeInsets (_contentInset, insets))
36183700 {
3619- if (completion )
3701+ if (pendingCompletion )
36203702 {
3621- [self animateWithDelay: duration animations: ^{
3622- completion ();
3623- }];
3703+ [self animateWithDelay: duration animations: pendingCompletion];
36243704 }
36253705 return ;
36263706 }
0 commit comments