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
7 changes: 6 additions & 1 deletion ios/RNSScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ namespace react = facebook::react;
@property (nonatomic) BOOL hideKeyboardOnSwipe;
@property (nonatomic) BOOL customAnimationOnSwipe;
@property (nonatomic) BOOL preventNativeDismiss;
@property (nonatomic, retain) RNSScreen *controller;
@property (nonatomic, weak) RNSScreen *controller;
@property (nonatomic, copy) NSDictionary *gestureResponseDistance;
@property (nonatomic) int activityState;
@property (nonatomic, nullable) NSString *screenId;
Expand Down Expand Up @@ -213,6 +213,11 @@ namespace react = facebook::react;
*/
- (BOOL)registerContentWrapper:(nonnull RNSScreenContentWrapper *)contentWrapper contentHeightErrata:(float)errata;

/**
* Sets the retained controller to break the retain cycle.
*/
- (void)setRetainedController:(RNSScreen *_Nullable)controller;

@end

@interface UIView (RNSScreen)
Expand Down
61 changes: 59 additions & 2 deletions ios/RNSScreen.mm
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,36 @@
float contentHeightErrata{0.f};
};

@interface RNSWeakProxy : NSProxy

@property (nonatomic, weak, readonly) id target;

- (instancetype)initWithTarget:(id)target;

@end

@implementation RNSWeakProxy

- (instancetype)initWithTarget:(id)target
{
_target = target;
return self;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
return [_target methodSignatureForSelector:selector];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
if (_target) {
[invocation invokeWithTarget:_target];
}
}

@end

@interface RNSScreenView () <
UIAdaptivePresentationControllerDelegate,
#if !TARGET_OS_TV
Expand All @@ -69,6 +99,7 @@ @interface RNSScreenView () <

@implementation RNSScreenView {
__weak RNS_REACT_SCROLL_VIEW_COMPONENT *_sheetsScrollView;
RNSScreen *_retainedController;

/// Up-to-date only when sheet is in `fitToContents` mode.
CGFloat _sheetContentHeight;
Expand Down Expand Up @@ -121,7 +152,9 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge

- (void)initCommonProps
{
_controller = [[RNSScreen alloc] initWithView:self];
RNSScreen *controller = [[RNSScreen alloc] initWithView:self];
_controller = controller;
_retainedController = controller;
_stackPresentation = RNSScreenStackPresentationPush;
_stackAnimation = RNSScreenStackAnimationDefault;
_gestureEnabled = YES;
Expand Down Expand Up @@ -950,9 +983,15 @@ - (BOOL)isTransparentModal
- (void)invalidateImpl
{
_controller = nil;
_retainedController = nil;
[_sheetsScrollView removeObserver:self forKeyPath:@"bounds" context:nil];
}

- (void)setRetainedController:(RNSScreen *)controller
{
_retainedController = controller;
}

#ifndef RCT_NEW_ARCH_ENABLED
- (void)invalidate
{
Expand Down Expand Up @@ -1861,6 +1900,9 @@ - (void)willMoveToParentViewController:(UIViewController *)parent
_previousFirstResponder = responder;
}
} else {
// When moving to a parent controller, release the retained controller
// The parent will now hold a strong reference to this controller
[self.screenView setRetainedController:nil];
[self.screenView overrideScrollViewBehaviorInFirstDescendantChainIfNeeded];
[self.screenView updateContentScrollViewEdgeEffectsIfExists];
}
Expand All @@ -1884,6 +1926,11 @@ - (id)findFirstResponder:(UIView *)parent

- (void)setupProgressNotification
{
// Clean up any existing animation timer before setting up a new one
// This prevents memory leaks when viewWillDisappear is called multiple times
[_animationTimer invalidate];
_animationTimer = nil;

if (self.transitionCoordinator != nil) {
if (!self.transitionCoordinator.isAnimated) {
// If the transition is not animated, there is no point to set up animation
Expand All @@ -1898,7 +1945,9 @@ - (void)setupProgressNotification
auto animation = ^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[[context containerView] addSubview:self->_fakeView];
self->_fakeView.alpha = 1.0;
self->_animationTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleAnimation)];
// Use weak proxy to prevent retain cycle between CADisplayLink and RNSScreen
RNSWeakProxy *weakProxy = [[RNSWeakProxy alloc] initWithTarget:self];
self->_animationTimer = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(handleAnimation)];
[self->_animationTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
};

Expand All @@ -1907,6 +1956,7 @@ - (void)setupProgressNotification
completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self->_animationTimer setPaused:YES];
[self->_animationTimer invalidate];
self->_animationTimer = nil;
[self->_fakeView removeFromSuperview];
}];
}
Expand Down Expand Up @@ -2187,6 +2237,13 @@ - (void)setViewToSnapshot

#endif // RCT_NEW_ARCH_ENABLED

- (void)dealloc
{
// Clean up animation timer to prevent memory leaks
[_animationTimer invalidate];
_animationTimer = nil;
}

@end

@implementation RNSScreenManager
Expand Down