Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

tvOS UITabBarController start unfocused

I have a simple tvOS application starting with a UITabBarController and I wish the main view to have the focus when the app launches, not the tab bar.

I've tried playing with self.tabBarController.tabBar.userInteractionEnabled to remove temporarily the focus, but in vain. (Besides I do no like that kind of workaround)

Any clue?

Thanks in advance.

like image 845
Zaphod Avatar asked Oct 18 '15 13:10

Zaphod


3 Answers

My original solution no longer works on tvOS 9.3, so I found a new one with subclassing UITabBarController:

@interface TVTabBarController : UITabBarController
@property (nonatomic) BOOL useDefaultFocusBehavior;
@end
@implementation TVTabBarController
- (UIView *)preferredFocusedView {
    return self.useDefaultFocusBehavior ? [super preferredFocusedView] : self.selectedViewController.preferredFocusedView;
}
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator {
    [super didUpdateFocusInContext:context withAnimationCoordinator:coordinator];
    self.useDefaultFocusBehavior = YES;
}
@end

...

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.tabBarController.tabBar.hidden = YES; // or do it in storyboard
}

If you use storyboard for initial UI setup, don't forget to set custom class TVTabBarController to your tab bar controller there.


Original solution:

Proposed approach with inheriting from UITabBarController didn't work for me because in fact -preferredFocusedView is called twice on startup, so I had to add a counter to return self.selectedViewController.preferredFocusedView for the first 2 calls. But it's a really hacky solution and there's no guarantee that it won't break in future.

So I found a much better solution: force focus update in appdelegate's -applicationDidBecomeActive: on the first call.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.tabBarController.tabBar.hidden = YES;
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    static BOOL forceFocusToFirstTab = YES;
    if (forceFocusToFirstTab) {
        forceFocusToFirstTab = NO;
        [self.tabBarController.selectedViewController updateFocusIfNeeded];
    }
}
like image 112
kambala Avatar answered Sep 20 '22 00:09

kambala


The above approach mostly works but does not allow you to select a tab bar item with click as it returns the tabBar in that case when it should return the selectedItem. Here is an improved version which solves this by returning [super preferredViewController] instead of tabBar in the normal case. This version also hides the tab bar with alpha at launch so that it doesn't flicker in. There are probably more elegant ways to do the hiding.

@interface MWTabBarController ()

@property (nonatomic, assign) BOOL firstTime;

@end

@implementation MWTabBarController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.firstTime = YES;
    self.tabBar.alpha = 0;
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(unAlphaTabBar) userInfo:nil repeats:NO];
}

- (void) unAlphaTabBar
{
    self.tabBar.alpha = 1;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (UIView *)preferredFocusedView {
    if (self.firstTime) {
        self.firstTime = NO;
        return self.selectedViewController.preferredFocusedView;
    }
    else {
        return [super preferredFocusedView];
    }
}
like image 45
Josh Michaels Avatar answered Sep 19 '22 00:09

Josh Michaels


I've found the solution, so if someone is interested, you just have to subclass UITabBarController and to override preferredFocusedView:

@interface ZTWTabBarController ()

@property (nonatomic, assign) BOOL firstTime;

@end

@implementation ZTWTabBarController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.firstTime = YES;
}

- (UIView *)preferredFocusedView {
    if (self.firstTime) {
        self.firstTime = NO;
        return self.selectedViewController.preferredFocusedView;
    }
    else {
        return [super preferredFocusedView];
    }
}

@end
like image 22
Zaphod Avatar answered Sep 18 '22 00:09

Zaphod