fabric: Migrate ModalHostView

Summary: Implements `ModalHostView` in Fabric.

Reviewed By: shergin

Differential Revision: D16090949

fbshipit-source-id: fe2c87063863e7f3c6c8dcc934a2cdb0b1aedaac
This commit is contained in:
Samuel Susla
2019-07-08 03:56:31 -07:00
committed by Facebook Github Bot
parent db17e969fa
commit faf9130560
6 changed files with 344 additions and 0 deletions
@@ -0,0 +1,15 @@
/**
* 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 <React/RCTViewComponentView.h>
/**
* UIView class for root <ModalHostView> component.
*/
@interface RNModalHostViewComponentView : RCTViewComponentView
@end
@@ -0,0 +1,207 @@
/**
* 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 "RNModalHostViewComponentView.h"
#import "RNModalHostViewController.h"
#import <React/UIView+React.h>
#import <react/components/modal/ModalHostViewComponentDescriptor.h>
#import <react/components/modal/ModalHostViewState.h>
#import <react/components/rncore/EventEmitters.h>
#import <react/components/rncore/Props.h>
#import "RCTConversions.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 ModalHostViewOnOrientationChangeStruct onOrientationChangeStruct(CGRect rect)
{
auto orientation = rect.size.width < rect.size.height ? ModalHostViewOnOrientationChangeOrientationStruct::Portrait
: ModalHostViewOnOrientationChangeOrientationStruct::Landscape;
return {orientation};
}
@interface RNModalHostViewComponentView () <RNModalHostViewControllerDelegate>
@end
@implementation RNModalHostViewComponentView {
RNModalHostViewController *_viewController;
ModalHostViewShadowNode::ConcreteState::Shared _state;
BOOL _shouldAnimatePresentation;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const ModalHostViewProps>();
_props = defaultProps;
_shouldAnimatePresentation = YES;
_viewController = [RNModalHostViewController new];
_viewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
_viewController.delegate = self;
}
return self;
}
- (BOOL)isViewControllerPresented
{
return _viewController.presentingViewController != nil;
}
- (void)ensurePresentedOnlyIfNeeded
{
BOOL shouldBePresented = !self.isViewControllerPresented && self.window;
if (shouldBePresented) {
UIViewController *controller = [self reactViewController];
return [controller
presentViewController:_viewController
animated:_shouldAnimatePresentation
completion:^{
ModalHostViewOnShowStruct onShow;
std::dynamic_pointer_cast<const ModalHostViewEventEmitter>(self->_eventEmitter)->onShow(onShow);
}];
}
BOOL shouldBeHidden = self.isViewControllerPresented && !self.superview;
if (shouldBeHidden) {
[_viewController dismissViewControllerAnimated:_shouldAnimatePresentation completion:nil];
}
}
- (void)didMoveToWindow
{
[super didMoveToWindow];
[self ensurePresentedOnlyIfNeeded];
}
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
[self ensurePresentedOnlyIfNeeded];
}
#pragma mark - RNModalHostViewControllerDelegate
- (void)boundsDidChange:(CGRect)newBounds
{
std::dynamic_pointer_cast<const ModalHostViewEventEmitter>(_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<ModalHostViewComponentDescriptor>();
}
- (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
_viewController.supportedInterfaceOrientations = supportedOrientationsMask(newProps.supportedOrientations);
#endif
std::tuple<BOOL, UIModalTransitionStyle> result = animationConfiguration(newProps.animationType);
_shouldAnimatePresentation = std::get<0>(result);
_viewController.modalTransitionStyle = std::get<1>(result);
_viewController.modalPresentationStyle = presentationConfiguration(newProps);
[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<const ModalHostViewShadowNode::ConcreteState>(state);
}
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
[_viewController.view insertSubview:childComponentView atIndex:index];
}
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
[childComponentView removeFromSuperview];
}
@end
@@ -0,0 +1,22 @@
/**
* 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 <UIKit/UIKit.h>
@protocol RNModalHostViewControllerDelegate <NSObject>
- (void)boundsDidChange:(CGRect)newBounds;
@end
@interface RNModalHostViewController : UIViewController
@property (nonatomic, weak) id<RNModalHostViewControllerDelegate> delegate;
#if !TARGET_OS_TV
@property (nonatomic, assign) UIInterfaceOrientationMask supportedInterfaceOrientations;
#endif
@end
@@ -0,0 +1,94 @@
/**
* 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 "RNModalHostViewController.h"
#import <React/RCTLog.h>
#import <React/RCTSurfaceTouchHandler.h>
@implementation RNModalHostViewController {
CGRect _lastViewBounds;
RCTSurfaceTouchHandler *_touchHandler;
}
- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}
_touchHandler = [RCTSurfaceTouchHandler new];
return self;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if (!CGRectEqualToRect(_lastViewBounds, self.view.bounds)) {
[_delegate boundsDidChange:self.view.bounds];
_lastViewBounds = self.view.bounds;
}
}
- (void)loadView
{
[super loadView];
[_touchHandler attachToView:self.view];
}
#if !TARGET_OS_TV
- (UIStatusBarStyle)preferredStatusBarStyle
{
return [RCTSharedApplication() statusBarStyle];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
_lastViewBounds = CGRectZero;
}
- (BOOL)prefersStatusBarHidden
{
return [RCTSharedApplication() isStatusBarHidden];
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)())completion
{
UIView *snapshot = [self.view snapshotViewAfterScreenUpdates:NO];
[self.view addSubview:snapshot];
[super dismissViewControllerAnimated:flag
completion:^{
[snapshot removeFromSuperview];
if (completion) {
completion();
}
}];
}
#if RCT_DEV
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
UIInterfaceOrientationMask appSupportedOrientationsMask =
[RCTSharedApplication() supportedInterfaceOrientationsForWindow:[RCTSharedApplication() keyWindow]];
if (!(_supportedInterfaceOrientations & appSupportedOrientationsMask)) {
RCTLogError(
@"Modal was presented with 0x%x orientations mask but the application only supports 0x%x."
@"Add more interface orientations to your app's Info.plist to fix this."
@"NOTE: This will crash in non-dev mode.",
(unsigned)_supportedInterfaceOrientations,
(unsigned)appSupportedOrientationsMask);
return UIInterfaceOrientationMaskAll;
}
return _supportedInterfaceOrientations;
}
#endif // RCT_DEV
#endif // !TARGET_OS_TV
@end
@@ -23,6 +23,7 @@
#import "RCTSwitchComponentView.h"
#import "RCTUnimplementedNativeComponentView.h"
#import "RCTViewComponentView.h"
#import "RNModalHostViewComponentView.h"
#import "RNPullToRefreshViewComponentView.h"
using namespace facebook::react;
@@ -47,6 +48,7 @@ using namespace facebook::react;
[componentViewFactory registerComponentViewClass:[RCTSliderComponentView class]];
[componentViewFactory registerComponentViewClass:[RCTSwitchComponentView class]];
[componentViewFactory registerComponentViewClass:[RCTUnimplementedNativeComponentView class]];
[componentViewFactory registerComponentViewClass:[RNModalHostViewComponentView class]];
return componentViewFactory;
}
@@ -21,8 +21,12 @@ namespace react {
class ModalHostViewComponentDescriptor final
: public ConcreteComponentDescriptor<ModalHostViewShadowNode> {
public:
#ifdef ANDROID
ModalHostViewComponentDescriptor(EventDispatcher::Shared eventDispatcher)
: ConcreteComponentDescriptor(eventDispatcher) {}
#else
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
#endif
void adopt(UnsharedShadowNode shadowNode) const override {
assert(std::dynamic_pointer_cast<ModalHostViewShadowNode>(shadowNode));