/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "RCTModalHostViewComponentView.h" #import #import #import #import #import #import #import #import "RCTConversions.h" #import "RCTFabricModalHostViewController.h" using namespace facebook::react; static UIInterfaceOrientationMask supportedOrientationsMask(ModalHostViewSupportedOrientationsMask mask) { UIInterfaceOrientationMask supportedOrientations = 0; if (mask & ModalHostViewSupportedOrientations::Portrait) { supportedOrientations |= UIInterfaceOrientationMaskPortrait; } if (mask & ModalHostViewSupportedOrientations::PortraitUpsideDown) { supportedOrientations |= UIInterfaceOrientationMaskPortraitUpsideDown; } if (mask & ModalHostViewSupportedOrientations::Landscape) { supportedOrientations |= UIInterfaceOrientationMaskLandscape; } if (mask & ModalHostViewSupportedOrientations::LandscapeLeft) { supportedOrientations |= UIInterfaceOrientationMaskLandscapeLeft; } if (mask & ModalHostViewSupportedOrientations::LandscapeRight) { supportedOrientations |= UIInterfaceOrientationMaskLandscapeRight; } if (supportedOrientations == 0) { if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { return UIInterfaceOrientationMaskAll; } else { return UIInterfaceOrientationMaskPortrait; } } return supportedOrientations; } static std::tuple animationConfiguration(ModalHostViewAnimationType const animation) { switch (animation) { case ModalHostViewAnimationType::None: return std::make_tuple(NO, UIModalTransitionStyleCoverVertical); case ModalHostViewAnimationType::Slide: return std::make_tuple(YES, UIModalTransitionStyleCoverVertical); case ModalHostViewAnimationType::Fade: return std::make_tuple(YES, UIModalTransitionStyleCrossDissolve); } } static UIModalPresentationStyle presentationConfiguration(ModalHostViewProps const &props) { if (props.transparent) { return UIModalPresentationOverFullScreen; } switch (props.presentationStyle) { case ModalHostViewPresentationStyle::FullScreen: return UIModalPresentationFullScreen; case ModalHostViewPresentationStyle::PageSheet: return UIModalPresentationPageSheet; case ModalHostViewPresentationStyle::FormSheet: return UIModalPresentationFormSheet; case ModalHostViewPresentationStyle::OverFullScreen: return UIModalPresentationOverFullScreen; } } static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(CGRect rect) { ; auto orientation = rect.size.width < rect.size.height ? ModalHostViewEventEmitter::OnOrientationChangeOrientation::Portrait : ModalHostViewEventEmitter::OnOrientationChangeOrientation::Landscape; return {orientation}; } @interface RCTModalHostViewComponentView () @end @implementation RCTModalHostViewComponentView { RCTFabricModalHostViewController *_viewController; ModalHostViewShadowNode::ConcreteState::Shared _state; BOOL _shouldAnimatePresentation; BOOL _shouldPresent; BOOL _isPresented; UIView *_modalContentsSnapshot; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { static const auto defaultProps = std::make_shared(); _props = defaultProps; _shouldAnimatePresentation = YES; _isPresented = NO; } return self; } - (RCTFabricModalHostViewController *)viewController { if (!_viewController) { _viewController = [RCTFabricModalHostViewController new]; _viewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; _viewController.delegate = self; } return _viewController; } - (void)presentViewController:(UIViewController *)modalViewController animated:(BOOL)animated completion:(void (^)(void))completion { UIViewController *controller = [self reactViewController]; [controller presentViewController:modalViewController animated:animated completion:completion]; } - (void)dismissViewController:(UIViewController *)modalViewController animated:(BOOL)animated completion:(void (^)(void))completion { [modalViewController dismissViewControllerAnimated:animated completion:completion]; } - (void)ensurePresentedOnlyIfNeeded { BOOL shouldBePresented = !_isPresented && _shouldPresent && self.window; if (shouldBePresented) { _isPresented = YES; [self presentViewController:self.viewController animated:_shouldAnimatePresentation completion:^{ auto eventEmitter = [self modalEventEmitter]; if (eventEmitter) { eventEmitter->onShow(ModalHostViewEventEmitter::OnShow{}); } }]; } BOOL shouldBeHidden = _isPresented && (!_shouldPresent || !self.superview); if (shouldBeHidden) { _isPresented = NO; // To animate dismissal of view controller, snapshot of // view hierarchy needs to be added to the UIViewController. UIView *snapshot = _modalContentsSnapshot; [self.viewController.view addSubview:snapshot]; [self dismissViewController:self.viewController animated:_shouldAnimatePresentation completion:^{ [snapshot removeFromSuperview]; auto eventEmitter = [self modalEventEmitter]; if (eventEmitter) { eventEmitter->onDismiss(ModalHostViewEventEmitter::OnDismiss{}); } }]; } } - (std::shared_ptr)modalEventEmitter { if (!self->_eventEmitter) { return nullptr; } assert(std::dynamic_pointer_cast(self->_eventEmitter)); return std::static_pointer_cast(self->_eventEmitter); } #pragma mark - RCTMountingTransactionObserving - (void)mountingTransactionWillMount:(MountingTransaction const &)transaction withSurfaceTelemetry:(facebook::react::SurfaceTelemetry const &)surfaceTelemetry { _modalContentsSnapshot = [self.viewController.view snapshotViewAfterScreenUpdates:NO]; } #pragma mark - UIView methods - (void)didMoveToWindow { [super didMoveToWindow]; [self ensurePresentedOnlyIfNeeded]; } - (void)didMoveToSuperview { [super didMoveToSuperview]; [self ensurePresentedOnlyIfNeeded]; } #pragma mark - RCTFabricModalHostViewControllerDelegate - (void)boundsDidChange:(CGRect)newBounds { auto eventEmitter = [self modalEventEmitter]; if (eventEmitter) { eventEmitter->onOrientationChange(onOrientationChangeStruct(newBounds)); } if (_state != nullptr) { auto newState = ModalHostViewState{RCTSizeFromCGSize(newBounds.size)}; _state->updateState(std::move(newState)); } } #pragma mark - RCTComponentViewProtocol + (ComponentDescriptorProvider)componentDescriptorProvider { return concreteComponentDescriptorProvider(); } - (void)prepareForRecycle { [super prepareForRecycle]; _state.reset(); _viewController = nil; _isPresented = NO; _shouldPresent = NO; } - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { const auto &newProps = *std::static_pointer_cast(props); #if !TARGET_OS_TV self.viewController.supportedInterfaceOrientations = supportedOrientationsMask(newProps.supportedOrientations); #endif auto const [shouldAnimate, transitionStyle] = animationConfiguration(newProps.animationType); _shouldAnimatePresentation = shouldAnimate; self.viewController.modalTransitionStyle = transitionStyle; self.viewController.modalPresentationStyle = presentationConfiguration(newProps); _shouldPresent = newProps.visible; [self ensurePresentedOnlyIfNeeded]; [super updateProps:props oldProps:oldProps]; } - (void)updateState:(facebook::react::State::Shared const &)state oldState:(facebook::react::State::Shared const &)oldState { _state = std::static_pointer_cast(state); } - (void)mountChildComponentView:(UIView *)childComponentView index:(NSInteger)index { [self.viewController.view insertSubview:childComponentView atIndex:index]; } - (void)unmountChildComponentView:(UIView *)childComponentView index:(NSInteger)index { [childComponentView removeFromSuperview]; } @end #ifdef __cplusplus extern "C" { #endif // Can't the import generated Plugin.h because plugins are not in this BUCK target Class RCTModalHostViewCls(void); #ifdef __cplusplus } #endif Class RCTModalHostViewCls(void) { return RCTModalHostViewComponentView.class; }