From b1b352d52bde882d8c50b8d1d9ef5d1a45ec14d6 Mon Sep 17 00:00:00 2001 From: Rick van der Linden Date: Thu, 5 Mar 2026 15:07:13 +0100 Subject: [PATCH] Fix Paywall component blank screen due to layout timing The #1622 fix introduced a timing dependency where layoutSubviews requires both a parentViewController and didReceiveInitialOptions to add the paywall to the view hierarchy. If setOptions: arrives after the initial layout pass and setNeedsLayout doesn't reliably trigger another layoutSubviews (e.g., zero frame, Fabric batching), the paywall never renders. Two changes: - Add didMoveToWindow override to retry adding the paywall when the view enters a window and gains a parentViewController - Use layoutIfNeeded after setNeedsLayout in setOptions: to force a synchronous layout pass --- .../ios/PaywallViewWrapper.m | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/react-native-purchases-ui/ios/PaywallViewWrapper.m b/react-native-purchases-ui/ios/PaywallViewWrapper.m index 9a89d54cd..5dd3f5431 100644 --- a/react-native-purchases-ui/ios/PaywallViewWrapper.m +++ b/react-native-purchases-ui/ios/PaywallViewWrapper.m @@ -53,6 +53,20 @@ - (void)reactSetFrame:(CGRect)frame [super reactSetFrame: frame]; } +- (void)didMoveToWindow { + [super didMoveToWindow]; + + // When the view is added to a window, it gains a parentViewController via the + // responder chain. If options were already received but the view wasn't in the + // hierarchy yet (parentViewController was nil), we need to retry adding the + // paywall view controller now. + // See: https://github.com/RevenueCat/react-native-purchases/issues/1644 + if (self.window && self.didReceiveInitialOptions && !self.addedToHierarchy) { + [self setNeedsLayout]; + [self layoutIfNeeded]; + } +} + - (void)layoutSubviews { [super layoutSubviews]; @@ -130,7 +144,12 @@ - (void)setOptions:(NSDictionary *)options { } else { // View is not yet in hierarchy, trigger layout to apply options. // Custom variables will be applied before adding to hierarchy in layoutSubviews. + // Use setNeedsLayout + layoutIfNeeded to force a synchronous layout pass, + // because setNeedsLayout alone may not reliably trigger layoutSubviews + // (e.g., when the view has a zero frame or under Fabric's layout batching). + // See: https://github.com/RevenueCat/react-native-purchases/issues/1644 [self setNeedsLayout]; + [self layoutIfNeeded]; } } else { NSLog(@"Error: attempted to present paywalls on unsupported iOS version.");