From 3db246994298c1066586d838a15b57216de558a5 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Sat, 10 Jan 2026 08:57:28 +0800 Subject: [PATCH] feat(ios): add RNSLifecycleListenerProtocol for screen lifecycle events - Add RNSLifecycleListenerProtocol for external view controllers to receive screen lifecycle events - Notify conforming presented view controllers in notifyWillDisappear - Preserve controller reference before invalidation to ensure notifications work during unmount - Pass isPresenterUnmounting flag to help external modals handle dismissal correctly --- ios/RNSScreen.mm | 25 +++++++++++++++++++ .../RNSLifecycleListenerProtocol.h | 14 +++++++++++ 2 files changed, 39 insertions(+) create mode 100644 ios/integrations/RNSLifecycleListenerProtocol.h diff --git a/ios/RNSScreen.mm b/ios/RNSScreen.mm index fdbe45af28..ca87266b5d 100644 --- a/ios/RNSScreen.mm +++ b/ios/RNSScreen.mm @@ -41,6 +41,7 @@ #import "RNSDefines.h" #import "UIView+RNSUtility.h" +#import "integrations/RNSLifecycleListenerProtocol.h" #ifdef RCT_NEW_ARCH_ENABLED namespace react = facebook::react; @@ -76,6 +77,7 @@ @implementation RNSScreenView { ContentWrapperBox _contentWrapperBox; bool _sheetHasInitialDetentSet; BOOL _shouldUpdateScrollEdgeEffects; + RNSScreen *_controllerBeforeInvalidate; #ifdef RCT_NEW_ARCH_ENABLED RCTSurfaceTouchHandler *_touchHandler; react::RNSScreenShadowNode::ConcreteState::Shared _state; @@ -630,6 +632,26 @@ - (void)notifyWillDisappear if (_hideKeyboardOnSwipe) { [self endEditing:YES]; } + + // Notify any presented view controllers that conform to RNSLifecycleListenerProtocol + RNSScreen *controller = _controller ?: _controllerBeforeInvalidate; + if (controller) { + UIViewController *presented = controller.presentedViewController; + while (presented) { + UIViewController *next = presented.presentedViewController; + if ([presented conformsToProtocol:@protocol(RNSLifecycleListenerProtocol)]) { + BOOL isPresenterUnmounting = NO; + RNSScreen *presenter = (RNSScreen *)presented.presentingViewController; + if ([presenter isKindOfClass:[RNSScreen class]]) { + isPresenterUnmounting = presenter.screenView.isMarkedForUnmountInCurrentTransaction; + } + [(id)presented screenWillDisappear:controller + isPresenterUnmounting:isPresenterUnmounting]; + } + presented = next; + } + } + #ifdef RCT_NEW_ARCH_ENABLED // If screen is already unmounted then there will be no event emitter if (_eventEmitter != nullptr) { @@ -965,6 +987,9 @@ - (BOOL)isTransparentModal - (void)invalidateImpl { + if (_controller && !_controllerBeforeInvalidate) { + _controllerBeforeInvalidate = _controller; + } _controller = nil; [_sheetsScrollView removeObserver:self forKeyPath:@"bounds" context:nil]; } diff --git a/ios/integrations/RNSLifecycleListenerProtocol.h b/ios/integrations/RNSLifecycleListenerProtocol.h new file mode 100644 index 0000000000..025b4231c0 --- /dev/null +++ b/ios/integrations/RNSLifecycleListenerProtocol.h @@ -0,0 +1,14 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol RNSLifecycleListenerProtocol + +// Called when a screen in the presenting hierarchy is about to disappear. +// @param screen The screen controller that is disappearing +// @param isPresenterUnmounting YES if the presenter (modal) itself is being unmounted +- (void)screenWillDisappear:(UIViewController *)screen isPresenterUnmounting:(BOOL)isPresenterUnmounting; + +@end + +NS_ASSUME_NONNULL_END