I've got a project setup using a Storyboard that contains a UITabViewController as the initial root view. One of the tabs loads a NavigationController that in turn loads a custom view controller class.
From the custom view controller, I have a navigation bar button that I want to trigger an action that returns the root UITabViewController to it's first index. I've been able to do this using a traditional xib structure by adding the appDelegate class to the xib and linking a method to the button that way.
Effectively, I want the button to trigger code that looks something like this:
#implementation AppDelegate
#synthesize window = _window;
#synthesize tabBarController=_tabBarController;
-(IBAction)handleHome:(id)sender{
//How do I send a message to the tabBarController?
[self.tabBarController setSelectedIndex:0];
}
Is it possible to do this with the Storyboard approach? I looked at Segue's but that doesn't seem to be what I'm trying to do (there is no way for me to talk to the root UITabViewController from what I can see).
I've got the handeHome method being triggered using the Responder approach, so really all I need to know is how to access the instantiated tabViewController in the Storyboard.
Hopefully this question makes sense, let me know if there is anything I should expand on.
Why not just do this in your custom view controller?
- (IBAction)handleHome:(id)sender {
self.tabBarController.selectedIndex = 0;
}
The tabBarController property is built in to UIViewController.
I figured it out. I updated the quoted block of code to this:
#implementation AppDelegate
#synthesize window = _window;
-(IBAction)handleHome:(id)sender{
UITabBarController *tabViewController = (UITabBarController *) self.window.rootViewController;
[tabViewController setSelectedIndex:0];
}
Sigh... need more coffee before asking questions on SO
Related
I am relatively new to Xcode and have tried to find the answer by searching, without luck.
My app has 5 View Controllers, V1 through V5, which are embedded in one Tab Bar Controller. Each View Controller has a segue to one and the same Setup Menu View Controller. The Menu changes some labels on the View Controllers. I use a delegate to make sure that the View Controller that calls the Menu gets updated with the new settings when you leave the Menu. However, this allows me to modify only the labels on the View Controller that called the Menu Controller, not on the 4 other ones.
I work form a Story Board. Is there a simple way to set the UILabels on V2, V3, V4 and V5 from V1 (and vice versa), or even better, set the labels on V1 through V5 from the Menu View Controller (which is not embedded in the Tab Bar Controller)?
I have seen something that could help here, but this seems rather complicated for what I want. The label changes I need are quite simple and are all predefined. Is there a method that is called every time you switch tabs in a tabbed application? Similar to ViewDidLoad?
This sounds like a good time for NSNotificationCenter. You are going to have your MenuViewController generate a notification with the new data that should be updated in your other view controllers:
// User has updated Menu values
[[NSNotificationCenter defaultCenter] postNotificationName:#"MenuDataDidChangeStuffForLabels" object:self userInfo:#{#"newLabelValue" : labelText}];
In your V1, V2, etc. you can add subscribe to these notifications using this code in your viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
// Subscribe to NSNotifications named "MenuDataDidChangeStuffForLabels"
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabelText) name:#"MenuDataDidChangeStuffForLabels" object:nil];
}
Any object that subscribes using that code will call the updateLabelText method anytime a notification with that name is posted by the MenuViewController. From that method you can get the new label value and assign it to your label.
- (void)updateLabelText:(NSNotification *)notification {
NSString *newText = notification.userInfo[#"newLabelValue"];
myLabel.text = newText;
}
What I would do is subclass the tab bar controller and set that as the delegate for the menu view controller. From there, you can get updated when the labels are supposed to change and then communicate with the 5 tabs and update the labels.
Alternatively, you could use NSNotifications to let all the 5 view controllers know when settings change.
Lastly, you could add the menu settings to a singleton and have all of the view controllers observe the various properties that can change.
The label changes I need are quite simple and are all predefined. Is there a method that is called every time you switch tabs in a tabbed application? Similar to ViewDidLoad?
Regarding this question, the methods you're looking for are viewWillAppear: and viewDidAppear.
Here is a very simple solution if your workflow is also simple. This method changes all the labels from the different ViewControllers directly from what you call the Menu ViewController.
Let's say you have the following situation :
The blue ViewController is of the FirstViewController class. The green ViewController is of the SecondViewController class. The labels on each of those are referenced by the properties firstVCLabel and secondVCLabel (on the appropriate class' header file). Both these ViewControllers have a "Modal" button which simply segues modally on touch up inside.
So when you clic on any of these two buttons, the orange ViewController (of ModalViewController class) is presented. This ViewController has two buttons, "Change Label" and "Back", which are linked to touch up inside IBActions called changeLabel: and back:.
Here is the code for the ModalViewController :
#import "ModalViewController.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#interface ModalViewController ()
#end
#implementation ModalViewController
// Action linked to the "Change Label" button
- (IBAction)changeLabel:(id)sender {
// Access the presenting ViewController, which is directly the TabBarController in this particular case
// The cast is simply to get rid of the warning
UITabBarController *tabBarController = (UITabBarController*)self.presentingViewController;
// Go through all the ViewControllers presented by the TabBarController
for (UIViewController *viewController in tabBarController.viewControllers) {
// You can handle each ViewController separately by looking at its class
if ([viewController isKindOfClass:[FirstViewController class]]) {
// Cast the ViewController to access its properties
FirstViewController *firstVC = (FirstViewController*)viewController;
// Update the label
firstVC.firstVCLabel.text = #"Updated first VC label from Modal";
} else if ([viewController isKindOfClass:[SecondViewController class]]) {
SecondViewController *secondVC = (SecondViewController*)viewController;
secondVC.secondVCLabel.text = #"Updated second VC label from Modal";
}
}
}
// Action linked to the "Back" button
- (IBAction)back:(id)sender {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
For the sake of completeness, here are FirstViewController.h :
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *firstVCLabel;
#end
And SecondViewController.h :
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *secondVCLabel;
#end
There is no relevant code in the implementation of these classes.
Thanks a lot guys, I am impressed by your quick responses. In this particular case, viewWillAppear does the trick:
- (void)viewWillAppear:(BOOL)animated
{ [self AdaptLabels];
NSLog(#"View will appear.");
}
Every time a new tab is chosen, it updates the labels in the new View, according to a global variable set by the Menu, just before they appear. Very quick and clean. Thanks to all of you!
I have an app which displays a simple tableview and I wanted to add the SWRevealViewController as well.
In my appDelegate, before I added the SWReveal VC, I was setting my tableViewController like so...
In didFinishLaunchingWithOptions:
STRTableViewController *tableViewController = [(UINavigationController *)self.window.rootViewController viewControllers][0];
self.delegate = tableViewController;
and then again in the below method:
- (void)loadTableViewData
{
UINavigationController *navVC = (UINavigationController *)self.window.rootViewController;
STRTableViewController *tableVC = navVC.childViewControllers[0];
[tableVC loadTableData]
}
Obviously when I put the SWRevealViewController to the front of the line, this no longer works as it is now trying to call loadTableData from the wrong view controller.
I've tried several ways and keep coming up short. How do I go about accessing the tableViewController now that it is not the first view controller?
If you need more code or logs or anything I'll be happy to post additional info. I have a feeling the answer is right there, I just don't have the experience to see it.
Also, just to be clear, now in the storyboard it goes from Reveal View Controller to Navigation Controller (the tableview's nav VC/ sw_front) and also to the sw_rear VC. Before it simply started with the Navigation Controller.
Thanks!
There's a bunch of ways you can go about keeping a reference to this.
The simplest would be just to keep a reference to the view controller in the AppDelegate.m
So you add a property
#property (nonatomic, strong) STRTableViewController *tableViewController;
Then, whenever and wherever you are instantiating and setting that table view controller, just do something like:
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
delegate.tableViewController = justCreatedTableViewController;
You'll need to #import "AppDelegate.h" to access the app delegate in other classes where you want to do this.
Then to access it you can just do something like:
- (void)loadTableViewData
{
[self.tableViewController loadTableData]
}
I make a Tabbed Application using storyboard template, two view controllers are embedded.
This is what I want to do: in the first viewController, let TabBar to select the second viewController programmatically.
The first viewController is a tableViewController, shows a list of items, and each item will push to a detailViewController. In the detailViewController, I edit some information and save the item. Then I want app to show the second ViewController, which is a tableViewController shows saved item.
Usually, we can use [TabBarController setSelectedIndex:1]; to select the second viewController.
However, since this is a storyboard template application, so many code are hidden behind. So I cannot get the TabBar instance in the first viewController, and use setSelectedIndex method.
This is what confuses me...
And now, I have found the solution for this problem. My answer is below.
I have figured out how to solve this problem.
First I add new a class: MyTabBarController.
Then, in storyboard, select the Tab Bar Controller, in identity inspector panel, set the custom class to this new class.
For the first viewController class, add a property
#property (nonatomic, weak) UITabBarController *tabBarController;
Then add - (void)viewDidAppear:(BOOL)animated in MyTabBarController class:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UINavigationController *navigationController = [self.viewControllers objectAtIndex:0];
FirstViewController *firstViewController = (FirstViewController *)navigationController.topViewController;
firstViewController.tabBarController = self;
In this way, I pass the tabBarController instance to the firstViewController, so, in the firstViewController, I can call [tabBarController setSelectedIndex:1];
Storyboard gives me a visual interface, however, it hides so many things behind.
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 have UITabViewController running with UINavigationController. In each tab i have a different TableViews that are roots of my application tree.
When i click an item of table, it goes to next level viewing another, detailed TableView, still having TabBar and NavigationBar on screen. It works perfectly, except for one thing. If i'm viewing details in one tab, then switch to another tab, and go back again, then i still see my detail. What i want to achieve, is to reset tab after leaving it.
I expect that i have to put something in viewDidUnload or simmilar, but couldn't find the right solution.
Hope you can help.
at
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
do
[viewController.navigationController popToRootViewControllerAnimated:YES];
That will navigate the tab's view controller to the root view whenever the tab is selected.
That's completely OK to keep your main window view controller (tab bar controller) instance at application delegate.
What you need to add is to set the delegate or whatever other initialized class to be the tab bar controller's delegate like this:
myTabBarController = [UITabBarController alloc ...
myTabBarController.delegate = self; // the app delegate will be also the tab bar delegate
in the app delegate, then you add the following method to the app delegate:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
[viewController.navigationController popToRootViewControllerAnimated:YES];
}
The tab bar controller will call this method whenever it's tab is selected.
You will also want to make the application delegate confirm UITabBarControllerDelegate this way:
#interface PSAppDelegate : UIResponder <UITabBarControllerDelegate>
that will let the compiler know that app delegate must or might have the methods declared in the protocol and will also give you so convenient auto-complition of this method.
One way to do this is to get the view controllers associated with the tabviewcontroller and then create new objects.
self.tabBarController?.viewControllers! will get an array of viewControllers for the tab bar. In my case they are a UINavigationControllers but they could be anything. I then get the viewController associated with that and reset that.
If you don't have navigation controllers you can just create new view controller objects and go with that.
below is the solution in my case (with the navcontroller)
let vc = self.tabBarController?.viewControllers![0] as! UINavigationController
let newVC = YourViewControllerClass()
vc.viewControllers[0] = newVC as UIViewController
This replaces the old (populated) vc with a new one!