From 8e5fac89bbdcc3028bb5d81a358969a235abf991 Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Wed, 5 Feb 2020 21:40:15 -0800 Subject: [PATCH] Implement onRequestClose for iOS 13+ modals (#27618) Summary: Starting on iOS 13, a View Controller presented modally will have a "bottom sheet" style unless it's explicitly presented full screen. Before this, modals on iOS were only being dismissed programatically by setting `visible={false}`. However, now that the dismissal can happen on the OS side, we need a callback to be able to update the state. This PR reuses the `onRequestClose` prop already available for tvOS and Android, and makes it work on iOS for this use case. Should fix https://github.com/facebook/react-native/issues/26892 ## Changelog [iOS] [Added] - Add support for onRequestClose prop to Modal on iOS 13+ Pull Request resolved: https://github.com/facebook/react-native/pull/27618 Test Plan: I tested this using the RNTester app with the Modal example: 1. Select any presentation style other than the full screen ones 2. Tap Present and the modal is presented 3. Swipe down on the presented modal until dismissed 4. Tap Present again and a second modal should be presented ![Screen Recording 2019-12-26 at 14 05 33](https://user-images.githubusercontent.com/8739/71477208-0ac88c80-27e9-11ea-9342-8631426a9b80.gif) Differential Revision: D19235758 Pulled By: shergin fbshipit-source-id: c0f1d946c77ce8d1baab209eaef7eb64697851df --- Libraries/Modal/Modal.js | 3 ++- .../Modal/RCTModalHostViewNativeComponent.js | 3 ++- .../Modal/RCTModalHostViewComponentView.mm | 18 +++++++++++++++++- React/Views/RCTModalHostView.h | 3 ++- React/Views/RCTModalHostView.m | 16 +++++++++++++++- React/Views/RCTModalHostViewManager.m | 3 --- 6 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index 8cc55ffe572..3a4f6d15358 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -95,7 +95,8 @@ export type Props = $ReadOnly<{| /** * The `onRequestClose` callback is called when the user taps the hardware - * back button on Android or the menu button on Apple TV. + * back button on Android, the menu button on Apple TV, or a modal is dismissed + * with a gesture on iOS 13+. * * This is required on Apple TV and Android. * diff --git a/Libraries/Modal/RCTModalHostViewNativeComponent.js b/Libraries/Modal/RCTModalHostViewNativeComponent.js index 9363a3b98d3..43428715b84 100644 --- a/Libraries/Modal/RCTModalHostViewNativeComponent.js +++ b/Libraries/Modal/RCTModalHostViewNativeComponent.js @@ -70,7 +70,8 @@ type NativeProps = $ReadOnly<{| /** * The `onRequestClose` callback is called when the user taps the hardware - * back button on Android or the menu button on Apple TV. + * back button on Android, the menu button on Apple TV, or a modal is dismissed + * with a gesture on iOS 13+. * * This is required on Apple TV and Android. * diff --git a/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm index 7fcbd62147f..574325af9fe 100644 --- a/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm @@ -94,7 +94,9 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct( return {orientation}; } -@interface RCTModalHostViewComponentView () +@interface RCTModalHostViewComponentView () < + RCTFabricModalHostViewControllerDelegate, + UIAdaptivePresentationControllerDelegate> @end @@ -113,6 +115,7 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct( _viewController = [RCTFabricModalHostViewController new]; _viewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; _viewController.delegate = self; + _viewController.presentationController.delegate = self; } return self; @@ -217,6 +220,19 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct( [childComponentView removeFromSuperview]; } +#pragma mark - UIAdaptivePresentationControllerDelegate + +- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController +{ + if (!_eventEmitter) { + return; + } + + assert(std::dynamic_pointer_cast(_eventEmitter)); + auto eventEmitter = std::static_pointer_cast(_eventEmitter); + eventEmitter->onRequestClose({}); +} + @end Class RCTModalHostViewCls(void) diff --git a/React/Views/RCTModalHostView.h b/React/Views/RCTModalHostView.h index e16dd222b50..6a35fb29565 100644 --- a/React/Views/RCTModalHostView.h +++ b/React/Views/RCTModalHostView.h @@ -32,8 +32,9 @@ @property (nonatomic, copy) NSArray *supportedOrientations; @property (nonatomic, copy) RCTDirectEventBlock onOrientationChange; -#if TARGET_OS_TV @property (nonatomic, copy) RCTDirectEventBlock onRequestClose; + +#if TARGET_OS_TV @property (nonatomic, strong) RCTTVRemoteHandler *tvRemoteHandler; #endif diff --git a/React/Views/RCTModalHostView.m b/React/Views/RCTModalHostView.m index 95d572bf3eb..9b767155def 100644 --- a/React/Views/RCTModalHostView.m +++ b/React/Views/RCTModalHostView.m @@ -20,6 +20,10 @@ #import "RCTTVRemoteHandler.h" #endif +@interface RCTModalHostView () + +@end + @implementation RCTModalHostView { __weak RCTBridge *_bridge; @@ -46,6 +50,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder) UIView *containerView = [UIView new]; containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; _modalViewController.view = containerView; + _modalViewController.presentationController.delegate = self; _touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge]; #if TARGET_OS_TV _menuButtonGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuButtonPressed:)]; @@ -70,10 +75,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder) _onRequestClose(nil); } } +#endif - (void)setOnRequestClose:(RCTDirectEventBlock)onRequestClose { _onRequestClose = onRequestClose; + #if TARGET_OS_TV if (_reactSubview) { if (_onRequestClose && _menuButtonGestureRecognizer) { [_reactSubview addGestureRecognizer:_menuButtonGestureRecognizer]; @@ -81,8 +88,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder) [_reactSubview removeGestureRecognizer:_menuButtonGestureRecognizer]; } } + #endif } -#endif - (void)notifyForBoundsChange:(CGRect)newBounds { @@ -257,4 +264,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder) } #endif +- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController +{ + if (_onRequestClose) { + _onRequestClose(nil); + } +} + @end diff --git a/React/Views/RCTModalHostViewManager.m b/React/Views/RCTModalHostViewManager.m index 44f9ac9d2da..9c0d13c478f 100644 --- a/React/Views/RCTModalHostViewManager.m +++ b/React/Views/RCTModalHostViewManager.m @@ -108,9 +108,6 @@ RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(identifier, NSNumber) RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray) RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock) - -#if TARGET_OS_TV RCT_EXPORT_VIEW_PROPERTY(onRequestClose, RCTDirectEventBlock) -#endif @end