Fix didMoveToWindow logic. (#352)

This change fixes logic that we run when stack is moved to a window. Before we'd first attach stack to parent VC and then run updateContainer. This could've led to a buggy behavior in case the stack is mounted within other stack that is running a transition (e.g. dismissing a modal). In such a case setting initial push VCs would fail (because UIKit does not allow push to be modified while transitioning). Because of that stack would initialize with the dummy VC and the initial VC would get added to dismissedScreens (which is another side effect of the logic that keeps track of dismissed screens).

The fix was to add call to updateContainer before VC is added to parent. This makes it possible for push screens to be properly initialized (since VC is not yet added it does not know its parent is transitioning). Then, since updating modals handles gracefully the case when modals cannot be shown (and they wont be shown unless added to parent VC) we can safely run updateContainer yet another time after the VC is added to parent.

Howeer after the above fix was applied we observed another issue that was due to an invalida appear/disappear event management within view controllers. To address that fix we no longer allow navigaton VC's view to be added to the container view unless it starts transition to parent view controller. We also fixed missing calls to super in willMoveToParentViewController callback of RNSScreen controller and also fixed view reference management withing RNScreen to keep views as weak references while the view is not attached. The latter fixes the problem with screen view leaking under certain conditions.
This commit is contained in:
Krzysztof Magiera
2020-02-21 18:06:38 +01:00
committed by GitHub
parent c8845cbb6a
commit f2caf02d8c
3 changed files with 46 additions and 21 deletions

View File

@@ -46,6 +46,11 @@
// subviews
}
- (UIViewController *)reactViewController
{
return _controller;
}
- (void)updateBounds
{
[_bridge.uiManager setSize:self.bounds.size forView:self];
@@ -268,6 +273,7 @@
- (void)willMoveToParentViewController:(UIViewController *)parent
{
[super willMoveToParentViewController:parent];
if (parent == nil) {
id responder = [self findFirstResponder:self.view];
if (responder != nil) {
@@ -282,9 +288,9 @@
if (self.parentViewController == nil && self.presentingViewController == nil) {
// screen dismissed, send event
[((RNSScreenView *)self.view) notifyDismissed];
_view = self.view;
self.view = nil;
}
_view = self.view;
self.view = nil;
}
- (void)viewDidAppear:(BOOL)animated

View File

@@ -158,6 +158,7 @@
- (void)layoutSubviews
{
[super layoutSubviews];
[self reactAddControllerToClosestParent:_controller];
_controller.view.frame = self.bounds;
for (RNSScreenView *subview in _reactSubviews) {
subview.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
@@ -165,16 +166,6 @@
}
}
- (void)didMoveToWindow
{
if (self.window) {
[self reactAddControllerToClosestParent:_controller];
} else {
[_controller removeFromParentViewController];
[_controller didMoveToParentViewController:nil];
}
}
@end

View File

@@ -37,7 +37,6 @@
_dismissedScreens = [NSMutableSet new];
_controller = [[UINavigationController alloc] init];
_controller.delegate = self;
[self addSubview:_controller.view];
_controller.interactivePopGestureRecognizer.delegate = self;
// we have to initialize viewControllers with a non empty array for
@@ -49,6 +48,11 @@
return self;
}
- (UIViewController *)reactViewController
{
return _controller;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
UIView *view = viewController.view;
@@ -174,14 +178,35 @@
{
[super didMoveToWindow];
if (self.window) {
[self reactAddControllerToClosestParent:_controller];
// 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.
// when stack is attached to a window we do two things:
// 1) we run updateContainer we do this because we want push view controllers to be installed
// before the VC is mounted. If we do that after it is added to parent the push updates operations
// are going to be blocked by UIKit.
// 2) we add navigation VS to parent this is needed for the VC lifecycle events to be dispatched
// properly
// 3) we again call updateContainer this time we do this to open modal controllers. Modals
// won't open in (1) because they require navigator to be added to parent. We handle that case
// gracefully in setModalViewControllers and can retry opening at any point.
[self updateContainer];
} else {
[_controller removeFromParentViewController];
[_controller didMoveToParentViewController:nil];
[self reactAddControllerToClosestParent:_controller];
[self updateContainer];
}
}
- (void)reactAddControllerToClosestParent:(UIViewController *)controller
{
if (!controller.parentViewController) {
UIView *parentView = (UIView *)self.reactSuperview;
while (parentView) {
if (parentView.reactViewController) {
[parentView.reactViewController addChildViewController:controller];
[self addSubview:controller.view];
[controller didMoveToParentViewController:parentView.reactViewController];
break;
}
parentView = (UIView *)parentView.reactSuperview;
}
return;
}
}
@@ -247,7 +272,8 @@
[weakSelf.presentedModals
removeObjectsInRange:NSMakeRange(changeRootIndex, oldCount - changeRootIndex)];
}
if (changeRootController.view.window == nil || changeRootIndex >= controllers.count) {
BOOL isAttached = changeRootController.parentViewController != nil || changeRootController.presentingViewController != nil;
if (!isAttached || 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
@@ -371,6 +397,8 @@
[controller dismissViewControllerAnimated:NO completion:nil];
}
[_presentedModals removeAllObjects];
[_controller willMoveToParentViewController:nil];
[_controller removeFromParentViewController];
}
- (void)dismissOnReload