tvOS UITabBarController start unfocused - focus

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.

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];
}
}

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];
}
}

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

I was able to achieve this effect very simply with the isHidden property of the UITabBar.
override func viewDidLoad() {
super.viewDidLoad()
self.tabBar.isHidden = true
}
When the user scrolls up to display the tab bar, the UITabBarController will unhide it automatically.

This is the easiest & cleanest solution in my opinion:
override var preferredFocusedView: UIView? {
if tabBar.hidden {
return selectedViewController?.preferredFocusedView
}
return super.preferredFocusedView
}

Since preferredFocusedView is deprecated in tvOS, you should override the preferredFocusEnvironments property instead
Swift 4.0
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if firsTime, let enviroments = selectedViewController?.preferredFocusEnvironments {
firsTime = false
return enviroments
}
return super.preferredFocusEnvironments
}

Related

Is it bad design to set self.delegate = self

I have a UIViewController subclass (say MyViewController).
MyViewController.h
#protocol TargetChangedDelegate
-(void) targetChanged;
#end
#interface MyViewController
#property (weak) id<TargetChangedDelegate> targetChangedDelegate;
-(void) doSomethingOnYourOwn;
#end
MyViewController.m
#implementation MyViewController <TargetChangedDelegate>
-(void) doSomethingOnYourOwn
{
// DO some stuff here
// IS THIS BAD ??
self.targetChangedDelegate = self;
}
-(IBAction) targetSelectionChanged
{
[self.targetChangedDelegate targetChanged];
}
-(void) targetChanged
{
// Do some stuff here
}
#end
Based on certain conditions a class that instantiates an instance of MyViewController may decide to set itself as the delegate or not.
Foo.m
#property(strong) MyViewController *myVC;
-(void) configureViews
{
self.myVC = [[MyViewController alloc] init];
[self.view addSubview:self.myVC];
if (someCondition)
{
self.myVC.targetChangedDelegate = self;
}
else
{
[self.myVC doSomethingOnYourOwn]
//MyViewController sets itself as the targetChangedDelegate
}
}
With reference to the code snippet above, I have the following question:
Is it a violation of MVC/delegation design pattern (or just a bad design) to say:
self.delegate = self;
There's absolutely no problem with setting the delegate to self. In fact it is a good way to provide default delegate functionality if a delegate is not set by somebody else.
Obviously, the delegate property has to be declared weak otherwise you get a reference cycle.
To expand a bit, having read the wrong answer and wrong comments above, if you allow an object to be its own delegate, your code is cleaner because you do not have to surround absolutely every single delegate call with
if ([self delegate] != nil)
{
[[self delegate] someMethod];
}
else
{
[self someMethod];
}
Its not proper way to assign self.delegate = self.
for your functionality, you can do this:
-(void) doSomethingOnYourOwn
{
// DO some stuff here
self.targetChangedDelegate = nil;
}
and when using delegate:
if(self.targetChangedDelegate != nil && [self.targetChangedDelegate respondsToSelector:#selector(targetChanged)]
{
[self.targetChangedDelegate targetChanged];
}
else
{
[self targetChanged];
}
It is bad design to set self.delegate = self; it should be another object. Delegation via protocols are an alternative design to subclassing and you can read more about delegation here:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html
And here is more on protocols:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Protocol.html

Refresh Current UIViewController from another view - Swift

my problem is that I want to apply some settings to my running app on-the-fly.
I found a lot of tutorials on how to refresh the TableController, but this is not the case.
I have a UIViewController with some labels inside and one button, when I press the button I open as PopOver (so inside the current View) another ViewController, my settings page Controller. From here I can choose the color of the text labels and the language to apply in App.
Unfortunately I do not know how to apply this settings immediately.
Any help, with some code, would be awesome!
You should transfer instance of parent controller to popover.
func showPopover() {
var settingController = ...
var popoverController = ...
settingController.parentController = self
//show popover
...
}
In SettingController, you will have a variance parentController
Or you can refresh parent controller by override willViewAppear, didViewAppear.
Edit
(1)
#protocol MyDelegate {
-(void) refreshLable:(UIColor*)color;
}
#implement ParentController<MyDelegate> {
- (IBAction) showPopover {
ChildController *childController = ...
childController.delegate = self;
...//Show popover
}
- (void) refreshLabel:(UIColor *) color {
//Implement protocol with update label
}
}
#implement ChildController {
MyDelegate *delegate;
- (IBAction) changeColor {
Color *color=...
if (delegate != null) [delegate changeLabel:color];
}
}
(2)
#implement ChildController {
- (IBAction) changeColor {
Color *color=...
[[SystemManager instance] setColor: color];
//Here SystemManager instance if static variable
}
}
#implement ParentController {
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//Get color from static variable
UIColor *color = [[SystemManager instance] getColor];
//Update label here
}
}

CGRectsIntersects without two images colliding

I have an issue at the moment. I am making a game. When two imageViews collide the game will end. I am using CGRECTIntersects rect to detect if the two images collide.
The issue is that when i restart the game the collision will happen when in fact the two images have not actually touched one another?
Any suggestions would be much appreciated.
Thank you
-(void)collision {
if (CGRectIntersectsRect(object.frame, object2.frame)) {
object.hidden=YES;
object2.hidden=YES;
retry.hidden=NO;
[timer invalidate];
}
}
/- (void)viewDidLoad
{
retry.hidden=YES;
timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(object2) userInfo:nil repeats:YES];
objectSpeed1 = CGPointMake(3.0, 2.0);
[super viewDidLoad];
}
/- (IBAction)retry:(id)sender {
[self performSegueWithIdentifier:#"restart" sender:sender];
}
-(void)object2 {
object2.center = CGPointMake(object2.center.x + object2.x, object2.center.y + objectspeed1.y);
if (object2.center.x > 310 || object2.center.x < 10) {
objectspeed1.x = - objectspeed1.x;
}
if (object2.center.y > 558 || object2.center.y < 10) {
objectspeed1.y = - objectspeed1.y;
}
Just consider this case on the code. You can check if image views are on their start position or they came to this position from another. It's enough to keep BOOL property on viewcontroller's class like this:
// ViewController.m
#interface ViewController ()
#property (readonly) BOOL isStarted;
#end
#implementation
- (void)viewDidLoad {
[super viewDidLoad];
_isStarted = NO;
// Make some preparations for app's needs
_isStarted = YES;
}
#end
_isStarted is a flag which shows whether viewcontroller is ready to handle the data.
if(_isStarted) {
// Analyze image views' frame
}
else {
// Do nothing
}

How to detect the pressed button and display it on Another View? Objective-C

I am new to iOS app development. I want to create a Calculator App in iOS that has split view. The left side is the "History" Feature in Scroll View and the right side is the calculator itself. Now, regarding the History feature of this app, I am thinking that my program needs to recognize what has been pressed and display it on the Scroll View when the Equal (=) button is pressed. Do you have any idea how will this go on Objective-C? I am using XCode 4.5 and iPhone Simulator 6.0.
Thanks in Advance!
If you want to communicate/send data between views or view controllers there are several options.
If you try to communicate/send data between views and you have reference to both views you can simply call the methods from your views for example
LeftView.h
#interface LeftView : UIView {
//instance variables here
}
//properties here
//other methods here
-(NSInteger)giveMeTheValuePlease;
#end
LeftView.m
#implementation LeftView
//synthesise properties here
//other methods implementation here
-(NSInteger)giveMeTheValuePlease {
return aValueThatIsInteger; //you can do other computation here
}
RightView.h
#interface RightView : UIView {
//instance variables here
}
//properties here
//other methods here
-(NSInteger) hereIsTheValue:(NSInteger)aValue;
#end
RightView.m
#implementation LeftView
//synthesise properties here
//other methods implementation here
-(void)hereIsTheValue:(NSInteger)aValue {
//do whatever you want with the value
}
AViewController.m
#implementation AViewController.m
//these properties must be declared in AViewController.h
#synthesise leftView;
#synthesise rightView;
-(void)someMethod {
NSInteger aValue = [leftView giveMeTheValuePlease];
[rightView hereIsTheValue:rightView];
}
You can use the delegate pattern (very very common in iOS), a short and basic example of delegate you can find in one of my SO answer at this link
You can also use blocks to communicate/send data between views/view controllers but this topic I think you will use a little bit later and for you will have to google a little bit in order to get a basic idea of iOS blocks.
Here is the solution for this requirement.
In my case.. I have 2 buttons in viewcontroller. When I click on those buttons I had to display popover. For this I had to detect which button is clicked in PopoverController(AnotherViewController).
First I have taken #property BOOL isClicked; in AppDelegate.h
And in AppDelegate.m #synthesize isClicked; (synthesized it) and in
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
isClicked = FALSE;
}
Now in ViewController.m where action is implemented for buttons changed like this,
- (IBAction)citiesButtonClicked:(id)sender
{
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
delegate.isClicked = FALSE;
}
- (IBAction)categoryButtonClicked:(id)sender
{
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
delegate.isClicked = TRUE;
}
PopoverViewController (AnotherViewController) in -(void)viewDidLoad method
-(void)viewDidLoad {
{
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
if (delegate.isClicked)
{
delegate.isClicked = FALSE;
NSLog(#"popover clicked");
}
else
{
delegate.isClicked = TRUE;
isClicked = YES;
}
}
I hope it helps. Let me know if you need any help.

Three20 TTTableViewController load more automatically

I've a TTTableViewController which follows TTTableViewController -> TTDataSource -> TTModel pattern. I've TTTableMoreButton and my list goes on to load more items when the user clicks on it.
How can I change the behaviour of this TTTableMoreButton? When the user came to the end of the list, I want it to behave as if it is clicked. In Facebook app, there is an implementation like this. I hope I could tell what I want.
Here is how to do it.
full disclosure: It is my code blog.
Here I've my own approach which i found out just before coneybeare's answer. I simply subclassed TTTableMoreButton and TTTableMoreButtonCell classes and in the "- (void)layoutSubviews" method, I detect that "Load More" button is appearing, and it should start loading more data if it is not already doing it.
I'm not sure which approach (coneybeaare's or mine) is the best and I'm looking forward for the comments about it.
AutoMoreTableItem.h
#interface AutoMoreTableItem : TTTableMoreButton {
}
#end
AutoMoreTableItem.m
#import "AutoMoreTableItem.h"
#implementation AutoMoreTableItem
#end
AutoMoreTableItemCell.h
#interface AutoMoreTableItemCell : TTTableMoreButtonCell {
}
#end
AutoMoreTableItemCell.m
#import "AutoMoreTableItemCell.h"
#import "AutoMoreTableItem.h"
#implementation AutoMoreTableItemCell
- (void)setObject:(id)object {
if (_item != object) {
[super setObject:object];
AutoMoreTableItem* item = object;
self.animating = item.isLoading;
self.textLabel.textColor = TTSTYLEVAR(moreLinkTextColor);
self.selectionStyle = TTSTYLEVAR(tableSelectionStyle);
self.accessoryType = UITableViewCellAccessoryNone;
}
}
- (void)layoutSubviews {
[super layoutSubviews];
AutoMoreTableItem* moreLink = self.object;
if(moreLink.isLoading ==YES) {
return;
}
if (moreLink.model) {
moreLink.isLoading = YES;
self.animating = YES;
[moreLink.model load:TTURLRequestCachePolicyDefault more:YES];
}
}
#end
And of course, in the datasource implementation:
- (Class)tableView:(UITableView*)tableView cellClassForObject:(id) object {
if([object isKindOfClass:[AutoMoreTableItem class]]){
return [AutoMoreTableItemCell class];
} else {
return [super tableView:tableView cellClassForObject:object];
}
}

Resources