diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index bb74dee32..cbf1b6199 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -118,10 +118,9 @@ class Modal extends React.Component { */ visible: PropTypes.bool, /** - * The `onRequestClose` callback is called when the user taps the hardware back button. - * @platform android + * The `onRequestClose` callback is called when the user taps the hardware back button on Android or the menu button on Apple TV. */ - 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. */ diff --git a/React/Views/RCTModalHostView.h b/React/Views/RCTModalHostView.h index 534613dcb..e0c9fcb9e 100644 --- a/React/Views/RCTModalHostView.h +++ b/React/Views/RCTModalHostView.h @@ -31,6 +31,10 @@ @property (nonatomic, copy) NSArray *supportedOrientations; @property (nonatomic, copy) RCTDirectEventBlock onOrientationChange; +#if TARGET_OS_TV +@property (nonatomic, copy) RCTDirectEventBlock onRequestClose; +#endif + - (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; @end diff --git a/React/Views/RCTModalHostView.m b/React/Views/RCTModalHostView.m index 2b8f7c84f..d488f0f98 100644 --- a/React/Views/RCTModalHostView.m +++ b/React/Views/RCTModalHostView.m @@ -26,9 +26,12 @@ RCTModalHostViewController *_modalViewController; RCTTouchHandler *_touchHandler; UIView *_reactSubview; -#if !TARGET_OS_TV +#if TARGET_OS_TV + UITapGestureRecognizer *_menuButtonGestureRecognizer; +#else UIInterfaceOrientation _lastKnownOrientation; #endif + } RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) @@ -43,6 +46,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder) containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; _modalViewController.view = containerView; _touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge]; +#if TARGET_OS_TV + _menuButtonGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuButtonPressed:)]; + _menuButtonGestureRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)]; +#endif _isPresented = NO; __weak typeof(self) weakSelf = self; @@ -54,6 +61,27 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder) 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 { if (_reactSubview && _isPresented) { @@ -89,6 +117,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder) RCTAssert(_reactSubview == nil, @"Modal view can only have one subview"); [super insertReactSubview:subview atIndex:atIndex]; [_touchHandler attachToView:subview]; +#if TARGET_OS_TV + if (_onRequestClose) { + [subview addGestureRecognizer:_menuButtonGestureRecognizer]; + } +#endif subview.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; @@ -99,8 +132,14 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder) - (void)removeReactSubview:(UIView *)subview { RCTAssert(subview == _reactSubview, @"Cannot remove view other than modal view"); + // Superclass (category) removes the `subview` from actual `superview`. [super removeReactSubview:subview]; [_touchHandler detachFromView:subview]; +#if TARGET_OS_TV + if (_menuButtonGestureRecognizer) { + [subview removeGestureRecognizer:_menuButtonGestureRecognizer]; + } +#endif _reactSubview = nil; } diff --git a/React/Views/RCTModalHostViewManager.m b/React/Views/RCTModalHostViewManager.m index a5adde4f0..fc4cc59ae 100644 --- a/React/Views/RCTModalHostViewManager.m +++ b/React/Views/RCTModalHostViewManager.m @@ -110,4 +110,8 @@ RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray) RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock) +#if TARGET_OS_TV +RCT_EXPORT_VIEW_PROPERTY(onRequestClose, RCTDirectEventBlock) +#endif + @end