Just right at the start: I don't want to change the user experience from Apple's user experience guidelines, I actually want to bring it back to my App, so here's the problem:
The "MENU" button should go back one level in the navigation hierarchy and terminate the App if there is no level anymore.
My first navigation screen also has an initially hidden full screen view (e.g. a video player). From this first navigation screen the user can go to deeper levels. The "MENU" button has its correct standard behavior.
The deepest navigation level then shows the hidden full screen view. When the user presses "MENU", the navigation should go back to the last level, but instead it terminates the App.
How can I change the behavior of the "MENU" button just for this single view?
It is not difficult to implement this. Just make sure the initial Screen that is displayed to the User is not the root View.
I will put the stack here.
Root View contains two views namely,
VideoContainerView and
MainContainerView.
VideoContainerView will have a UIViewController, which has menuHandler button listener. This VC displays anotherView on top of RootView on button press.
MainContainerView will have UIViewController, which doesn't have any menuHandler. MainContainerView is displayed on top of the VideoContainerView.
STACK
MainContainerView (Top)
VideoContainerView
RootViewController
RootView -> VideoContainerView (A View containing VideoScreenController)
RootView -> MainContainerView (A View containing MainViewController)
RootView -> VideoContainerView -> VideoScreen (Initial Screen, make sure this is a View Controller displayed on top of that FirstView using ContainerView).
From the VideoScreenController, Menu button press will display the RootView's MainContainerView which was hidden till now on top of that VideoContainerView.
On Next Menu button press, the app will go to background, as the MainViewController doesn't handle any menu Button press.
Never listen to any MenuButton press on the RootView's topView.
One can easily control the preferred focus flow using,
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if topView.isHidden {
return [VideoContainerView]
} else {
return [MainContainerView]
}
}
Like wise, have preferredFocusEnvironments in every ViewController to handle the initial preferred Focus, so that system is aware which is currently focused.
The above code, helps the system to know, which view should be focused on launch or on every focus change.
The thing that actually causes your app to exit is if the pressesEnded:withEvent: for the Menu button press makes it all the way up the responder chain to UIApplication. So if you prevent that from happening, the app won’t exit.
There are two ways to do that: either somebody in the responder chain needs to override that method and not call super, or somebody in the responder chain needs to have a gesture recognizer that recognizes that button press.
UINavigationController, for instance, uses the latter. It also disables the gesture if the navigation stack is at its root; that way, attempting to pop the last view controller actually exits the app.
Related
What is the maximum number of view controllers in UITabBarController for tvOS? It's not documented on developer.apple.com.
The screen is obviously bigger than iOS devices. I know for iOS it will add a "More" button if the number is bigger than 5.
The More Navigation Controller The tab bar has limited space for
displaying your custom items. If you add six or more custom view
controllers to a tab bar controller, the tab bar controller displays
only the first four items plus the standard More item on the tab bar.
Tapping the More item brings up a standard interface for selecting the
remaining items. The interface for the standard More item includes an
Edit button that allows the user to reconfigure the tab bar. By
default, the user is allowed to rearrange all items on the tab bar. If
you do not want the user to modify some items, though, you can remove
the appropriate view controllers from the array in the
customizableViewControllers property.
Note
Tab bar customization and
the More interface is not available in tvOS
Differences in tvOS
Tab bar controllers serve the same purpose in tvOS as in iOS, but
provide slightly different user interface features: The tab bar
interface appears at the top of the window. When focus leaves the tab
bar, the tab bar is hidden. Swiping up on the remote shows the tab bar
again and focuses it. A user can also show and focus the tab bar by
pressing the Menu button. Swiping down from the tab bar moves focus
into the content view; specifically, to the first focusable view that
is visually below the selected tab. Swiping down behaves like a normal
focus-changing gesture—that is, focus moves in the direction the user
swiped. If nothing is focusable immediately below the selected tab,
the closest focusable view is focused instead. Pressing the Select
button while a tab is focused moves focus into the content view.
Because there is no direction associated with this change, focus moves
to the view specified in the content view's preferredFocusedView
property. Tab bar controllers in tvOS do not support customization. A
tab bar controller displays only the number of view controllers from
its viewControllers array that fit on the screen, and does not provide
the More interface seen in iOS.
I found it by adding ViewControllers in StoryBoard. No matter how smaller your buttons are, the answer is 7. It will hide if it's more than 7 ViewController. And no "More" button of course. I added an 8 view controller but it only shows 7 (seven).
The answer is 7
Great, right?!
Solution
Sadly, I wrote my own TabBarController :(
Which is not a subclass of UITabBarController but UIViewController. Hoping Apple will change this (they'll not.). Should be depending on the size of the buttons, and could auto calculate each specific case.
Best.
The game controller button B is, by default, quitting the app and navigating back to the tvOS home screen. At first I thought this was intuitive, but quickly realized that's what the Nimbus MENU button (dead middle of the controller) is for, and that I actually want to use button B in-game.
Setting a change handler for button B works, but the app still quits when the button is released.
GCControllerButtonValueChangedHandler buttonBHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
NSLog(#"B");
};
I had the same issue.
The solution was to have my main ViewController inherit from GCEventViewController instead of UIViewController.
By default, when using GCEventViewController, the MENU button will not return to the menu. In this case, if you want it to be able to return to the menu with the original behavior you can simply set controllerUserInteractionEnabled to YES.
see the documentation for this class here :
https://developer.apple.com/library/tvos/documentation/GameController/Reference/GCEventViewController_Ref/index.html
edit : apple dev forum helpep me fix this issue : https://forums.developer.apple.com/message/57926#57926
hope this helps,
I too had the issue, related to Unity, but I think this rather hacky solution can help.
Deriving from GCEventViewController one can override several methods, one of them is:
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
returning or handling presses without calling super removes all calls to the internals.
In my metro style app (windows 8) I have created an page overlay and a flyout displaying an message. When the user clicks on the page the flyout message goes to hidden. But I want flyout to remain even when the user clicks on the overlay page. I am using HTML5 and WinJS.
Is there anyway I can achieve the above scenario using someother control or can I prevent the page to stop reacting to events when the user taps/clicks on the screen.
This is not really supported -- the Flyout itself doesn't have the concept of being persistent/sticky/modal. If you look at the code in base.js, you'll see that it doesn't expose any code to leverage the inbuilt sticky behavior that is used in the appbar (which is kinda of flyout, it turns out.
There are a couple things you can do:
Pass "_sticky: true" to the constructor of the flyout. e.g <div data-win-control="WinJS.UI.Flyout" data-win-options="{ _sticky: true}">
Attach a click handler to the top of the document, and if it's on the click eating div that the flyout creates, cancel the event
Create your own UI by simply adding a new div to the body, and absolute positioning it.
I'd like to override the animation of popToRootViewControllerAnimated: (currently: YES) when a user taps on the same tab bar item that is already selected.
When the tab bar item is first tapped, I check whether the user is signed-in in the viewWillAppear: method of the tab bar item root view controller and if not, immediately push the sign-in controller with animation disabled.
controller.navigationItem.hidesBackButton = YES;
[self.navigationController pushViewController:controller animated:NO];
[controller release];
When the user successfully signs-in, I pop back to the root controller, [animation is] no problem:
[self.navigationController popViewControllerAnimated:YES];
However, the default behavior when tapping the current tab bar item again appears to make the following calls:
-[UITabBarController _tabBarItemClicked:]
which calls
-[UINavigationController popToRootViewControllerAnimated:]
I can't tell if YES is passed into that last one, but assume so based on observation.
If the user doesn't sign-in but taps the tab bar item again, it looks like the same [sign-in] controller is getting pushed/popped with animation since the viewDidLoad of the root controller just pushes the sign-in controller again. I'd like the animation to be NO in this one case.
To add to my problems, there are other navigation stacks where the sign-in controller can be pushed and it is appropriate for taps on its tab bar item to pop to the root controller.
I have implemented UITabBarControllerDelegate protocol and tabBarController:shouldSelectViewController: in the sign-in controller to check if the tapped bar item root controller is the same and if that controller is the class of the controller I want to special case, but it seems inelegant. Not only that, but now the sign-in controller must know about the class of a controller that could push it onto the navigation stack. While I can keep using the protocol method, there must be a better way to do this.
Thanks for your input and ideas!
I think the best solution to this problem would be presenting a modal view controller when the tab bar button is selected, and then returning NO. From the delegate method.
My apps pops up a dialog. Users usually want to switch back and forth between this dialog and the application window for a period of time. I want this dialog to stay on top, so that it doesn't get hidden behind the main application window. But at the same time I want the dialog to have a minimize button so that it can get out of the way if it's not needed for a while.
Here is what I've tried:
use a modeless JDialog - the dialog stays in front nicely, but it doesn't not have a minimize button, and it doesn't have its own taskbar button either
use a JFrame - the dialog now has a minimize button and its own taskbar button, but when the main window gets focus the dialog is hidden behind it.
use a JFrame and add WindowListener.windowDeactivated() { this.toFront() } on the dialog. The problem with this is that toFront() also sets the focus, so that you get a weird focus flickering effect.
use a JFrame with setAlwaysOnTop - this is the neaerest solution, but now the window will stay on top of all other applications, not just my application.
It would be easy if toFront just brought the JFrame to the front without changing the focus, but unfortunately that is not the case. Is there another way to change JFrame Z-order?
edit: It just occurred to me that if there were an easy way to "roll-up" a JDialog, i.e. minimize it but not to the taskbar, that would solve my problem too.
Why not listen for the focusGained event in the frame and then use setZOrder/toFront on the dialog at that point? (Not sure this will work but worth a shot).