I am trying to detect tapping of another tab from within a view controller embedded in a tabbarcontroller using one of the tabbarcontroller's delegate methods. However, I am confused about whether those methods can be in the individual view controllers or whether they have to be in the uitabbarcontroller class. I would like to have them in the view controllers where I have access to all the properties and local variables of those VCs rather than in the tabbarcontroller class.
I am also confused about how to set the delegate.
In a tableview controller embedded in the tabbarcontroller, I have declared the delegate protocol and then included the following code. However, the method is not firing. Is it okay to put this delegate method in a VC and if so, how and where should I set the delegate to get it to fire?
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
{
NSLog(#"DIDSELECTVC FIRED");
NSLog(#"controller class: %#", NSStringFromClass([viewController class]));
NSLog(#"controller title: %#", viewController.title);
if (viewController == tabBarController.moreNavigationController)
{
tabBarController.moreNavigationController.delegate = self;
}
}
As said Toru Furuya said better way to implement UITabBarControllerDelegate is inside subclass of UITabBarController itself.
If you want to use certain inner view controller as delegate use tabBarController property:
- (void)viewDidLoad {
[super viewDidLoad];
self.tabBarController.delegate = self;
}
You can use either individual ViewController or UITabBarController itself for delegate as long as it conforms to UITabBarControllerDelegate protocol.
I think it is more common to use UITabBarController itself (or another dedicated class) to UITabBarControllerDelegate than child ViewControllers because you can set delegate only one. But if you want to use individual ViewController, I hope this code will help you.
#implementation MyTabBarController : UITabBarController
- (id)initWithCoder:(NSCoder *)aCoder{
self = [super initWithCoder:aCoder];
if (self) {
MyTableViewController *controller = [[MyTableViewController alloc] init];
controller.tabBarItem = ...
_delegate = controller; //Set individual ViewController to UITabBarControllerDelegate
[self setViewControllers:#[controller] animated:YES];
}
return self;
}
#end
MyTableViewController is:
#interface MyTableViewController : UITableViewController<UITabBarControllerDelegate>
#end
#implementation MyTableViewController
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
//Handle tap event of UITabBarController
}
#end
Related
i've got this method in my dashBoardViewController.m
- (void)demoSetup
{
self.tabBar.tintColor = [UIColor colorWithRed:(160/255.0) green:(97/255.0) blue:(5/255.0) alpha:1]; // set the tab bar tint color to something cool.
self.delegate = self; // Just to demo that delegate methods are being called.
}
#pragma UITabBarController Delegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
NSLog(#"UITabBarDelegate: shouldSelectViewController...");
return YES;
}
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
NSLog(#"UITabBarDelegate: didSelectViewController...");
}
- (void)tabBarController:(UITabBarController *)tabBarController willBeginCustomizingViewControllers:(NSArray *)viewControllers
{
NSLog(#"UITabBarDelegate: willBeginCustomizingViewControllers...");
}
- (void)tabBarController:(UITabBarController *)tabBarController willEndCustomizingViewControllers:(NSArray *)viewControllers changed:(BOOL)changed
{
NSLog(#"UITabBarDelegate: willEndCustomizingViewControllers...");
}
- (void)tabBarController:(UITabBarController *)tabBarController didEndCustomizingViewControllers:(NSArray *)viewControllers changed:(BOOL)changed
{
NSLog(#"UITabBarDelegate: didEndCustomizingViewControllers...");
}
i've got 4 linked on it, and a view with Facebook login
i need to pass from the view that contains Facebook login 2 datas (FBID, FBUSER) to my dashBoardViewController and from it pass the same data to all of my tabs, is it possible?
this is the dashBoardViewController.h
#import <UIKit/UIKit.h>
#import <FacebookSDK/FacebookSDK.h>
#import "BFPaperTabBarController.h"
#interface dashBoardViewController : BFPaperTabBarController <UITabBarControllerDelegate,FBLoginViewDelegate>
#property (weak, nonatomic) IBOutlet FBLoginView *loginButton;
#property (retain, nonatomic) NSString *id;
#property (retain, nonatomic) NSString *first_name;
#end
Create FBID, FBUSER properties in the required viewcontroller classes.Then write a method to create a shared instance of the view controller in the view controller class
+ (instancetype)sharedInstance {
static MyViewController *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
This method will return a shared instance of MyViewController class. This is a static object.
MyViewController *myViewController = [MyViewController sharedInstance];
set your properties as myViewController.fbid = yourvalue or myViewController.first_name = yourvalue
You have to implement this shared instance method in all of your view controllers. So that you can create static objects and set a not static property as static
Here's my understanding of the view controller setup.
----- FaceBook Login VC
|
--> TabBarController --|----- VC1
|
|------VC2
|
------VC3
If the diagram looks right and you are trying to pass data between FaceBookLogin and VC -1, V-2 and VC -3.
You can do this by creating a subclass of TabBar Controller. In your subclass declare two properties for FBID and FBUSER.
If the use successfully logs in using "FaceBook Login" page, then set these properties in the custom tabBar controller.
You can now access these properties from any VC that is part of the tabbar controller.
Let me know if you need to see some code or need more detailed explanation.
Im not even sure if the title is correct but i do not understand how else to explain it so here it goes. I will show you in steps what i do so you will understand.
First off , i am using UITabBarController where i got three different items.
1) The app starts and it shows me the first Item which is a UITableView
2) I click on a cell and it pushes me to a detailed view of the cell.
3) I navigate from there to another TabBarItem
4) I navigate back to the first TabBarItem and it still shows me the view from step 3). I DONT want that i want it to show me the first view from step 1)
How can i achieve this?
You can implement UITabBarController delegate method:
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
Which will let you know when a tab is selected.
You can then access the UIViewControllers of your UITabBar using if you don't have a reference to the UIViewControllers:
#property(nonatomic, copy) NSArray *viewControllers
From that grab the refence to the appropriate UIViewController (or UITableViewController) that you want to reset and call:
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
On it's UINavigationController. Or create a public method for that call as call popToRootViewController that way.
EDIT:
Your AppDelegate should look like this:
#interface AppDelegate () <UITabBarControllerDelegate>
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
tabBarController.delegate = self;
return YES;
}
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController {
NSLog(#"was selecated");
}
Assuming you've set up your tab bar controller in the your AppDelegate, you want to make it your <UITabBarControllerDelegate>
And in your AppDelegate.m you want to implement this method like this:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
if ([viewController isKindOfClass:[UINavigationController class]]) {
[(UINavigationController*)viewController popToRootViewControllerAnimated:YES];
}
}
I am trying to implement JASidePanels where CenterViewController slides and reveals LeftViewControler which contains a TableView. Once the user selects a row in the TableView, I'd like the CenterView to regain it's position by sliding back and also have a method (within CenterViewController) do be called with a parameter from LEftViewController to update the CenterView. Can somebody please help my with this?
Thank you.
There are two parts to solving your problem:
first showing center panel when user select a row:
[self.viewController showCenterPanelAnimated:YES];
// add this method to your tableView row
passing a message back to center panel with new instruction, this can be done by creating delegate or notification. to keep it simple i will use Notification:
in your left panel class:
// Add to your tableView row method
NSNotification *msg = [NSNotification notificationWithName:#"leftPanelMsg" object:#"Hello"];
[[NSNotificationCenter defaultCenter] postNotification:msg];
in your center panel class:
add Observer in viewDidLoad and another method when message is passed back:
- (void)viewDidLoad {
[super viewDidLoad];
// method listen to meesssage with specfic name and calls selector when it get hit
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(msgResponder:) name:#"leftPanelMsg" object:nil];
}
-(void)msgResponder:(NSNotification *)notification {
NSLog(#"name:%# object:%#", notification.name, notification.object);
}
If you want to use delegates, then you have to add a protocol to the LeftViewController.h file, e.g.
#protocol LeftViewControllerDelegate <NSObject>
-(void)useThisValue:(NSString *)value;
#end
#interface LeftViewController : UITableViewController
#property (weak, nonatomic) id <LeftViewControllerDelegate> delegate;
#end
In your tableView:didSelectRowAtIndexPath: method, you can then add
[self.delegate useThisValue:menu[indexPath.row]];
Your CenterViewController will become the delegate of the LeftViewController, so in your CenterViewController.h file, import the LeftViewController.h and add:
#interface CenterViewController : UIViewController <LeftViewControllerDelegate>
- (void)useThisValue:(NSString *)value;
In your CenterViewController.m file, import both the AppDelegate.h and your RootViewController.h file. In its viewDidLoad method, you have to get a reference to the current instances of your JASidePanelController subclass (RootViewController) and the leftViewController:
RootViewController *rootViewController = (RootViewController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController];
LeftViewController *leftViewController = (LeftViewController *)rootViewController.leftPanel;
and then make the CenterViewController the delegate of the LeftViewController:
leftViewController.delegate = self;
[super viewDidLoad];
Implement the delegate method however you wish, for example:
- (void) useThisValue:(NSString *)value
{
self.label.text = value;
}
I have to acknowledge and thank Kevin McNeish here for pointing out to me how to properly get the instances of the RootViewController and the LeftViewController in order to get the delegate pattern to work with JASidePanels.
Did you see these methods in JASidePanels
// toggle them opened/closed
- (void)toggleLeftPanel:(id)sender;
- (void)toggleRightPanel:(id)sender;
JASidePanels is a good implementation, you can set the left, center and the right view controllers. So lets say that you have a UITableViewController as the left VC and according to row selection you load the center VC. You should have #import "UIViewController+JASidePanel.h" in your left VC.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.row)
case 1:
{
self.sidePanelController.centerPanel = [[UINavigationController alloc] initWithRootViewController:[[FirstViewController alloc] init]];
[self.sidePanelController toggleLeftPanel:nil];
break;
}
case 2:
{
self.sidePanelController.centerPanel = [[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc] init]];
[self.sidePanelController toggleLeftPanel:nil];
break;
}
}
I have an application with tab bar and 3 different View Controller. one of these imanage a UItableView that I designed through Interface Builder (storyboard) and I set the it's view controller class in the Inspector -> inspector identity -> and I set class field there, hence, I have no control when this view controller get instantiated, as it's done through storyboard when user click on tab bar. notice, i'm new to objective C and iOS programming.
the issue that I'm facing, i'm also using remote notification. hence, when I receive a remote notification message in "didReceiveRemoteNotification" in the AppDelgate class. I need to update UI interface (above ViewController), but the issue I don't have a reference (pointer) to this ViewController from my AppDelgate class ( or do I?). the problem this ViewController instantiated by storyboard nor programmatically, otherwise I could have kept a reference to it.
I did some reading and I understand I could do this communication via NSNotification, but I think this will be an overkill for a problem that maybe arise just because I'm new to this and I don't have full understanding of iOS development.
Thanks,
NSNotifications are easy to use and are probably the right solution.
In the app delegate that needs to send the message, just put:
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyNotification" object:someObjectYouWantToPassCouldBeAppDelegateOrRemoteNotificationObjectOrAnything];
In the view controller that is receiving the message, put:
-(void)viewDidLoad
{
[super viewDidLoad];
//you can add as many of these as you like to handle different notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNotification:) name:#"MyNotification" object:nil];
}
-(void)viewDidUnload
{
//make sure you remove every observer you've added here
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[super viewDidUnload];
}
-(void)dealloc
{
//clean up in case viewDidUnload wasn't called (it sometimes isn't)
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
//use a different handler method for each notification
//the method name should match the selector in your observe call in viewDidLoad
-(void)handleNotification:(NSNotification *)notification
{
WhateverClassOfObjectYouWerePassing *object = notification.object;
//now you have a reference to the object that was passed from your app delegate
}
For different methods you want to call, just and a new notification name and a new handler method.
Your app Delegate will have a window property which points to the apps window.
Window Property has a -rootViewController property/method.
For your Tab Based Application it would return you the TabViewController.
Each TabViewController have a method -(NSArray *)viewControllers which returns the ViewControllers inside the Tab. These are arranged in the order.
To Access your applications AppDelegate use [[UIApplication sharedApplication]delegate]
Once you have these viewcontrollers you would know which all viewController these are since you have added it in the XIB files. and can perform your methods
1. Communicate two ViewControllers
If you want to communicate two ViewControllers, you should use #protocol as Apple recommended:
ViewController1.h
#interface ViewController1 : UIViewController<ViewController2Delegate, ViewController2DataSource>
#end
ViewController1.m
- (IBAction)goToViewController2:(id)sender{
if(viewController2 == nil) {
ViewController2 *viewController = [[ViewController2 alloc]
initWithNibName:#"View2" bundle:[NSBundle mainBundle]];
viewController2 = viewController;
}
//...
viewController2.delegate = self;
viewController2.dataSource = self;
[self.navigationController pushViewController:viewController2 animated:YES];
}
- (NSString)viewController:(ViewController2 *)controller itemForSomethingAtIndex:(NSInteger)index{
//Send to viewController2 what it needs
return [items objectAtIndex: index];
}
- (void)viewController:(ViewController2 *)controller didFinishEnteringItem:(NSString *)item{
//Handle the result from the viewController2
NSLog(#"result: %#", item);
}
ViewController2.h
#import <UIKit/UIKit.h>
// Define your delegate methods to return items to the delegate of this viewController
#protocol ViewController2Delegate <NSObject>
- (void)viewController:(ViewController2 *)controller didFinishEnteringItem:(NSString *)item;
#end
// Define your dataSource methods to send items from the dataSource to this viewController
#protocol ViewController2DataSource <NSObject>
- (NSString)viewController:(ViewController2 *)controller itemForSomethingAtIndex:(NSInteger)index;
#end
#interface ViewController2 : UIViewController
#property (nonatomic) id <ViewController2Delegate> delegate;
#property (nonatomic) id <ViewController2DataSource> dataSource;
#end
ViewController2.m
#import "ViewController2.h"
#interface ViewController2 ()
#end
#implementation ViewController2
#synthesize //...;
- (void)someMethod {
//Get someThing from controller1
NSString *item = [dataSource viewController: self itemForSomethingAtIndex:0];
//Return someThing to controller1
[delegate viewController: self didFinishEnteringItem: item];
}
2. Communicate backgroundTask with viewController
If you want to communicate a background task or handle a push notification, use #NickLockwood's answer. But this dont gonna work if the viewController its not loaded. In this case you should handle that in the AppDelegate:
//Get the appDelegate instance
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
//And call your custom method to show what it needs
[appDelegate customMethod];
Your custom method should call controllers consecutevly like:
AppDelegate > RootController > ViewController1 > ViewController2 > myMethod
//do something if viewController2 is visible to the user or push it before do something.
//if you use navigation controller, then you need to ask for the position and className
I may have some misunderstanding regarding the use of the UINavigationControllerDelegate protocol. Here is my situation:
I have a ViewController, let's call it, BViewController that may display a PopoverViewController. BViewController is the second ViewController in a NavigationContoller's stack, after AViewController. I need to dismiss the PopoverViewController when the user hits a button in BViewController and the app takes us back to the previous view--AViewController.
To do that, I have implemented the following in BViewController
- (void)viewWillDisappear:(BOOL)animated {
NSLog(#"BViewController will disappear");
// Check whether the popoverViewController is visible
if (self.popoverController.popoverVisible==YES) {
[self.popoverController dismissPopoverAnimated:NO];
}
}
However, that is not being called directly by the framework as BViewController is inside a NavigationController. Hence, I register a UINavigationControllerDelegate with my NavigationController and implement the following two methods:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Pass the message on to the viewController in question
[viewController viewWillAppear:animated];
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Pass the message on to the viewController in question
[viewController viewWillDisappear:animated];
}
However, it seems that the passed in viewController parameter in both methods is the one that is about to be shown. I would have expected that the second method gives me access to the one that is about to disappear. So, when the user hits aforementioned button viewWillDisappear gets called on AViewController (which is about to be shown) and not on BViewController (which is about to disappear). Does that sound right? The apple documentation refers in both cases to
The view controller whose view and navigation item properties are being shown.
...which is not quite clear, I think. Thank you for some help, guys.
The two delegate method are both called for the same action (showing a view controller). The navigationController: willShowViewController:animated: is called before the new view controller is visible in the gui. The navigationController:navigationController didShowViewController:animated: is called after the new view controller is shown.
You will find this pattern in a lot of delegate protocols from apple. Unfortunately you do not have a delegate method in the NavigationViewController which tells you if the action was a pop or push.
I hook in my own protocol, which will know about the TO and FROM sides:
NavigationControllerDelegate.h:
#protocol NavigationControllerDelegate <NSObject>
#required
-(void) navigationController: (UINavigationController*) navController
willMoveFromViewController: (UIViewController*) from
toViewController: (UIViewController*) to;
#end
Instead of the regular UINavigationViewController, I then use a little helper class which keeps track of the view controllers:
NavigationHandler.h:
#interface NavigationHandler : NSObject <UINavigationControllerDelegate> {
NSMutableArray* m_viewControllers;
}
In my app delegate, I create one of these objects and set it as the delegate of the navigation controller:
...
m_navigationHandler = [[NavigationHandler alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController: mainMenuViewController];
navigationController.delegate = m_navigationHandler;
...
And from then on its a simple case of comparing my own list of view controllers with what the navigation controller has:
NavigationHandler.m
#import "NavigationHandler.h"
#import "NavigationControllerDelegate.h"
#implementation NavigationHandler
-(id) init {
if ((self = [super init])) {
m_viewControllers = [[NSMutableArray alloc] init];
}
return self;
}
-(void) dealloc {
[m_viewControllers release];
[super dealloc];
}
- (void)navigationController:(UINavigationController *)navController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated {
// Find out which viewControllers are disappearing and appearing
UIViewController* appearingViewController = nil;
UIViewController* disappearingViewController = nil;
if ([m_viewControllers count] < [navController.viewControllers count]) {
// pushing
if ([m_viewControllers count] > 0) {
disappearingViewController = [m_viewControllers lastObject];
}
appearingViewController = viewController;
[m_viewControllers addObject: viewController];
} else if ([m_viewControllers count] > [navController.viewControllers count]) {
// popping
disappearingViewController = [m_viewControllers lastObject];
appearingViewController = viewController;
[m_viewControllers removeLastObject];
} else {
return;
}
// Tell the view that will disappear
if (disappearingViewController != nil) {
if ([disappearingViewController conformsToProtocol: #protocol(NavigationControllerDelegate)]) {
if ([disappearingViewController respondsToSelector: #selector(navigationController:willMoveFromViewController:toViewController:)]) {
UIViewController<NavigationControllerDelegate>* vcDelegate = (UIViewController<NavigationControllerDelegate>*)disappearingViewController;
[vcDelegate navigationController: navController willMoveFromViewController: disappearingViewController toViewController: appearingViewController];
}
}
}
// Tell the view that will appear
if ([appearingViewController conformsToProtocol: #protocol(NavigationControllerDelegate)]) {
if ([appearingViewController respondsToSelector:#selector(navigationController:willMoveFromViewController:toViewController:)]) {
UIViewController<NavigationControllerDelegate>* vcDelegate = (UIViewController<NavigationControllerDelegate>*)appearingViewController;
[vcDelegate navigationController: navController willMoveFromViewController: disappearingViewController toViewController: appearingViewController];
}
}
}
#end