/* * Copyright (c) Facebook, Inc. and its 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 "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::ConcreteStateTeller _stateTeller; BOOL _shouldAnimatePresentation; 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 { [modalViewController dismissViewControllerAnimated:animated completion:nil]; } - (void)ensurePresentedOnlyIfNeeded { BOOL shouldBePresented = !_isPresented && self.window; if (shouldBePresented) { _isPresented = YES; [self presentViewController:self.viewController animated:_shouldAnimatePresentation completion:^{ if (!self->_eventEmitter) { return; } assert(std::dynamic_pointer_cast(self->_eventEmitter)); auto eventEmitter = std::static_pointer_cast(self->_eventEmitter); eventEmitter->onShow(ModalHostViewEventEmitter::OnShow{}); }]; } BOOL shouldBeHidden = _isPresented && !self.superview; if (shouldBeHidden) { _isPresented = NO; // To animate dismissal of view controller, snapshot of // view hierarchy needs to be added to the UIViewController. [self.viewController.view addSubview:_modalContentsSnapshot]; [self dismissViewController:self.viewController animated:_shouldAnimatePresentation]; } } #pragma mark - RCTMountingTransactionObserving - (void)mountingTransactionWillMountWithMetadata:(MountingTransactionMetadata const &)metadata { _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 { if (_eventEmitter) { assert(std::dynamic_pointer_cast(_eventEmitter)); auto eventEmitter = std::static_pointer_cast(_eventEmitter); eventEmitter->onOrientationChange(onOrientationChangeStruct(newBounds)); } auto newState = ModalHostViewState{RCTSizeFromCGSize(newBounds.size)}; _stateTeller.updateState(std::move(newState)); } #pragma mark - RCTComponentViewProtocol + (ComponentDescriptorProvider)componentDescriptorProvider { return concreteComponentDescriptorProvider(); } - (void)prepareForRecycle { [super prepareForRecycle]; _stateTeller.invalidate(); _viewController = nil; _isPresented = 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 std::tuple result = animationConfiguration(newProps.animationType); _shouldAnimatePresentation = std::get<0>(result); self.viewController.modalTransitionStyle = std::get<1>(result); self.viewController.modalPresentationStyle = presentationConfiguration(newProps); [super updateProps:props oldProps:oldProps]; } - (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState { _stateTeller.setConcreteState(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