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:
parent
14e67a379b
commit
0d3039f1a0
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue