My app is based on a tab bar architecture. In order to have an easy internationalisation mechanic, I do not write any string in XIB files. viewdidload permits to change programmaticaly the strings in the views.
When my app launches, I can see the 4 tabs. But in fact only the first one loads its view controller. The others wait for user click to be loaded.
Tabs title can be changed using [self setTitle:#"Mouhahaha"]; in viewDidLoad of loaded view controller.
If I want to keep my internationalisation mechanic available, I do not set in my XIB the name of tabbar items. But, as at start all tab' view controllers are not loaded, I have blank titles for some tabs. The right title is set only when the user click on the tab.
I am looking for a way to set this title programaticaly for each tabbaritem. Do you have hints ?
Thanks a lot.
kheraud
my preferred method of doing this programmatically together with the storyboard is to make a subclass of UITabBarController, have my tab bar controller scene in my storyboard use the new subclass (with 3 UIViewController relationships from the tab bar controller to the desired view controller in the case below), and then override viewWillAppear:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSArray* titleKeys = [NSArray arrayWithObjects:#"top places",
#"localizablekey1",
#"localizablekey2",
#"localizablekey3",
nil];
[super viewWillAppear:animated];
int count = 0; for (UIViewController* viewController in self.viewControllers)
viewController.tabBarItem.title = NSLocalizedString([titleKeys objectAtIndex:count++], nil);
}
All you need to do is make an instance of UITabBarController, then alloc any views you want in it, then set the UITabBarController views. Make sure that your TabBarController is the one that is visible. Also make sure that any viewControllers you want in your tab bar are being imported with #import.
UITabBarController *c = [self tabBarController];
SecondViewController *s = [[SecondViewController alloc] init];
[s setTitle:#"Whatever"];
c.viewControllers = [NSArray arrayWithObjects:self, s, nil];
[s release];
Put this in the viewDidLoad method of the first controller allocated.
Alternatively you could just change the way your ApplicationDelegate sets up the TabBarController, but i won't go into that.
Hope this helps
Related
Does anyone know if it is possible for a single tab bar item to have multiple relationships?
I would like to be able to direct to two different view controllers from a single UITabbar icon, depending on the type of user that logs in.
For example, if the user logs in as user type "A", I want the Tab bar icon to direct to a profile view controller. If the user logs in as user type "B", I want the same icon to direct to a settings view controller.
I've tried to connect the additional view controller to the tab bar and it just creates an additional icon/tab on the tab bar.
You will need to do it from code, so look at the setViewControllers method.
Supposing you have 4 Tabs corresponding to vc1 vc2 vc A or B and vc4...
You could determine which VC you want assigned, then instantiate the full "set" of controllers with:
// set "vcA" as the 3rd tab
[self.tabBarController setViewControllers:#[vc1, vc2, vcA, vc4] animated:NO];
// or, set "vcB" as the 3rd tab
[self.tabBarController setViewControllers:#[vc1, vc2, vcB, vc4] animated:NO];
Or... to save on "manually" instantiating the controllers...
You could assign all 5 controllers in your storyboard, then:
// get the array of viewControllers
NSMutableArray *a = self.tabBarController.viewControllers;
// a now contains [vc1, vc2, vcA, vcB, vc4]
// remove "vcA"
[a removeObjectAtIndex:2];
// or, remove "vcB"
[a removeObjectAtIndex:3];
// set the controllers array
[self.tabBarController setViewControllers:a animated:NO];
You could also place a container view in the the view controller for that tab, add two views to the container view, and then depending on what type of user it is show the proper view during viewDidLoad.
Will add code when I have time.
This would be one way of doing it:
A. keep track of what sort of user logs in held a variable, passed on from your previous viewController, or held centrally in a data object:
bool userCanAccessProfile = false;
B. depending on the above bool, update your layout and logic code accordingly:
//layout your tab bar
UITabBar * tabBar = [UITabBar new];
tabBar.frame = CGRectMake(0, h-50, w, 50);
tabBar.delegate = self;
[self.view addSubview:tabBar];
//create the item(s)
UITabBarItem * item = [UITabBarItem new];
item.title = (userCanAccessProfile) ? #"Profile" : #"Settings";
item.image = (userCanAccessProfile) ? [UIImage imageNamed:#"profile.png"] : [UIImage imageNamed:#"settings.png"];
[tabBar setItems:#[item]];
The lines above that look like this, means this:
something = (isThisTrue) ? (true) setThisValue : (false) setAnotherValue;
You're asking if userCanAccessProfile is true, and if it is, you're setting a different text and image accordingly.
C. When the User clicks on the item, you would again query the bool to find out what to do:
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
//when the item is clicked
if (userCanAccessProfile){
//open profile
} else {
//open settings
}
}
Be sure to set the delegate in the .m file:
tabBar.delegate = self;
and add the delegate in the .h file:
#interface yourVC : UIViewController <UITabBarDelegate>
I am writing an application with help screens. As per my requirement I need to show the help screens when the view appears for the first time and I am placing the code on the tabbar controller class as (void)viewDidLoad:
{
[super viewDidLoad];
XDKWalkthroughController *sc = [[XDKWalkthroughController alloc]
initWithView:self.view];
[self addChildViewController:sc];
[self.view addSubview:sc.view];
sc.delegate = self;
[sc start];
}
When I am placing this code I am getting one more tabbar item along with existing tabbar item. How to avoid that in my case?
And same thing when I placed in the first view i.e in the first tab view controller view did class. Both navigation bar and tabbar are pushing the child view back.
I have tried these two scenarios, please help me with the possible solution.
I've looked at all other answers for this topic on Stackoverflow but don't get really further. I've set up my Tabbar controller in Storyboard. I've defined the icons for the tabbar items also in Storyboard, the titles however I've set via code in their respective view controllers since my app offers multi-language support.
Now I want one single tabbar button which doesn't segue to another view but just calls an actionsheet. No matter on which other tabbar I am. So my questions are:
Where do I add this tab bar button? Because all other buttons I can only define after creating the segue to the new view controller
Where do I place the code for the action sheet?!
I'm not very clued up on the tabbar, however i would do the following:
Assuming you are using a TabBarController View or a Central View With a TabBar in it.
In the -viewDidLoad
NSMutableArray *tmp = [[NSMutableArray alloc] initWithArray:self.tabBarController.viewControllers];
UIViewController *sheet= [[UIViewContoller alloc] init];
sheet.title = #"Sheet";
sheet.tabBarItem.image = [UIImage....];
[tmp addObject:sheet];
[self.tabBarController setviewControllers:tmp];
self.tabBarController.delegate = self;
Then place the following
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
if([viewController.Title isEqualToString:#"Sheet"])
{
//ActionSheet create code here
retrun NO;
}
else
{
return YES;
}
}
Okay, so in the process of developing my newest app, I found that my storyboard got huge, so in an effort to clean it up some, i have divided it into multiple storyboards before it gets out of hand. just for settings alone i have roughly 20 tableviewcontrollers that branch out from a root NavigationController. That navigationcontroller was a TabItem on a TabBarController, which is the application's root view controller.
I've moved the TabBar into it's own StoryBoard as the Root_Storyboard and the Navigation controller is now the initial view of the Settings_Storyboard.
Just for testing purposes, I placed a few UIViewControllers as tab items in the TabBarController (Root_Storyboard) and subclassed one and added the following code to it's viewWillAppear method. It works great, but I know that the presentViewController displays the NavigationController modally and hides the tabBar. Obviously I don't want that, how do I get it to push properly so that the TabBar remains visible?
- (void) viewWillAppear:(BOOL)animated {
UIStoryboard *settingsStoryboard = [UIStoryboard storyboardWithName:#"Settings_iPhone" bundle:nil];
UIViewController *rootSettingsView = [settingsStoryboard instantiateInitialViewController];
[self.tabBarController presentViewController:rootSettingsView animated:NO completion:NULL];
}
Edit - To clarify. The above code is the subclassed method for a UIViewController (child of UITabBarController:index(1)) in the Root_iPhone.storyboard. The UINavigationController/UITableViewController that I am trying to load is found in Settings_iPhone.storyboard. Not sure how to implement the linkView suggested below in this situation.
This is quite possible and a smart move - decluttering your Storyboards presents cleaner interface files to dig through, reduced loading times in XCode, and better group editing.
I've been combing across Stack Overflow for a while and noticed everyone is resorting to Custom Segues or instantiating tab based setups programmatically. Yikes. I've hacked together a simple UIViewController subclass that you can use as a placeholder for your storyboards.
Code:
Header file:
#import <UIKit/UIKit.h>
#interface TVStoryboardViewController : UIViewController
#end
Implementation file:
#import "TVStoryboardViewController.h"
#interface TVStoryboardViewController()
#property (nonatomic, strong) UIViewController *storyboardViewController;
#end
#implementation TVStoryboardViewController
- (Class)class { return [self.storyboardViewController class]; }
- (UIViewController *)storyboardViewController
{
if(_storyboardViewController == nil)
{
UIStoryboard *storyboard = nil;
NSString *identifier = self.restorationIdentifier;
if(identifier)
{
#try {
storyboard = [UIStoryboard storyboardWithName:identifier bundle:nil];
}
#catch (NSException *exception) {
NSLog(#"Exception (%#): Unable to load the Storyboard titled '%#'.", exception, identifier);
}
}
_storyboardViewController = [storyboard instantiateInitialViewController];
}
return _storyboardViewController;
}
- (UINavigationItem *)navigationItem
{
return self.storyboardViewController.navigationItem ?: [super navigationItem];
}
- (void)loadView
{
[super loadView];
if(self.storyboardViewController && self.navigationController)
{
NSInteger index = [self.navigationController.viewControllers indexOfObject:self];
if(index != NSNotFound)
{
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[viewControllers replaceObjectAtIndex:index withObject:self.storyboardViewController];
[self.navigationController setViewControllers:viewControllers animated:NO];
}
}
}
- (UIView *)view { return self.storyboardViewController.view; }
#end
Description:
The view controller uses its Restoration Identifier to instantiate a storyboard in your project.
Once loaded, it will attempt to replace itself in its
UINavigationController's viewController array with the Storyboard's
initial view controller.
When requested, this subclass will return the UINavigationItem of the Storyboard's initial view controller. This is to ensure that navigation items loaded into UINavigationBars will correspond to the view controllers after the swap.
Usage:
To use it, assign it as the subclass of a UIViewController in your Storyboard that belongs to a UINavigationController.
Assign it a Restoration ID, and you're good to go.
Setup:
And here's how you set it up in the Storyboard:
This setup shows a tab bar controller with navigation controllers as its first tab controllers. Each navigation controller has a simple UIViewController as its root view controller (I've added UIImageViews to the placeholders to make it easy to remember what it links to). Each of them is a subclass of TVStoryboardViewController. Each has a Restoration ID set to the storyboard they should link to.
Some wins here:
It seems to work best for modal presentations where the subclass is the root view controller of a navigation controller.
The subclass doesn't push any controllers on the stack - it swaps. This means you don't have to manually hide a back button or override tab behaviour elsewhere.
If you double tap on a tab, it will take you to the Storyboard's initial view, as expected (you won't see that placeholder again).
Super simple to set up - no custom segues or setting multiple subclasses.
You can add UIImageViews and whatever you like to the placeholder view controllers to make your Storyboards clearer - they will never be shown.
Some limitations:
This subclass needs to belong to a UINavigationController somewhere in the chain.
This subclass will only instantiate the initial view controller in the Storyboard. If you want to instantiate a view controller further down the chain, you can always split your Storyboards further and reapply this subclass trick.
This approach doesn't work well when pushing view controllers.
This approach doesn't work well when used as an embedded view controller.
Message passing via segues likely won't work. This approach suits setups where sections of interface are unique, unrelated sections (presented modally or via tab bar).
This approach was hacked up to solve this UITabBarController problem, so use it as a partial solution to a bigger issue. I hope Apple improves on 'multiple storyboard' support. For the UITabBarController setup however, it should work a treat.
This is a bit late for Hawke_Pilot but it might help others.
From iOS 9.0 onwards you can create a Relationship Segue to another storyboard. This means that Tab Bar View Controllers can link to View Controllers on another storyboard without some of the mind-bending tricks seen in other answers here. :-)
However, this alone doesn't help because the recipient in the other storyboard doesn't know it's being linked to a Tab Bar View Controller and won't display the Tab Bar for editing. All you need to do once you point the Storyboard Reference to the required View Controller is select the Storyboard Reference and choose Editor->Embed In->Navigation Controller. This means that the Nav Controller knows it's linked to a Tab Bar View Controller because it's on the same storyboard and will display the Tab Bar at the bottom and allow editing of the button image and title. No code required.
Admittedly, this may not suit everyone but may work for the OP.
Not sure if your question is answered, and for others looking for a solution to this problem, try this method.
Create the Tab Bar Controller with Navigation Controllers in one storyboard file. And add an empty view controller (I named it RedirectViewController) as shown in the picture.
The child view controller (let's call it SettingsViewController for your case) is located in Settings_iPhone.storyboard.
In RedirectViewController.m, code this:
- (void)viewWillAppear:(BOOL)animated
{
UIStoryboard *settingsStoryboard = [UIStoryboard storyboardWithName:#"Settings_iPhone" bundle:nil];
UIViewController *rootSettingsView = [settingsStoryboard instantiateInitialViewController];
[self.navigationController pushViewController:rootSettingsView animated:NO completion:nil];
}
SettingsViewController will be pushed into view instantly when Settings tab is touched.
The solution is not complete yet! You will see "< Back" as the left navigationItem on SettingsViewController. Use the following line in its viewDidLoad method:
self.navigationItem.hidesBackButton = YES;
Also, to prevent the same tab bar item from being tap and causes a jump back to the blank rootViewController, the destination view controllers will need to implement UITabBarControllerDelegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
return viewController != tabBarController.selectedViewController;
}
It works for me.
Add Following code to your LinkViewController
-(void) awakeFromNib{
[super awakeFromNib];
///…your custom code here ..
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:self.storyBoardName bundle:nil];
UIViewController * scene = nil;
// Creates the linked scene.
if ([self.sceneIdentifier length] == 0)
scene = [storyboard instantiateInitialViewController];
else
scene = [storyboard instantiateViewControllerWithIdentifier:self.sceneIdentifier];
if (self.tabBarController)
scene.tabBarItem = self.tabBarItem;
}
Here is the screenShot for LinkViewController .
LinkViewController is just a placeholder where new viewController would be placed. Here is the sample code which I used for my app.
RBStoryboardLink . Its working great for me. Let me know if it is helpful for you.
I'm trying to select no tabs at all in my application. At first the first tab is selected, but I'd like to deselect it so no tabs at all would be selected.
Don't ask me why, it's just that way the client wants it! hehe
Thanks for your help!
PS: I already tried:
// rootController = UITabBarController
rootController.tabBar.selectedItem = 0;
rootController.tabBar.selectedItem = nil;
[rootController setSelectedIndex:[rootController.items objectAtIndex:0]];
[rootController setSelectedIndex:nil];
[rootController setSelectedIndex:0];
// That one works : (but I can't select 0 or -1 for instance)
[rootController setSelectedIndex:2];
Any ideas? Thanks again!
You can deselect all tab bar items if you are using UITabBar instance without UITabBarController one.
In such case below code works well.
[tabBar setSelectedItem:nil];
If UITabBar is a part of UITabBarController then application will crash with exception:
'Directly modifying a tab bar managed
by a tab bar controller is not
allowed.'
In other words if you would like to get this worked you need to manage tabbar's routines manually without controller.
I just came across this question and it's actually really simple:
tabBarController.selectedViewController = viewController;
This is somewhat similar to HG's answer, but setting the selected view controller to nil is unnecessary.
I finally managed to do this using the following code:
DefaultView *defaultView = [[DefaultView alloc]initWithNibName:#"DefaultView" bundle:[NSBundle mainBundle]];
[self.tabBarController setSelectedViewController:nil];
[self.tabBarController setSelectedViewController:defaultView];
Note that just doing [self.tabBarController setSelectedViewController:nil]; won't do anything. You HAVE TO specify a view controller. This view Controller will be displayed with no tabBar icon selected. Upon selecting the other TabBar options, the defaultView will disappear and the required view will be loaded.
Better to change selected image whenever you want & make a view hide or show according to your requirement. Here my piece of code that could help to understand:
-(void)viewWillAppear:(BOOL)animated{
if ([[NSUserDefaults standardUserDefaults]integerForKey:#"flagAsk"]) {
UITabBarItem *firstTab = [self.tabBarController.tabBar.items objectAtIndex:0];
firstTab.selectedImage = [[UIImage imageNamed:#"Ask2"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal ];
vieToHide.hidden=YES;
}
else{
UITabBarItem *firstTab = [self.tabBarController.tabBar.items objectAtIndex:0];
firstTab.selectedImage = [[UIImage imageNamed:#"Ask"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal ];
vieToHide.hidden=NO;
}
}
From the documentation:
This view controller is the one whose custom view is currently displayed by the tab bar interface. The specified view controller must be in the viewControllers array. Assigning a new view controller to this property changes the currently displayed view and also selects an appropriate tab in the tab bar. Changing the view controller also updates the selectedIndex property accordingly. The default value of this property is nil.
So, I would assume you need to [rootController setSelectedViewController: nil];.
Update:
To clarify a bit,
[self.tabBarController setSelectedViewController:nil];
There is also documentation on preventing the selection of tabs that could be helpful.
Are there better methods?
use [self.tabBarController setSelectedViewController:nil],
Warning : "-[UITabBarController setSelectedViewController:] only a view controller in the tab bar controller's list of view controllers can be selected."
I think rootController.tabBar.selectedItem = 0;
it's wrong whatever you have tried.
Because when You are setting selectedItem=0, then sure it will take the first tabBarItem of tabBarController.