Fix for Modal behavior when menu button pressed on Apple TV (Issue #15313)

Summary:
**Motivation**

On Apple TV, pressing the menu button destroys the native view that backs the `Modal` component, causing an app using this component to get into a broken state.  This fix implements `onRequestClose` for tvOS to have the same behavior as it does for the Android back button.

**Test plan**

Manually tested this with the `ModalExample` in the `RNTester` app.  See also the test code in issue #15313.
Closes https://github.com/facebook/react-native/pull/15341

Differential Revision: D5651035

Pulled By: shergin

fbshipit-source-id: 54bf66887bbe85940567e63e90b437ac4a8daf9a
This commit is contained in:
Douglas Lowder 2017-08-17 15:05:26 -07:00 committed by Facebook Github Bot
parent 14e67a379b
commit 0d3039f1a0
4 changed files with 50 additions and 4 deletions

View File

@ -118,10 +118,9 @@ class Modal extends React.Component {
*/ */
visible: PropTypes.bool, visible: PropTypes.bool,
/** /**
* The `onRequestClose` callback is called when the user taps the hardware back button. * The `onRequestClose` callback is called when the user taps the hardware back button on Android or the menu button on Apple TV.
* @platform android
*/ */
onRequestClose: Platform.OS === 'android' ? PropTypes.func.isRequired : PropTypes.func, onRequestClose: (Platform.isTVOS || Platform.OS === 'android') ? PropTypes.func.isRequired : PropTypes.func,
/** /**
* The `onShow` prop allows passing a function that will be called once the modal has been shown. * The `onShow` prop allows passing a function that will be called once the modal has been shown.
*/ */

View File

@ -31,6 +31,10 @@
@property (nonatomic, copy) NSArray<NSString *> *supportedOrientations; @property (nonatomic, copy) NSArray<NSString *> *supportedOrientations;
@property (nonatomic, copy) RCTDirectEventBlock onOrientationChange; @property (nonatomic, copy) RCTDirectEventBlock onOrientationChange;
#if TARGET_OS_TV
@property (nonatomic, copy) RCTDirectEventBlock onRequestClose;
#endif
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; - (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
@end @end

View File

@ -26,9 +26,12 @@
RCTModalHostViewController *_modalViewController; RCTModalHostViewController *_modalViewController;
RCTTouchHandler *_touchHandler; RCTTouchHandler *_touchHandler;
UIView *_reactSubview; UIView *_reactSubview;
#if !TARGET_OS_TV #if TARGET_OS_TV
UITapGestureRecognizer *_menuButtonGestureRecognizer;
#else
UIInterfaceOrientation _lastKnownOrientation; UIInterfaceOrientation _lastKnownOrientation;
#endif #endif
} }
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
@ -43,6 +46,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
_modalViewController.view = containerView; _modalViewController.view = containerView;
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge]; _touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge];
#if TARGET_OS_TV
_menuButtonGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuButtonPressed:)];
_menuButtonGestureRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
#endif
_isPresented = NO; _isPresented = NO;
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
@ -54,6 +61,27 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
return self; return self;
} }
#if TARGET_OS_TV
- (void)menuButtonPressed:(__unused UIGestureRecognizer *)gestureRecognizer
{
if (_onRequestClose) {
_onRequestClose(nil);
}
}
- (void)setOnRequestClose:(RCTDirectEventBlock)onRequestClose
{
_onRequestClose = onRequestClose;
if (_reactSubview) {
if (_onRequestClose && _menuButtonGestureRecognizer) {
[_reactSubview addGestureRecognizer:_menuButtonGestureRecognizer];
} else {
[_reactSubview removeGestureRecognizer:_menuButtonGestureRecognizer];
}
}
}
#endif
- (void)notifyForBoundsChange:(CGRect)newBounds - (void)notifyForBoundsChange:(CGRect)newBounds
{ {
if (_reactSubview && _isPresented) { if (_reactSubview && _isPresented) {
@ -89,6 +117,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
RCTAssert(_reactSubview == nil, @"Modal view can only have one subview"); RCTAssert(_reactSubview == nil, @"Modal view can only have one subview");
[super insertReactSubview:subview atIndex:atIndex]; [super insertReactSubview:subview atIndex:atIndex];
[_touchHandler attachToView:subview]; [_touchHandler attachToView:subview];
#if TARGET_OS_TV
if (_onRequestClose) {
[subview addGestureRecognizer:_menuButtonGestureRecognizer];
}
#endif
subview.autoresizingMask = UIViewAutoresizingFlexibleHeight | subview.autoresizingMask = UIViewAutoresizingFlexibleHeight |
UIViewAutoresizingFlexibleWidth; UIViewAutoresizingFlexibleWidth;
@ -99,8 +132,14 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
- (void)removeReactSubview:(UIView *)subview - (void)removeReactSubview:(UIView *)subview
{ {
RCTAssert(subview == _reactSubview, @"Cannot remove view other than modal view"); RCTAssert(subview == _reactSubview, @"Cannot remove view other than modal view");
// Superclass (category) removes the `subview` from actual `superview`.
[super removeReactSubview:subview]; [super removeReactSubview:subview];
[_touchHandler detachFromView:subview]; [_touchHandler detachFromView:subview];
#if TARGET_OS_TV
if (_menuButtonGestureRecognizer) {
[subview removeGestureRecognizer:_menuButtonGestureRecognizer];
}
#endif
_reactSubview = nil; _reactSubview = nil;
} }

View File

@ -110,4 +110,8 @@ RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray) RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray)
RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock)
#if TARGET_OS_TV
RCT_EXPORT_VIEW_PROPERTY(onRequestClose, RCTDirectEventBlock)
#endif
@end @end