diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt index 0fcccb3850..c0a6978119 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt @@ -385,8 +385,17 @@ open class ScreenContainer( "fragment manager is null when performing update in ScreenContainer" }.fragments, ) + + // When activityState is driven by Animated.Value interpolations (as react-navigation + // does for tab animations), each screen's value updates independently, frame by frame. + // Rapid tab switching can create a transient window where ALL screens are inactive + // simultaneously — the leaving screen crossed the threshold while the arriving one + // has not yet risen above it. Only detach when at least one screen remains visible. + val hasVisibleScreen = screenWrappers.any { getActivityState(it) !== ActivityState.INACTIVE } + for (fragmentWrapper in screenWrappers) { - if (getActivityState(fragmentWrapper) === ActivityState.INACTIVE && + if (hasVisibleScreen && + getActivityState(fragmentWrapper) === ActivityState.INACTIVE && fragmentWrapper.fragment.isAdded ) { detachScreen(it, fragmentWrapper.fragment) diff --git a/ios/RNSScreenContainer.mm b/ios/RNSScreenContainer.mm index 86633d937c..3d51fdeac3 100644 --- a/ios/RNSScreenContainer.mm +++ b/ios/RNSScreenContainer.mm @@ -160,8 +160,23 @@ - (void)updateContainer BOOL screenRemoved = NO; // remove screens that are no longer active NSMutableSet *orphaned = [NSMutableSet setWithSet:_activeScreens]; + + // When activityState is driven by Animated.Value interpolations (as react-navigation does + // for tab animations), each screen's value updates independently, frame by frame. Rapid tab + // switching can create a transient window where ALL screens are inactive simultaneously — + // the leaving screen has already crossed the threshold while the arriving screen has not yet + // risen above it. Only detach when at least one screen remains visible to avoid a blank flash. + BOOL hasVisibleScreen = NO; + for (RNSScreenView *screen in _reactSubviews) { + if (screen.activityState != RNSActivityStateInactive) { + hasVisibleScreen = YES; + break; + } + } + for (RNSScreenView *screen in _reactSubviews) { - if (screen.activityState == RNSActivityStateInactive && [_activeScreens containsObject:screen]) { + if (hasVisibleScreen && screen.activityState == RNSActivityStateInactive && + [_activeScreens containsObject:screen]) { screenRemoved = YES; [self detachScreen:screen]; }