I have a TabBarController with 4 tabs setup in storboard editor. When I select the 4th tab, I want to send a string to its view controller. In my TabbarController class (named TabBarVC) I implemented the following method:
- (void)tabBarController:(UITabBarController *)theTabBarController didSelectViewController:(UIViewController *)viewController {
NSUInteger indexOfTab = [theTabBarController.viewControllers indexOfObject:viewController];
if(indexOfTab == 3) {
SupplierListVC *slvc = (SupplierListVC *) viewController;
slvc.locationType = #"favorite";
self.tabBarController.selectedViewController = slvc;
}
}
This method is being called just fine but the string is not getting passed. When I debugged, I noticed that the above piece of code is called after viewDidLoad of SupplierListVC. What am I doing wrong here? How can I pass the string when I select the tab?
As you said, this method is called after viewDidLoad. The method name itself should tell you that: **DID**SelectViewController
Past tense. English may not be your first language, but a good understanding of English verb tense goes a really, really, really, really, really long way when working with Apple's method names. They're not misleading. They tell you exactly when/what/why that method is for, just in the method name.
Now then, the string SHOULD be appropriately passed into the view controller--you're probably just checking it at the wrong time.
But with a tab bar, the view controllers it contains aren't loaded each time that tab is switched to. The tabs are preloaded once and will only ever be unloaded if the tab bar controller itself is dismissed (or perhaps the view controller is removed from the tab bar controller's view controllers array).
You can use tabBarController:shouldSelectViewController:
This method, like the one you're using, gives you a reference to the tab bar controller and the view controller it's trying to switch too. It returns a BOOL value however. If you return NO the switch won't happen. You can simply always return YES if you want, the main point here though is that this method is called before the switch starts and therefore definitely before viewWillAppear and viewDidAppear are called.
I had the same issue when passing data from tabbar controller to viewcontroller.
NumberpadViewController *numpad= [[self viewControllers] objectAtIndex:1];
numpad.number=#"12345";
I had get number as nil in viewdidload method.
- (void)viewDidLoad {
NSLog(#"number#",number);//nil value
}
So I had used viewdidappear method to perfom processing on the number data.
-(void)viewDidAppear:(BOOL)animated{
NSLog(#"number#",number);//12345
}
Related
I have an iOS app that displays multiple screens and has different root controller for both iPhone and iPad. Here is a simplified example code to show what is going on.
if (iPad) {
self.sideMenuController = [LGSideMenuController sideMenuControllerWithRootViewController:viewControllerA
leftViewController:viewControllerB
rightViewController:nil];
} else {
self.sideMenuController = [LGSideMenuController sideMenuControllerWithRootViewController:viewControllerB
leftViewController:viewControllerA
rightViewController:nil];
}
[self.navigationController pushViewController:self.sideMenuController animated:NO];
I need to be able to tell when both controllers (ViewControllerA and ViewControllerB) is loaded.
I've implemented the following delegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (self.viewControllerA.viewIfLoaded.window != nil && self.viewControllerB.viewIfLoaded.window != nil) {
// do stuff after both controllers have been loaded and it is current view.
}
}
The delegate solution works, but not sure if it is best practice. I check if viewControllerA and viewControllerB is not nil and current view controllers because I push other controllers in the navigation controller and don't want to do anything if that happens.
It seems fragile. You're making a lot of assumptions and not (as far as I can tell) asking the navigation controller the question you really want to know the answer to. That question would be (as far as I can tell):
Is the view controller that just got pushed one of VCA and VCB?
Is the other of that pair already the navigation controller's child?
Assuming that the root view controller always loads first, you could perform work in the viewDidLoad method of the non-root view controller, which you could determine by referencing the device type.
You can have some BaseClass that both viewControllers inherit from (ViewControllerA and ViewControllerB) and in that BaseClass, you can use viewDidLoad: method to run whatever code you want.
I have a UITabBarController with Tabs (say Tab1, Tab2, Tab3, Tab4) and UITabBarController is my RootViewController and I'm making an API Call in the same. Since it is a RootViewController I'm displaying Tab1 as my default View. When I get the results to my API Call in UITabBarController. I need to share the details in real time to my Tabs. I have tried few ideas(like NotificationCenter, Singleton Class) but it's not working out. Can somebody help me to fix this? Thanks in advance. If you have a working example I kindly request you to share it with me.
Img Representation:
UITabBarController has a property viewControllers that is an array of view controllers displayed (by index for tab position, but you probably don't need that). One simple solution is when your UITabBarController subclass gets data, it can loop over the viewControllers array and check each for delegate conformance / responds to selector and update accordingly.
That's probably the simplest. Another way would be to have the view controllers in the tabs register for updates. So they conform to a delegate protocol, and get a reference to their tab bar controller (the tab bar controller subclass), and call the tab bar saying "observe updates." The tab bar controller keeps storage of registered observing objects and calls each with new data.
This assumes you are setting the tab's view controllers in IB. If you are doing it programmatically, then with the second option you can just link the tabs to the tab bar controller when you add them. If set in IB you could also do it by overriding -prepareForSegue since embedding the tabs in the root is considered a segue, but you'd still have to cast the destinationViewController as whatever subclass can receive data.
The first option is simple enough and though I don't like casting, it's unavoidable to take advantage that the references you want are right there. Plus, there will in reality only ever be a single-digit number of tabs, so it can't get expensive. Hope this helps.
This is just same like passing the values to another viewController but here you need to use tabBarController's delegate method in below example:
Passing value using delegate method from controller A:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
ControllerB *controllerB = (ControllerB *) [tabBarController.viewControllers objectAtIndex:1];
//In this example, there is only have 2 tab bar controllers (A and B)
//So, index 1 is where controller B resides and index 0 where controller A resides.
self.controllerB.data.text = #"some value!";
//This will change the text of the label in controller B
}
Getting value in controller B
// set property in controller B .h file
#property(strong, nonatomic) UILabel *data;
// in controller B .m file viewDidLoad method
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"getting data: %#",data.text); // getting value
}
How about
-Declare a delegate protocol, say, Tab1ViewControllerDelegate, with a method - (void) tab1ViewController:(Tab1ViewController *) tab1ViewController, didReceiveData: (NSDictionary *) data
make a TabBarController subclass a delegate of your tab1ViewController
when tab1ViewController gets it's data, call [self.delegate tab1ViewController: self, didReceiveData: datDict]
then your TabBarController can distribute the data to its array of viewControllers
Here is the problem I am having. I am unable to set the UINavigationBar title for the views I have contained within a UIPageViewController.
The basic architecture of the app is as follows.
The root view controller for the app is a UITabBarController, with 5 navigation controllers contained in it.
The first Navigation controller, which is the one I am having issues with, contains a page view controller and this page view controller contains a number of UIViewControllers.
I want that, when I swipe through each of these view controllers, I can set the title in the UINavigationBar.
I have tried the following:
In the UIViewController contained within the page view controller, I have tried [self setTitle:#"Title I want"] - it didn't work.
Within the same UIViewController I have also tried [self.navigationBar.navigationItem setTitle:#"Title I want"] - this also didn't work.
I also tried setting the title for the View controller and attempted to extract that inside the PageViewControllers delegate method transitionCompleted, but this didn't work either.
I am wondering should I go back to the drawing board, and whether I am going down a rabbit hole with this view layout architecture. Has anyone else encountered this issue and if so, how did you solve it?
Edit: I must also add that I am doing this programatically.
Thanks for the help.
So, in the end I came up with a way to get this working, albeit not the cleanest solution that I wanted, but suitable for the purpose nonetheless.
I created a new class called PageLeafViewController and set up its init method as below. Child view controllers of a page view controller inherit from this. Here is the code.
Code sample
- (id)initWithIndex:(NSUInteger)index andTitle:(NSString *)navBarTitle; {
if(self = [super init]) {
self.index = index;
self.navBarTitle = navBarTitle;
}
return self;
}
These can be initialised like so before being added to the UIPageViewController.
Code sample
ChildViewController *aChildViewController = [[ChildViewController alloc] initWithIndex:1 andTitle:#"A Title"];
You will need to add a UIPageViewControllerDelegate to your interface for your page view controller. This is so you can implement the code for the delegate methods for when your view transition has been completed, and you need to set the title.
When the UIPageViewController loads, I grab the first view controller and get its title, setting it to the UINavigationController navigation bar
Code sample
PageLeafViewController *initialViewController = (PageLeafViewController *)[self viewControllerAtIndex:0];
[self.navigationItem setTitle:initialViewController.navBarTitle];
When a transition occurs, we set the title again to that of the new child view controller, when the transitioning into view has completed.
Code sample
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
PageLeafViewController *currentLeaf = (PageLeafViewController *)[self.pageViewController.viewControllers lastObject];
[self.navigationItem setTitle:currentLeaf.navBarTitle];
}
Note: The above gets called automatically when a new child view controller has been displayed.
While this is not the most elegant solution it works for now, and I don't think its possible to call a function from within a child view to update the NavigationBar title, unless someone wants to correct me?
Hope this helps.
I don't think you're supposed to set the title on the navigationBar, have you tried self.navigationController.title = #"Title"; ?
Say you've got a UIViewController subclass called BeverageViewController, and you are using four separate instances of this controller within a UITabBarController collection. You've also got a data structure of, say, four NSString string objects, #"Beer", #"Wine", #"Whiskey", and #"More Whiskey". Each string corresponds to one BeverageViewController instance, which will use the string for a text label within its view. Hence, when a particular BeverageViewController instance is executing its viewDidLoad method, it expects to have a public property assigned with with one of these four strings.
When one has such a controller as a segue destination within a UINavigationController hierarchy, one typically uses prepareForSegue:sender: to set a property of this destination controller before viewDidLoad is called.
I've been exploring how to set a controller property before viewDidLoad, when the controller is part of a UITabBarController collection. I've determined that implementing a UITabBarControllerDelegate's tabBarController:shouldSelectViewController: method is an ok approach, except you have to dick around to get the relevant tab index of the controller:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
NSInteger index = [tabBarController.viewControllers indexOfObject:viewController];
BeverageController *controller = (BeverageController *)viewController;
controller.beverageName = self.beverageNames[index];
return YES;
}
Don't be fooled: you can't use the tabBarController.selectedIndex; you can't use the delegate method tabBarController:didSelectViewController:, because this method fires after viewDidLoad. Furthermore, you can't use self.tabBarController.selectedIndex within the controller's implementation of viewDidLoad. These three approaches seem the most obvious, but they do not facilitate the goal above.
Oh lawd, lawd, if only tabBarController:didSelectViewController: fired before the controller's viewDidLoad method. But it doesn't. Nor does it precede viewWillAppear, and preparing the view within viewDidAppear is, well, moronic.
What's the best way to set a controller property within a UITabBarController collection that enables one to have access to the property within a viewDidLoad method, without resorting to abandoning IB and procedurally managing the UITabBarController? Is it my approach above? Is it to implement a custom UITabBarController and manipulate the controllers in its viewControllers property? Or did I miss the APIobvious?
If your setting everything up in IB and those strings for the BeverageController are constant, i.e. they don't change during runtime, you can set them up in IB directly.
In the IB's inspector there is an option called "User Defined Runtime Attributes". If your BeverageController subclass has a property called let's say controllerName, you just select that VC in IB and click the little plus sign under "User Defined Runtime Attributes", the attribute name would be controllerName, then select the string-type and input you value (f.e. "Beer").
If you need to change the values dynamically, your approach with tabBarController:shouldSelectViewController: is fine.
I use application:didFinishLaunchingWithOptions: to set up my viewControllers. Get the tabBarController from your window, and then get each viewController from the tabBarController.
Something like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
NSAssert([tabBarController isKindOfClass:[UITabBarController class]], #"Must be a tabBarController");
MBFirstViewController *firstVC = tabBarController.viewControllers[0];
firstVC.foo = #"Bar";
MBSecondViewController *secondVC = tabBarController.viewControllers[1];
secondVC.foo = #"Baz";
return YES;
}
I've been converting an application to use storyboards. I'm sure this is a simple problem, but somehow I can't seem to figure out the 'correct' way of doing it, coming as we are from the old XIB world.
One of the subsections of it contains a UITabBarController, each with some subviews within it.
The action that launches this set of tabs works perfectly; I detect the segue, and set some data properties within my (custom) UITabBarController.
Next, I would like to be able to pass that data to the child views when they get created. But - because these tabs are simply 'relationships' and not segue's, I can't do what I do everywhere else, which is override the 'prepareForSegue' function.
In the old XIB universe, I'd simply bind some IBOutlets together between the tab controller and the child views. But I can't do that in storyboards, because the parent and children are separate 'scenes'.
I've tried making my UITabBarController class implement its own delegate, override 'didSelectViewController' and doing 'self.delegate = self' which almost works, except for the fact that it is never called with the first tab when the view is initially shown.
What's the "correct" (or 'best') way to do this? Please don't tell me to get/set some value on the app delegate, as this is 'global variable' territory - nasty.
Try looping through the view controllers on the UITabBarController, e.g. in this example the setData method is called from the segue in to the UITabBarController, and it then loops through the child view controllers, making a similar call on the child controller to set the data on that too;
- (void)setData:(MyDataClass *)newData
{
if (_myData != newData) {
_myData = newData;
// Update the view.
[self configureView];
}
}
- (void) configureView {
for (UIViewController *v in self.viewControllers)
{
if ([v isKindOfClass:[MyDetailViewController class]])
{
MyDetailViewController *myViewController = v;
[myViewController setData:myData];
}
}
}