mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: Changelog: [Internal] # Problem `RCTModalHostViewComponentView` reuses the same UIViewController between recycles. This can be a problem if dismissal of the modal is animated as the UIViewController is still presented while it is being dismissed. That's when "Application tried to present modally an active controller" exception can happen. # Solution Deallocate UIViewController when `RCTModalHostViewComponentView` is recycled. Reviewed By: PeteTheHeat Differential Revision: D23345711 fbshipit-source-id: da540571184afcb88b52758c4a1f0b4ec5874eb1
245 lines
7.6 KiB
Plaintext
245 lines
7.6 KiB
Plaintext
/*
|
|
* 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 <React/UIView+React.h>
|
|
#import <react/renderer/components/modal/ModalHostViewComponentDescriptor.h>
|
|
#import <react/renderer/components/modal/ModalHostViewState.h>
|
|
#import <react/renderer/components/rncore/EventEmitters.h>
|
|
#import <react/renderer/components/rncore/Props.h>
|
|
|
|
#import "RCTConversions.h"
|
|
|
|
#import "RCTFabricModalHostViewController.h"
|
|
|
|
using namespace facebook::react;
|
|
|
|
#if !TARGET_OS_TV
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
static std::tuple<BOOL, UIModalTransitionStyle> 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 () <RCTFabricModalHostViewControllerDelegate>
|
|
|
|
@end
|
|
|
|
@implementation RCTModalHostViewComponentView {
|
|
RCTFabricModalHostViewController *_viewController;
|
|
ModalHostViewShadowNode::ConcreteStateTeller _stateTeller;
|
|
BOOL _shouldAnimatePresentation;
|
|
BOOL _isPresented;
|
|
}
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame
|
|
{
|
|
if (self = [super initWithFrame:frame]) {
|
|
static const auto defaultProps = std::make_shared<const ModalHostViewProps>();
|
|
_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<ModalHostViewEventEmitter const>(self->_eventEmitter));
|
|
auto eventEmitter =
|
|
std::static_pointer_cast<ModalHostViewEventEmitter const>(self->_eventEmitter);
|
|
eventEmitter->onShow(ModalHostViewEventEmitter::OnShow{});
|
|
}];
|
|
}
|
|
|
|
BOOL shouldBeHidden = _isPresented && !self.superview;
|
|
if (shouldBeHidden) {
|
|
_isPresented = NO;
|
|
[self dismissViewController:self.viewController animated:_shouldAnimatePresentation];
|
|
}
|
|
}
|
|
|
|
- (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<ModalHostViewEventEmitter const>(_eventEmitter));
|
|
|
|
auto eventEmitter = std::static_pointer_cast<ModalHostViewEventEmitter const>(_eventEmitter);
|
|
eventEmitter->onOrientationChange(onOrientationChangeStruct(newBounds));
|
|
}
|
|
|
|
auto newState = ModalHostViewState{RCTSizeFromCGSize(newBounds.size)};
|
|
_stateTeller.updateState(std::move(newState));
|
|
}
|
|
|
|
#pragma mark - RCTComponentViewProtocol
|
|
|
|
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
{
|
|
return concreteComponentDescriptorProvider<ModalHostViewComponentDescriptor>();
|
|
}
|
|
|
|
- (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<const ModalHostViewProps>(props);
|
|
|
|
#if !TARGET_OS_TV
|
|
self.viewController.supportedInterfaceOrientations = supportedOrientationsMask(newProps.supportedOrientations);
|
|
#endif
|
|
|
|
std::tuple<BOOL, UIModalTransitionStyle> 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<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
|
|
{
|
|
[self.viewController.view insertSubview:childComponentView atIndex:index];
|
|
}
|
|
|
|
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
|
|
{
|
|
[childComponentView removeFromSuperview];
|
|
}
|
|
|
|
@end
|