diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm index b77b87f469..89d3035daa 100644 --- a/ios/RNSScreenStack.mm +++ b/ios/RNSScreenStack.mm @@ -601,8 +601,10 @@ - (void)setModalViewControllers:(NSArray *)controllers // This check is for external modals that are not owned by this stack. They can prevent the dismissal of the modal by // extending RNSDismissibleModalProtocol and returning NO from isDismissible method. - if (![firstModalToBeDismissed conformsToProtocol:@protocol(RNSDismissibleModalProtocol)] || - [(id)firstModalToBeDismissed isDismissible]) { + BOOL shouldDismissFirstModal = ![firstModalToBeDismissed conformsToProtocol:@protocol(RNSDismissibleModalProtocol)] || + [(id)firstModalToBeDismissed isDismissible]; + + if (shouldDismissFirstModal) { if (firstModalToBeDismissed != nil) { const BOOL firstModalToBeDismissedIsOwned = [firstModalToBeDismissed isKindOfClass:RNSScreen.class]; const BOOL firstModalToBeDismissedIsOwnedByThisStack = @@ -660,6 +662,33 @@ - (void)setModalViewControllers:(NSArray *)controllers return; } } + } else { + // Modal is non-dismissible (e.g., third-party modal like TrueSheet) + // Check if the external modal provides a presenting controller + if (firstModalToBeDismissed != nil) { + id dismissibleModal = (id)firstModalToBeDismissed; + UIViewController *presentingController = nil; + + // Check if the external modal implements the optional method + if ([dismissibleModal respondsToSelector:@selector(newPresentingViewController)]) { + presentingController = [dismissibleModal newPresentingViewController]; + } + + // Only handle the non-dismissible modal if it provides a presenting controller + if (presentingController != nil) { + changeRootController = presentingController; + + // Check if the presenting controller has presented modals that need to be dismissed + UIViewController *modalPresentedByController = presentingController.presentedViewController; + if (modalPresentedByController != nil && ![modalPresentedByController isBeingDismissed] && + [_presentedModals containsObject:modalPresentedByController]) { + // The presenting controller has presented one of our modals + // We need to dismiss it before presenting new ones + [presentingController dismissViewControllerAnimated:YES completion:finish]; + return; + } + } + } } // We didn't detect any controllers for dismissal, thus we start presenting new VCs diff --git a/ios/integrations/RNSDismissibleModalProtocol.h b/ios/integrations/RNSDismissibleModalProtocol.h index dd08f3af77..07003a16db 100644 --- a/ios/integrations/RNSDismissibleModalProtocol.h +++ b/ios/integrations/RNSDismissibleModalProtocol.h @@ -1,5 +1,7 @@ #pragma once +#import + NS_ASSUME_NONNULL_BEGIN @protocol RNSDismissibleModalProtocol @@ -8,6 +10,13 @@ NS_ASSUME_NONNULL_BEGIN // Use it on your own responsibility, as it can lead to unexpected behavior. - (BOOL)isDismissible; +@optional +// If the modal is non-dismissible, it can optionally provide a view controller +// that should be used as the presenting controller for subsequent modals. +// This gives the external modal implementation control over the presentation chain. +// If not implemented or returns nil, the original implementation will continue. +- (nullable UIViewController *)newPresentingViewController; + @end NS_ASSUME_NONNULL_END