mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-24 04:25:34 +08:00
Update iOS native modals presenting logic to be more resilient (#342)
This update changes the logic of how we present modals. Previously presentedModals array could get out of sync with the actual app state because in case when parent VC is not mounted, presenting a modal would silently fail. In order to handle that we added additional update trigger from where the screen stack is mounted to a window. Second change that we added here addresses a problem of concurrent calls to update modals. As we cannot update presentedModals array synchrounously (all presentation callbacks are async even when no animation is involved), we need to block future updates unless the previous batch of updates is done. To do that we added a flag that prevents method re-entry. The flag is reset when all the presentation animations are over.
This commit is contained in:
committed by
GitHub
parent
cce8373a20
commit
3193e7da8f
@@ -10,6 +10,11 @@
|
||||
#import <React/RCTTouchHandler.h>
|
||||
|
||||
@interface RNSScreenStackView () <UINavigationControllerDelegate, UIAdaptivePresentationControllerDelegate, UIGestureRecognizerDelegate>
|
||||
|
||||
@property (nonatomic) NSMutableArray<UIViewController *> *presentedModals;
|
||||
@property (nonatomic) BOOL updatingModals;
|
||||
@property (nonatomic) BOOL scheduleModalsUpdate;
|
||||
|
||||
@end
|
||||
|
||||
@interface RNSScreenStackAnimator : NSObject <UIViewControllerAnimatedTransitioning>
|
||||
@@ -21,7 +26,6 @@
|
||||
UINavigationController *_controller;
|
||||
NSMutableArray<RNSScreenView *> *_reactSubviews;
|
||||
NSMutableSet<RNSScreenView *> *_dismissedScreens;
|
||||
NSMutableArray<UIViewController *> *_presentedModals;
|
||||
__weak RNSScreenStackManager *_manager;
|
||||
}
|
||||
|
||||
@@ -160,10 +164,20 @@
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
// do nothing
|
||||
[self updateContainer];
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
if (self.window) {
|
||||
// when stack is added to a window we try to update push and modal view controllers. It is
|
||||
// because modal operations are blocked by UIKit when parent VC is not mounted, so we need
|
||||
// to redo them when the stack is attached.
|
||||
[self updateContainer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setModalViewControllers:(NSArray<UIViewController *> *)controllers
|
||||
{
|
||||
// when there is no change we return immediately. This check is important because sometime we may
|
||||
@@ -197,26 +211,57 @@
|
||||
}
|
||||
}
|
||||
|
||||
// prevent re-entry
|
||||
if (_updatingModals) {
|
||||
_scheduleModalsUpdate = YES;
|
||||
return;
|
||||
}
|
||||
_updatingModals = YES;
|
||||
|
||||
__weak RNSScreenStackView *weakSelf = self;
|
||||
|
||||
void (^dispatchFinishTransitioning)(void) = ^{
|
||||
void (^afterTransitions)(void) = ^{
|
||||
if (weakSelf.onFinishTransitioning) {
|
||||
weakSelf.onFinishTransitioning(nil);
|
||||
}
|
||||
weakSelf.updatingModals = NO;
|
||||
if (weakSelf.scheduleModalsUpdate) {
|
||||
// if modals update was requested during setModalViewControllers we set scheduleModalsUpdate
|
||||
// flag in order to perform updates at a later point. Here we are done with all modals
|
||||
// transitions and check this flag again. If it was set, we reset the flag and execute updates.
|
||||
weakSelf.scheduleModalsUpdate = NO;
|
||||
[weakSelf updateContainer];
|
||||
}
|
||||
};
|
||||
|
||||
void (^finish)(void) = ^{
|
||||
UIViewController *previous = changeRootController;
|
||||
for (NSUInteger i = changeRootIndex; i < controllers.count; i++) {
|
||||
UIViewController *next = controllers[i];
|
||||
BOOL animate = (i == controllers.count - 1);
|
||||
[previous presentViewController:next
|
||||
animated:animate
|
||||
completion:animate ? dispatchFinishTransitioning : nil];
|
||||
previous = next;
|
||||
NSUInteger oldCount = weakSelf.presentedModals.count;
|
||||
if (changeRootIndex < oldCount) {
|
||||
[weakSelf.presentedModals
|
||||
removeObjectsInRange:NSMakeRange(changeRootIndex, oldCount - changeRootIndex)];
|
||||
}
|
||||
if (changeRootIndex >= controllers.count) {
|
||||
dispatchFinishTransitioning();
|
||||
if (changeRootController.view.window == nil || changeRootIndex >= controllers.count) {
|
||||
// if change controller view is not attached, presenting modals will silently fail on iOS.
|
||||
// In such a case we trigger controllers update from didMoveToWindow.
|
||||
// We also don't run any present transitions if changeRootIndex is greater or equal to the size
|
||||
// of new controllers array. This means that no new controllers should be presented.
|
||||
afterTransitions();
|
||||
return;
|
||||
} else {
|
||||
UIViewController *previous = changeRootController;
|
||||
for (NSUInteger i = changeRootIndex; i < controllers.count; i++) {
|
||||
UIViewController *next = controllers[i];
|
||||
BOOL lastModal = (i == controllers.count - 1);
|
||||
[previous presentViewController:next
|
||||
animated:lastModal
|
||||
completion:^{
|
||||
[weakSelf.presentedModals addObject:next];
|
||||
if (lastModal) {
|
||||
afterTransitions();
|
||||
};
|
||||
}];
|
||||
previous = next;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -227,7 +272,6 @@
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
[_presentedModals setArray:controllers];
|
||||
}
|
||||
|
||||
- (void)setPushViewControllers:(NSArray<UIViewController *> *)controllers
|
||||
|
||||
Reference in New Issue
Block a user