diff --git a/ios/gamma/modals/form-sheet/RNSFormSheetAppearanceApplicator.h b/ios/gamma/modals/form-sheet/RNSFormSheetAppearanceApplicator.h index 727d909554..d1ae624dfb 100644 --- a/ios/gamma/modals/form-sheet/RNSFormSheetAppearanceApplicator.h +++ b/ios/gamma/modals/form-sheet/RNSFormSheetAppearanceApplicator.h @@ -1,18 +1,19 @@ #pragma once #import +#import "RNSFormSheetProviders.h" @class RNSFormSheetAppearanceCoordinator; @class RNSFormSheetContentController; -@class RNSFormSheetHostComponentView; NS_ASSUME_NONNULL_BEGIN @interface RNSFormSheetAppearanceApplicator : NSObject -- (void)updateAppearanceIfNeededForHost:(RNSFormSheetHostComponentView *)host - controller:(RNSFormSheetContentController *)controller - coordinator:(RNSFormSheetAppearanceCoordinator *)coordinator; +- (void)updateAppearanceIfNeededWithAppearanceProvider:(id)appearanceProvider + behaviorProvider:(id)behaviorProvider + controller:(RNSFormSheetContentController *)controller + coordinator:(RNSFormSheetAppearanceCoordinator *)coordinator; - (void)resetInitialDetent; diff --git a/ios/gamma/modals/form-sheet/RNSFormSheetAppearanceApplicator.mm b/ios/gamma/modals/form-sheet/RNSFormSheetAppearanceApplicator.mm index 5da37e5d7b..1d948d2765 100644 --- a/ios/gamma/modals/form-sheet/RNSFormSheetAppearanceApplicator.mm +++ b/ios/gamma/modals/form-sheet/RNSFormSheetAppearanceApplicator.mm @@ -6,7 +6,6 @@ #import "RNSFormSheetAppearanceUpdateFlags.h" #import "RNSFormSheetContentController.h" #import "RNSFormSheetDetentResolver.h" -#import "RNSFormSheetHostComponentView.h" @implementation RNSFormSheetAppearanceApplicator { BOOL _initialDetentApplied; @@ -25,20 +24,24 @@ - (void)resetInitialDetent _initialDetentApplied = NO; } -- (void)updateAppearanceIfNeededForHost:(RNSFormSheetHostComponentView *)host - controller:(RNSFormSheetContentController *)controller - coordinator:(RNSFormSheetAppearanceCoordinator *)coordinator +- (void)updateAppearanceIfNeededWithAppearanceProvider:(id)appearanceProvider + behaviorProvider:(id)behaviorProvider + controller:(RNSFormSheetContentController *)controller + coordinator:(RNSFormSheetAppearanceCoordinator *)coordinator { [coordinator updateIfNeeds:RNSFormSheetAppearanceUpdateFlagsConfiguration performOperations:^{ - [self updateSheetConfigurationForHost:host controller:controller]; + [self updateSheetConfigurationForAppearanceProvider:appearanceProvider + behaviorProvider:behaviorProvider + controller:controller]; }]; } #pragma mark - Updaters -- (void)updateSheetConfigurationForHost:(RNSFormSheetHostComponentView *)host - controller:(RNSFormSheetContentController *)controller +- (void)updateSheetConfigurationForAppearanceProvider:(id)appearanceProvider + behaviorProvider:(id)behaviorProvider + controller:(RNSFormSheetContentController *)controller { #if !TARGET_OS_TV UISheetPresentationController *sheet = controller.sheetPresentationController; @@ -47,22 +50,23 @@ - (void)updateSheetConfigurationForHost:(RNSFormSheetHostComponentView *)host @"[RNScreens] sheetPresentationController is nil. Ensure modalPresentationStyle is set to UIModalPresentationFormSheet."); NSArray *nativeDetents = - [RNSFormSheetDetentResolver buildSheetDetentsForFractions:host.detents]; + [RNSFormSheetDetentResolver buildSheetDetentsForFractions:behaviorProvider.detents]; UISheetPresentationControllerDetentIdentifier initialDetentIdentifier = nil; if (!_initialDetentApplied) { - initialDetentIdentifier = [RNSFormSheetDetentResolver initialDetentIdentifierForDetents:nativeDetents - atRequestedIndex:host.initialDetentIndex]; + initialDetentIdentifier = + [RNSFormSheetDetentResolver initialDetentIdentifierForDetents:nativeDetents + atRequestedIndex:behaviorProvider.initialDetentIndex]; _initialDetentApplied = YES; } - UISheetPresentationControllerDetentIdentifier largestUndimmedDetentIdentifier = - [RNSFormSheetDetentResolver largestUndimmedDetentIdentifierForDetents:nativeDetents - atRequestedIndex:host.largestUndimmedDetentIndex]; + UISheetPresentationControllerDetentIdentifier largestUndimmedDetentIdentifier = [RNSFormSheetDetentResolver + largestUndimmedDetentIdentifierForDetents:nativeDetents + atRequestedIndex:appearanceProvider.largestUndimmedDetentIndex]; - BOOL prefersGrabberVisible = host.prefersGrabberVisible; - CGFloat preferredCornerRadius = host.preferredCornerRadius; - BOOL prefersScrollingExpandsWhenScrolledToEdge = host.prefersScrollingExpandsWhenScrolledToEdge; + BOOL prefersGrabberVisible = appearanceProvider.prefersGrabberVisible; + CGFloat preferredCornerRadius = appearanceProvider.preferredCornerRadius; + BOOL prefersScrollingExpandsWhenScrolledToEdge = behaviorProvider.prefersScrollingExpandsWhenScrolledToEdge; [sheet animateChanges:^{ sheet.detents = nativeDetents; diff --git a/ios/gamma/modals/form-sheet/RNSFormSheetContentController.h b/ios/gamma/modals/form-sheet/RNSFormSheetContentController.h index ae48ae2e95..07c6aae481 100644 --- a/ios/gamma/modals/form-sheet/RNSFormSheetContentController.h +++ b/ios/gamma/modals/form-sheet/RNSFormSheetContentController.h @@ -1,6 +1,7 @@ #pragma once #import +#import "RNSFormSheetProviders.h" NS_ASSUME_NONNULL_BEGIN @@ -20,10 +21,21 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak, nullable) id delegate; +@property (nonatomic, weak, nullable) id presentationProvider; +@property (nonatomic, weak, nullable) id appearanceProvider; +@property (nonatomic, weak, nullable) id behaviorProvider; + @property (nonatomic, readonly, nonnull) RNSFormSheetContentView *contentView; -- (void)presentFromWindowIfNeeded:(nonnull UIWindow *)window; -- (void)dismissIfNeeded; +#pragma mark - Signals + +- (void)setNeedsPresentationUpdate; +- (void)setNeedsAppearanceUpdate; +- (void)setNeedsInitialDetentReset; + +#pragma mark - Updates + +- (void)flushPendingUpdates; @end diff --git a/ios/gamma/modals/form-sheet/RNSFormSheetContentController.mm b/ios/gamma/modals/form-sheet/RNSFormSheetContentController.mm index 129c3fca5d..db6ec60ed2 100644 --- a/ios/gamma/modals/form-sheet/RNSFormSheetContentController.mm +++ b/ios/gamma/modals/form-sheet/RNSFormSheetContentController.mm @@ -1,4 +1,7 @@ #import "RNSFormSheetContentController.h" +#import "RNSFormSheetAppearanceApplicator.h" +#import "RNSFormSheetAppearanceCoordinator.h" +#import "RNSFormSheetAppearanceUpdateFlags.h" #import "RNSFormSheetContentView.h" #import "RNSPresentationSourceProvider.h" @@ -13,12 +16,22 @@ @interface RNSFormSheetContentController () @end -@implementation RNSFormSheetContentController +@implementation RNSFormSheetContentController { + RNSFormSheetAppearanceCoordinator *_Nonnull _appearanceCoordinator; + RNSFormSheetAppearanceApplicator *_Nonnull _appearanceApplicator; + + BOOL _needsInitialDetentReset; +} - (instancetype)init { if (self = [super init]) { self.modalPresentationStyle = UIModalPresentationFormSheet; + + _appearanceCoordinator = [RNSFormSheetAppearanceCoordinator new]; + _appearanceApplicator = [RNSFormSheetAppearanceApplicator new]; + + _needsInitialDetentReset = NO; } return self; } @@ -46,6 +59,27 @@ - (void)viewDidLayoutSubviews #pragma mark - Presentation +- (void)updatePresentationState +{ + id presentationProvider = self.presentationProvider; + + RCTAssert(presentationProvider != nil, + @"[RNScreens] Presentation provider must be set before updating presentation state."); + + if (presentationProvider == nil) { + return; + } + + if (presentationProvider.isOpen) { + UIWindow *window = presentationProvider.hostWindow; + if (window != nil) { + [self presentFromWindowIfNeeded:window]; + } + } else { + [self dismissIfNeeded]; + } +} + - (void)prepareForPresentation { // The presentation controller is recreated by UIKit on every present/dismiss cycle. @@ -88,6 +122,62 @@ - (void)dismissIfNeeded [self dismissViewControllerAnimated:YES completion:nil]; } +#pragma mark - Appearance + +- (void)updateAppearanceIfNeeded +{ + id appearanceProvider = self.appearanceProvider; + id behaviorProvider = self.behaviorProvider; + + RCTAssert(appearanceProvider != nil, @"[RNScreens] Appearance provider must be set before updating appearance."); + + RCTAssert(behaviorProvider != nil, @"[RNScreens] Behavior provider must be set before updating appearance."); + + if (appearanceProvider == nil || behaviorProvider == nil) { + return; + } + + if (_needsInitialDetentReset) { + _needsInitialDetentReset = NO; + [_appearanceApplicator resetInitialDetent]; + } + + [_appearanceApplicator updateAppearanceIfNeededWithAppearanceProvider:appearanceProvider + behaviorProvider:behaviorProvider + controller:self + coordinator:_appearanceCoordinator]; + + // TODO: @t0maboro - decouple presentation logic from AppearanceCoordinator + [_appearanceCoordinator updateIfNeeds:RNSFormSheetAppearanceUpdateFlagsPresentation + performOperations:^{ + [self updatePresentationState]; + }]; +} + +#pragma mark - Signals + +- (void)setNeedsPresentationUpdate +{ + [_appearanceCoordinator setNeeds:RNSFormSheetAppearanceUpdateFlagsPresentation]; +} + +- (void)setNeedsAppearanceUpdate +{ + [_appearanceCoordinator setNeeds:RNSFormSheetAppearanceUpdateFlagsConfiguration]; +} + +- (void)setNeedsInitialDetentReset +{ + _needsInitialDetentReset = YES; +} + +#pragma mark - Updates + +- (void)flushPendingUpdates +{ + [self updateAppearanceIfNeeded]; +} + #pragma mark - UIAdaptivePresentationControllerDelegate - (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController diff --git a/ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.h b/ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.h index dcd05051e1..2f9853382c 100644 --- a/ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.h +++ b/ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN - (const std::vector &)detents; #endif +@property (nonatomic, readonly) BOOL isOpen; @property (nonatomic, readonly) BOOL prefersGrabberVisible; @property (nonatomic, readonly) CGFloat preferredCornerRadius; @property (nonatomic, readonly) NSInteger largestUndimmedDetentIndex; diff --git a/ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.mm b/ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.mm index e93235f2a4..a9763f4e0c 100644 --- a/ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.mm +++ b/ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.mm @@ -1,13 +1,12 @@ #import "RNSFormSheetHostComponentView.h" -#import "RNSDefines.h" -#import "RNSFormSheetAppearanceApplicator.h" -#import "RNSFormSheetAppearanceCoordinator.h" #import "RNSFormSheetContentController.h" #import "RNSFormSheetContentView.h" #import "RNSFormSheetDetentResolver.h" #import "RNSFormSheetHostEventEmitter.h" #import "RNSFormSheetHostShadowStateProxy.h" +#import "RNSFormSheetProviders.h" +#import #import #import #import @@ -15,14 +14,16 @@ namespace react = facebook::react; -@interface RNSFormSheetHostComponentView () +@interface RNSFormSheetHostComponentView () @end @implementation RNSFormSheetHostComponentView { RNSFormSheetHostEventEmitter *_Nonnull _reactEventEmitter; RNSFormSheetHostShadowStateProxy *_Nonnull _shadowStateProxy; - RNSFormSheetAppearanceCoordinator *_Nonnull _appearanceCoordinator; - RNSFormSheetAppearanceApplicator *_Nonnull _appearanceApplicator; RNSFormSheetContentController *_Nullable _controller; RCTSurfaceTouchHandler *_Nullable _touchHandler; @@ -30,6 +31,11 @@ @implementation RNSFormSheetHostComponentView { // Props BOOL _isOpen; std::vector _detents; + BOOL _prefersGrabberVisible; + CGFloat _preferredCornerRadius; + NSInteger _largestUndimmedDetentIndex; + NSInteger _initialDetentIndex; + BOOL _prefersScrollingExpandsWhenScrolledToEdge; } - (instancetype)initWithFrame:(CGRect)frame @@ -47,8 +53,6 @@ - (void)initState _reactEventEmitter = [RNSFormSheetHostEventEmitter new]; _shadowStateProxy = [RNSFormSheetHostShadowStateProxy new]; - _appearanceCoordinator = [RNSFormSheetAppearanceCoordinator new]; - _appearanceApplicator = [RNSFormSheetAppearanceApplicator new]; } - (void)resetProps @@ -65,32 +69,38 @@ - (void)resetProps _prefersScrollingExpandsWhenScrolledToEdge = YES; } -- (const std::vector &)detents -{ - return _detents; -} - - (void)setupController { _controller = [RNSFormSheetContentController new]; _controller.delegate = self; + + _controller.presentationProvider = self; + _controller.appearanceProvider = self; + _controller.behaviorProvider = self; } -- (void)updatePresentationState +- (void)didMoveToWindow { - if (_isOpen) { - if (self.window != nil) { - [_controller presentFromWindowIfNeeded:self.window]; - } - } else { - [_controller dismissIfNeeded]; + [super didMoveToWindow]; + + if (self.window != nil) { + [_controller setNeedsPresentationUpdate]; + [_controller flushPendingUpdates]; } } -- (void)didMoveToWindow +#pragma mark - RNSFormSheetPresentationProvider + +- (nullable UIWindow *)hostWindow { - [super didMoveToWindow]; - [self updatePresentationState]; + return self.window; +} + +#pragma mark - RNSFormSheetBehaviorProvider + +- (const std::vector &)detents +{ + return _detents; } #pragma mark - RNSFormSheetContentControllerDelegate @@ -161,36 +171,36 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props if (oldComponentProps.isOpen != newComponentProps.isOpen) { _isOpen = static_cast(newComponentProps.isOpen); - [_appearanceCoordinator setNeeds:RNSFormSheetAppearanceUpdateFlagsPresentation]; + [_controller setNeedsPresentationUpdate]; if (_isOpen) { // ALWAYS refresh the sheet configuration when reopening, // because UIKit destroys the presentationController after the modal is dismissed. - [_appearanceCoordinator setNeeds:RNSFormSheetAppearanceUpdateFlagsConfiguration]; + [_controller setNeedsAppearanceUpdate]; // Reset the initial-detent applied flag when reopening so the // configured initialDetentIndex can be applied again. - [_appearanceApplicator resetInitialDetent]; + [_controller setNeedsInitialDetentReset]; } } if (oldComponentProps.detents != newComponentProps.detents) { _detents = newComponentProps.detents; - [_appearanceCoordinator setNeeds:RNSFormSheetAppearanceUpdateFlagsConfiguration]; + [_controller setNeedsAppearanceUpdate]; } if (oldComponentProps.prefersGrabberVisible != newComponentProps.prefersGrabberVisible) { _prefersGrabberVisible = newComponentProps.prefersGrabberVisible; - [_appearanceCoordinator setNeeds:RNSFormSheetAppearanceUpdateFlagsConfiguration]; + [_controller setNeedsAppearanceUpdate]; } if (oldComponentProps.preferredCornerRadius != newComponentProps.preferredCornerRadius) { _preferredCornerRadius = newComponentProps.preferredCornerRadius; - [_appearanceCoordinator setNeeds:RNSFormSheetAppearanceUpdateFlagsConfiguration]; + [_controller setNeedsAppearanceUpdate]; } if (oldComponentProps.largestUndimmedDetentIndex != newComponentProps.largestUndimmedDetentIndex) { _largestUndimmedDetentIndex = newComponentProps.largestUndimmedDetentIndex; - [_appearanceCoordinator setNeeds:RNSFormSheetAppearanceUpdateFlagsConfiguration]; + [_controller setNeedsAppearanceUpdate]; } if (oldComponentProps.initialDetentIndex != newComponentProps.initialDetentIndex) { @@ -201,26 +211,12 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props newComponentProps.prefersScrollingExpandsWhenScrolledToEdge) { _prefersScrollingExpandsWhenScrolledToEdge = static_cast(newComponentProps.prefersScrollingExpandsWhenScrolledToEdge); - [_appearanceCoordinator setNeeds:RNSFormSheetAppearanceUpdateFlagsConfiguration]; + [_controller setNeedsAppearanceUpdate]; } [super updateProps:props oldProps:oldProps]; } -- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask -{ - [super finalizeUpdates:updateMask]; - - [_appearanceApplicator updateAppearanceIfNeededForHost:self - controller:_controller - coordinator:_appearanceCoordinator]; - - [_appearanceCoordinator updateIfNeeds:RNSFormSheetAppearanceUpdateFlagsPresentation - performOperations:^{ - [self updatePresentationState]; - }]; -} - - (void)invalidate { if (_touchHandler != nil) { @@ -236,6 +232,14 @@ - (void)invalidate } } +#pragma mark - RCTMountingTransactionObserving + +- (void)mountingTransactionDidMount:(const facebook::react::MountingTransaction &)transaction + withSurfaceTelemetry:(const facebook::react::SurfaceTelemetry &)surfaceTelemetry +{ + [_controller flushPendingUpdates]; +} + #pragma mark - Layout helpers - (void)syncShadowNodeState diff --git a/ios/gamma/modals/form-sheet/RNSFormSheetProviders.h b/ios/gamma/modals/form-sheet/RNSFormSheetProviders.h new file mode 100644 index 0000000000..f005af4b90 --- /dev/null +++ b/ios/gamma/modals/form-sheet/RNSFormSheetProviders.h @@ -0,0 +1,36 @@ +#pragma once + +#import + +#ifdef __cplusplus +#include +#endif + +NS_ASSUME_NONNULL_BEGIN + +@protocol RNSFormSheetPresentationProvider + +- (BOOL)isOpen; +- (nullable UIWindow *)hostWindow; + +@end + +@protocol RNSFormSheetAppearanceProvider + +- (BOOL)prefersGrabberVisible; +- (CGFloat)preferredCornerRadius; +- (NSInteger)largestUndimmedDetentIndex; + +@end + +@protocol RNSFormSheetBehaviorProvider + +#ifdef __cplusplus +- (const std::vector &)detents; +#endif +- (NSInteger)initialDetentIndex; +- (BOOL)prefersScrollingExpandsWhenScrolledToEdge; + +@end + +NS_ASSUME_NONNULL_END