I have googled far and wide, but everything is extremely confusing. I need it so when the tab bar gets switched to a different view controller, is there a method that gets called when the view controllers are about to switch, get the destination controller and set some variables to add some annotations to a MKMapView. How can I do this?
There are many options, but one of them is to implement the UITabBarControllerDelegate protocol and then set the class that implements it as the delegate of your UITabBarController. The delegate receives a message - tabBarController:didSelectViewController:
In that method you can implement the behavior you desire by looking at the last view controller and the next one. To get access to those view controllers from your delegate, you may need to add them as weak properties to your delegate class. You can also access all of your UITabBarController's sub view controllers through its viewControllers property which is an array of view controllers.
After some experimentation with the delegate methods, I was able to find an answer.
I used the - (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController method.
This feels like a fairly decent replacement because you can get each controller easily.
Warning: This method should not be used to handle large blocks of code, but to set up a view controller to run large blocks of code.
A test implementation:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
FirstViewController *currentController = (FirstViewController *)[tabBarController selectedViewController];
SecondViewController *destinationController = (SecondViewController *)viewController;
// If you want, do some code on these here. For more precision, read on.
return YES;
}
This can be used with logic to determine whether you should execute some specific code: EX:
if ([[tabBarController selectedViewController] class] == [FirstViewController class]) {
if ([viewController class] == [SecondViewController class]) {
// It is going from first to second. Do some code here.
}
}
Also, you have to have set the delegate
TabBarController .h
#interface TabBarController : UITabBarController<UITabBarControllerDelegate>
...
TabBarController .m
...
- (void)viewDidLoad
{
self.delegate = self;
}
...
Related
I have a Show segue that is embedded in a UITabBarController. I want to prevent an unwind segue when I tap the currently selected tab unless a certain condition is met. I've tried using shouldPerformSegueWithIdentifier and canPerformUnwindSegueAction but neither appear to be triggered when unwinding in this way.
Not sure what you mean by unwind segue on a tab bar, but if you want to prevent a tab change, there is a delegate function on UITabBarController for that purpose.
Add the protocol to your tab bar class.
#interface YourTabbarViewController () <UITabBarControllerDelegate>
#end
Assign the delegate, then later implement the function.
#implementation YourTabbarViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = self;
}
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
if (preventTabChange)
return NO;
return YES;
}
UPDATE
OK, assuming you have set up relevant parts as on this picture, and you want to prevent the unwind from B to A if certain conditions are met. My solution as described above will work.
As you will get a query/notification whenever the Navigation Controller is about to become active, you could create your own sub-class of that to hold whatever information you need to decide if it should be allowed to show or unwind from a sub-view controller. In that case your prevention could look like this (expanding the shouldSelectViewController above):
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
if ([viewController isKindOfClass:[YourNavigationController class]]) {
if ([(YourNavigationController *)viewController preventUnwind])
return NO;
}
return YES;
}
Note that I purposely chose preventUnwind as a flag in your custom class to say what to do. This will default to NO when you move to the view controller, and thus allowing that.
Don't forget to set YourTabbarViewController as the class for the Tabbar View Controller and YourNavigationController as the Navigation Controller in the picture.
How to delegate methods actually get called?
Like lets say I add this to my UIViewController.m:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// code
}
Does the method get called when the view controller appears? How does delegation work?
According to the docs, the delegate is called just before the navigation controller displays a viewController’s view and navigationItem properties.
But, it appears that your question is, "how?".
In your Storyboard or somewhere in code, the UINavigationController is set up. Wherever that is, it has a delegate property (a variable). That property is set to some object that implements the UINavigationControllerDelegate protocol.
For example:
MySpecialViewController *myViewController = [[MySpecialViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:myViewController];
navigationController.delegate = self;
Now, whenever that navigationController is about to present a view controller, the navigationController:willShowViewController:animated: delegate method will be called on your object.
If you set your object as the delegate of the navigation controller, the navigation controller will call this method when a view controller is about to appear.
For delegate method implementation details, see this question and Concepts in Objecctive-C Programming: Delegates and Data Sources.
Well it's probably something like this inside UINavigationController class:
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[self.delegate navigationController:self willShowViewController:viewController animated:animated];
//do stuff to actually push the view controller
}
If the delegate is nil nothing happens because messages to nil make no effect, but if you set the delegate that method is invoked. I'm not sure if it happens directly inside pushViewController: method, but that doesn't really matter. Whenever the navigation controller is about to show next view controller it sends a message to its delegate
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 have 2 versions of a tabbed ios5 application, one created using a storyboard and one using xib files. The storyboard version does not call the UITabBarControllerDelegate method didSelectViewController (the xib version does). Something is (I think) missing from the storyboard, but I don't know what. Another way of framing the question might be — how can I refer to the UITabBarController object instantiated by the storyboard?
Thanks for your help.
EDIT: The tab bar controller delegate is set:
In AppDelegate.h:
#interface MyAppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
#property (strong, nonatomic) UITabBarController *tabBarController;
In AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.tabBarController.delegate = self;
return YES;
}
Then later in AppDelegate.m, the delegate method is:
- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
NSLog(#"Got Here");
}
The NSLog output never appears. The problem seems to me to be that I am not correctly referencing the tab bar controller object which has been instantiated by the storyboard.
How do I do that?
I had this issue.
If you're not using storyboards, setting the UITabBarController delegate in the AppDelegate is the way to go. However, with Storyboards, the AppDelegate has no idea where the tabBarController is on startup. You'd think by subclassing the tabBarController and adding the delegate method:
(void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController {
}
... would be enough. But, it's irritatingly not.
I needed to know when a user had pressed a tab button. I needed to know this more that I needed to know that the viewController's "- (void)viewWillDisappear:(BOOL)animated {}
" method had been run.
I decided to make my UITabBarController a delegate of itself. This seemed silly to me but I did the following...
#import <UIKit/UIKit.h>
#interface PlumbsTabBarController : UITabBarController <UITabBarControllerDelegate>
#end
And then, in my viewDidLoad method, wrote the following:
[self setDelegate:self];
Which enabled my tab bar delegate methods to run.
Crazy or what?
Ok - I'm editing this answer now, as even though the above is all correct, where a navigationController is being used, selected with each tabBarButton touched, the didSelectViewController delegate method will, when you try to NSLog(#"%#", viewController); only show you that you have selected the UINavigationController class?
So, the total solution, just to add more complexity, is to subclass the UINavigationController for each viewController that you want to monitor, (do something) when the tabBarbutton has been touched.
It works for me anyhow. And, if anyone can nit-pick through the above dribble, they might find an aspect that's useful - and that's enough for me - seeing as I find this site utterly useful too.
Put [self setDelegate:self]; in your ViewDidLoad or somewhere where the object get's initialized
I have 2 view controllers and a tab bar controller created in storyboard.
is it possible to execute a method in either of the 2 view controllers when the relevant tab bar is pressed?
Ive tried several ways but they need a nib name on the firstViewController or secondViewController if I want to initialize an object of the firstViewController, normally the firstViewController is just created on launch,
Any help would be appreciated, I'm vaguely familiar with the uitabcontroller app delegate but I don't know how to hook up the two view controllers to the tab controller
Have a look at the UITabViewController Delegate :
You use the UITabBarControllerDelegate protocol when you want to
augment the behavior of a tab bar. In particular, you can use it to
determine whether specific tabs should be selected, to perform actions
after a tab is selected, or to perform actions before or after the
user customizes the order of the tabs. After implementing these
methods in your custom object, you should then assign that object to
the delegate property of the corresponding UITabBarController object.
All of the methods in this protocol are optional.
Reference : http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UITabBarControllerDelegate_Protocol/Reference/Reference.html
What you need should be achievable by implementing :
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
If you are using storyboard, do this
in didFinishLaunchingWithOptions
UITabBarController *tabBar = (UITabBarController *)self.window.rootViewController;
[tabBar setDelegate:self];
And then
-(void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
//Write your code here
}