Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions ios/RNSScreenStack.mm
Original file line number Diff line number Diff line change
Expand Up @@ -601,8 +601,10 @@ - (void)setModalViewControllers:(NSArray<UIViewController *> *)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<RNSDismissibleModalProtocol>)firstModalToBeDismissed isDismissible]) {
BOOL shouldDismissFirstModal = ![firstModalToBeDismissed conformsToProtocol:@protocol(RNSDismissibleModalProtocol)] ||
[(id<RNSDismissibleModalProtocol>)firstModalToBeDismissed isDismissible];

if (shouldDismissFirstModal) {
if (firstModalToBeDismissed != nil) {
const BOOL firstModalToBeDismissedIsOwned = [firstModalToBeDismissed isKindOfClass:RNSScreen.class];
const BOOL firstModalToBeDismissedIsOwnedByThisStack =
Expand Down Expand Up @@ -660,6 +662,33 @@ - (void)setModalViewControllers:(NSArray<UIViewController *> *)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<RNSDismissibleModalProtocol> dismissibleModal = (id<RNSDismissibleModalProtocol>)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
Expand Down
9 changes: 9 additions & 0 deletions ios/integrations/RNSDismissibleModalProtocol.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@protocol RNSDismissibleModalProtocol <NSObject>
Expand All @@ -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