facebook / react-native

A framework for building native applications using React

Home Page:https://reactnative.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Apple TV react-native doesn't follow Apple's tvOS guidelines for menu key behavior

douglowder opened this issue · comments

When running a react-native app built for tvOS, the remote control's menu key does not exit the app and return to the tvOS home screen, as required by Apple. See guidelines at https://developer.apple.com/tvos/human-interface-guidelines/remote-and-controllers/remote/

This is happening because the RCTTVRemoteHandler class is adding a menu key gesture recognizer by default to RCTRootView. The gesture recognizer should be added if a screen is pushed onto a navigation stack and we are navigating back to a lower level screen; it should not be added for the app's main view.

Environment

Environment:
OS: macOS High Sierra 10.13.4
Node: 8.9.1
Yarn: 1.5.1
npm: 5.5.1
Watchman: Not Found
Xcode: Xcode 9.3 Build version 9E145
Android Studio: 3.1 AI-173.4697961

Packages: (wanted => installed)
react: ^16.0.0 => 16.3.2
react-native: ^0.54.0 => 0.54.4

Steps to Reproduce

See issue description above -- the same thing happens on both tvOS simulator and a real tvOS device.

Expected Behavior

Menu key should exit the app as required.
For apps that use navigation frameworks that back-navigate using menu key, the correct behavior needs to be implemented.

Actual Behavior

See description above.

Thanks for posting this! It looks like your issue may refer to an older version of React Native. Can you reproduce the issue on the latest release, v0.55?

Thank you for your contributions.

Just curious how this is coming along? Let me know if I can help in any way or if you know of a quick work around.

And thanks for all your work porting RN to Apple TV.

@euroclydon37 I've made some progress.... The issue can be resolved by modifying RCTTVRemoteHandler to not create a gesture recognizer for the menu key. However, this would break apps that needed a menu key, e.g. ones with a navigation stack and using BackHandler. This will need some thought to come up with a graceful solution.

I thought the Menu button would go back to the previous screen, and when the root screen of the app is reached, it would exit to the Apple TV home screen? The home button will always exit the app?

From the Apple Docs:

" Users expect to press the Menu button on the remote and return to a previous screen or the main Apple TV Home screen. Pressing Menu at the top level of an app or game should always exit to the Apple TV Home screen. During gameplay, pressing Menu should show or hide an in-game pause menu that includes an option to navigate back to the game’s main menu. "

@mrendsley Yes, that is exactly what the menu button is supposed to do. The problem is that there is no way to get the menu button to go to the Apple TV Home screen from the root screen of your app.

In case it helps anybody else, this was my workaround.

I added the below method to my bridge that lets me enable/disable the menu button gesture recognizer. If the gesture recognizer is enabled, we can handle the menu button and go back a screen. If the gesture recognizer is NOT enabled, the app will close.

#import <React/RCTRootView.h>
#import <React/RCTTVRemoteHandler.h>
#import <React/RCTRootViewInternal.h>

RCT_EXPORT_METHOD(setMenuHandlerEnabled: (nonnull NSNumber*)enabled) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // get your RCTRootView somehow - your code may differ here
        RSTVAppDelegate *appDelegate = (RSTVAppDelegate*)[[UIApplication sharedApplication] delegate];
        RCTRootView *rootView = (RCTRootView*)appDelegate.rootViewController.view;

        // get the menu button gesture recognizer and enable/disable it
        UITapGestureRecognizer *menuTapRecognizer = [rootView.tvRemoteHandler.tvRemoteGestureRecognizers objectForKey:RCTTVRemoteEventMenu];
        menuTapRecognizer.enabled = [enabled boolValue];
    });
}

Then in my javascript I call NativeModules.YourBridge.setMenuHandlerEnabled(false); when my home screen mounts or the user returns to my homescreen. I call NativeModules.YourBridge.setMenuHandlerEnabled(true); when the user visits any other screen.

@ahfarmer I'm trying to use your workaround. But for some reason I can't import these

#import <React/RCTTVRemoteHandler.h>
TVMenuManager.m:12:9: 'React/RCTTVRemoteHandler.h' file not found

The same for RCTRootViewInternal.h The files are there in the React/Base folder and the other file imports work (ex. RCTRootView.h).

Any idea?

@hufkens Hmmmm sorry I'm not sure. Maybe a difference in our setup? I am using CocoaPods and react-native version 0.57.8.

@ahfarmer I found way to add / remove the gesture handler without those 2 imports. There is no progress on fixing this properly in react-native?

FYI there's been a discussion lately on whether to extract tvOS out of the core: react-native-community/discussions-and-proposals#111

Since unfortunately there's no incentive from anyone on the core team to fix it in foreseeable future, I believe it makes sense to close it. But please keep the discussion going if you find it helpful.

@hufkens could you share how you "found way to add / remove the gesture handler without those 2 imports" please

Sure.

I made a method that will look for the GestureHandler. Keep a reference to it in the AppDegelate. Later you can remove the GestureHandler when your app reaches the Top level. In this case the App will close and go to the Home screen. You can re-add the GestureRecognizer when necessary.

- (void)findMenuHandler:(UIView*)rootView {
  for(UIGestureRecognizer *gr in rootView.gestureRecognizers) {
    if ([gr.allowedPressTypes.firstObject isEqualToValue:@(UIPressTypeMenu)]) {
      self.menuGestureRecognizer = gr;
    }
  }
}


- (void)setMenuHandlerEnabled:(BOOL)enabled {
  UIWindow * window = [[UIApplication sharedApplication] keyWindow];
  RCTRootView *rootView = (RCTRootView*)window.rootViewController.view;
  if (enabled) {
    [rootView addGestureRecognizer:self.menuGestureRecognizer];
  } else {
    [rootView removeGestureRecognizer:self.menuGestureRecognizer];
  }
}

Going forward, Apple TV is going to be supported in a community repo separate from RN core.... https://github.com/react-native-community/react-native-tvos . Just added a commit that fixes this issue by adding a new TVMenuControl module, with methods to enable and disable the menu key gesture handler. The commit includes changes to the RNTester app to show how to use the new module to enable the menu key when pushing a screen, and disabling the menu key when you're back to the bottom level of your navigation.

Hey @hufkens!
I'm trying your solution on AppDelegate.m file and I'm getting this error message:

Property 'menuGestureRecognizer' not found on object of type 'AppDelegate *'

Maybe I'm missing something?

Thank you

@oscargallegoglobant You will need to add a property on the AppDelegate for the menuGestureRecognizer.

@property (nonatomic, strong) UIGestureRecognizer *menuGestureRecognizer;

Or start using the version on https://github.com/react-native-community/react-native-tvos where this feature has been added. All new features bug fixes are maintained in the community repo now.