Using a Delegate with a TabBarController - iphone

I have a TabBarController which is set up with multiple ViewControllers at launch. When the user clicks a button I want to send them to a different ViewController in the TabBarController, and pass data through a delegate.
I have a protocol and delegate set up. However, when do you set the delegate since all the ViewControllers are in the TabBarController
Is this possible, how can I pass data to another ViewController in the TabBar when the user clicks a button. Any ideas, I'd really like to use a delegate.
- (IBAction)sendData:(id)sender
{
[self.delegate setStringData:strData];
self.tabBarController.selectedIndex = 0;
}
Edit:
So let's say I have a TabBarController with two ViewControllers called ViewControllerOne and ViewControllerTwo.
I have ViewControllerTwo set up as the delegate and protocol. This is the ViewController that will send data to ViewControllerOne after the button is pressed. ViewControllerOne implements protocol and contains the method setStringData which should be called after the button in ViewControllerTwo is pressed.

From a UIViewController you want to change the selected tab bar index and pass data.
I suggest you add a function in you app delegate for this.
That way your UIViewController won't be tied with a UITabBar (if tomorrow you want to use a different UI idiom, you will just have to change the implementation of your function in your app delegate).
To pass data, i you could try to introspection in your function : you take the current UIViewController of the new selected tab index, verify it responds to your selector and call the function.
Edit :
Let's assume your 'just' have to change the selected tabBar index (e.g. your UIViewController will always be the same on the new tab bar index).
In your first View Controller :
- (IBAction)sendData:(id)sender
{
UIApplicationDelegate * appDelegate = [[UIApplication sharedApplication] delegate];
if ([appDelegate respondToSelector:#selector(goToFirstTabBarWithData:)])
{
[appDelegate performSelector:#selector(goToFirstTabBarWithData:) withObject: strData];
}
}
In your Appdelegate :
- (void)goToFirstTabBarWithData:(NSString *)data
{
self.tabBarController.selectedIndex = 0;
UIViewController * vc = [self.tabBarController.viewControllers objectAtIndex:0];
if ([vc respondToSelector:#selector(setStringData:)])
{
[vc performSelector:#selector(setStringData:) withObject:data];
}
}
In your second View controller (the one you will arrive on) :
- (void)setStringData:(NSString *)data
{
// Do something...
}

I found a simpler solution to my problem. Inside of ViewControllerTwo, I just create an instance of ViewControllerOne and pass it that data I need. Then I change the tabBarController index to ViewControllerOne.
For example:
// A method inside of ViewControllerTwo
ViewController *viewcontrollerOne = [ViewcontrollerOne alloc] init];
[viewcontrollerOne setStringData:str];
[viewcontrollerOne release];
self.tabBarController.selectedIndex = 0;

Related

Check which viewController is running in IPhone application Programmatically in Appdelegate

Is their any Possibilty to Check which viewController is running in IPhone application Programmatically in Appdelegate
There is no easy answer to this. You need to walk the view controller hierarchy starting with the main window's root view controller. If you encounter a UINavigationController you need to look at the topViewController. Once you get to a UIViewController, you need to look at the modalViewController, if any. If you have any tab bar controllers then you need to look at the currently selected tab.
Things like UISplitViewController complicates things since this can show two view controllers at once.
Here is the start of a category method you could add to UIViewController. This only handles regular view controllers and navigation controllers.
- (UIViewController *)topMostController {
if (self.modalViewController) {
return [self.modalViewController topMostController];
} else {
if ([self isKindOfClass:[UINavigationController class]]) {
UINavigationController *nc = (UINavigationController *)self;
return [nc.topViewController topMostController];
} else {
return self;
}
}
}
Call this from your app delegate on the key window's rootViewController.
Assuming you've set the rootViewController property in your AppDelegate:
[UIApplication sharedApplication].keyWindow.rootViewController;
For view controller it is not possible to get the curent running viewcontroller name.
for that you write one following method in your app delegate file & then call getCurentViewController method in each viewcontroller view did load or view did appear if you are not allocating agin by passing self to it
-(void) getCurentViewController:(UIViewController*) vc
{
if([vc isMemberOfClass:NSClassFromString(#"vcName")])
{
//write your code here
}
else if([vc isMemberOfClass:NSClassFromString(#"vcName1")])
{
//write your code here
}
}
UIViewController *currentViewController = yourRootViewController;
while (currentViewController.presentedViewController) {
currentViewController = currentViewController.presentedViewController;
}
//currentViewController is now your top-most viewController
//I use this same snippet in my production code

Using more than 1 UINavigationController and only one UITabBarController

I am having a problem with managing more than one UINavigationControlller.
I have a UITabBar and in every tab I have a separate UINavigationController. So If I switch in UITabBar by
tabBar.selectedIndex = i; // where i is index of tabbar.
and when I go back to previous tab by
tabBar.selectedIndex = previousIndex;
I go to the root of the UINavigationController, not the viewcontroller which I left previously.
I want to go to the viewcontroller from which I jump to another tab, without calling -viewDidLoad: or -viewWillAppear:.
I need a method which show either UINavigationController or my viewController.
The following method when implemented in your AppDelegate.m file will be fired whenever u try to switch the tabs
- (BOOL)tabBarController:(UITabBarController *)tbc shouldSelectViewController:(UIViewController *)viewController {
}
specify in your appDelegate class as the delagate of your tabbarcontroller
ie.( TabBarController.delegate = self) in appDidFinishLaunching method

Pass managedObjectContext (Core data) to other classes, correctly done?

I used the default template provided by Apple with Core Data (managedObjectContext is in AppDelegate). At first I was including appdelegate.h in every classes I needed to use managedObjectContext, but I saw this was not the correct way to do it. Apple says it's better to only pass the context to other classes that need it and so on, so I ended up doing it that way. Thing is, it looks a bit "hackerish" the way I did it, and I'm wondering if there's a better option or my solution is correct.
My app is currently setup like that (here's a SS of my storyboard):
So my root window is a UITabBarController, and each tab is a UINavigationController that points to multiple UITableViewController/UIViewController.
Here is what I have in my Appdelegate to pass the managedObjectContext instance to 2 tabs:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UITabBarController *rootViewController;
UINavigationController *navigationController;
ItemsTableViewController *itemsTableViewController;
// Get the root window (UITabBarController)
rootViewController = (UITabBarController *)self.window.rootViewController;
// Get the second item of the UITabBarController
navigationController = [[rootViewController viewControllers] objectAtIndex:1];
// Get the first item of the UINavigationController (ItemsTableViewController)
itemsTableViewController = [[navigationController viewControllers] objectAtIndex:0];
itemsTableViewController.managedObjectContext = self.managedObjectContext;
// Get the third item of the UITabBarController (again ItemsTableViewController)
navigationController = [[rootViewController viewControllers] objectAtIndex:2];
// Get the first item of the UINavigationController (ItemsTableViewController)
itemsTableViewController = [[navigationController viewControllers] objectAtIndex:0];
itemsTableViewController.managedObjectContext = self.managedObjectContext;
return YES;
}
Everything works well, but having to call multiple times objectAtIndex to get to the right ViewController looks meh...
Anyone as a better solution?
Thanks!
You should look at using the prepareForSegue: method to pass your managedObjectContext to the other controllers.
Alternatively, you can subclass the tab bar controller and add the managed object context as a property, which you can then access from anywhere within your app provided the tab bar controller is also there.
Finally, if you are only ever going to use one context (i.e. no multi threads) you can always setup a CoreDataHelper class with a class method that returns your default context whenever you ask for it. To avoid importing the helper in every single class just add the helper to your precompiled header file (.pch) and let it also import the <CoreData/CoreData.h> framework.
If you want to see an example of how this is done, checkout MagicalRecord on github https://github.com/magicalpanda/MagicalRecord
[EDIT]
Here's an example of how you would pass the context using the prepareForSegue method. Remember that this method is called when a segue is about to initiate and it gives you the opportunity to setup the view controller that is about to be pushed. This is where you could pass delegate references and assign values to other variables in your destination view controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSString *segueIdentifier = [segue identifier];
if ([segueIdentifier isEqualToString:#"YourSegueIdentifier"]) // This can be defined via Interface Builder
{
MyCustomViewController *vc = [segue destinationViewController];
vc.managedObjectContext = self.managedObjectContext;
}
}

How to enumerate view controllers on iPhone?

I have a modal view controller which fetches a password. Since I don't want the password written to disk if my application is interrupted, I want to cear the password on applicationWillResignActive. (For those who may comment, I know the secure text field does not properly zeroize).
I've tried the following code, and my view controller is never located. For the first set of code (UIView) I believe its because I'm mixing and matching views and view controllers. I'm not sure why the second set of code (UIViewController) is not working since a few folks have suggested it.
How does one enumerate view controllers and locate a controller of interest? I simply want to send clearPassworAndPin to PasswordPromptController if present (since viewWillDisappear is not always sent when the view disappears).
Modified 7KV7 and Jhaliya answer is below (it worked). The 'if' statement using viewController.modalViewController was executed 5 times (once for each controller in the tab view). So the single modal controller of interest was sent the clearPasswords message 5 times.
for (UIViewController * viewController in viewsControllers)
{
if ([viewController isKindOfClass:passwordPromptClass])
{
[(PasswordPromptController *)viewController clearPassworAndPin];
}
else
{
if(viewController.modalViewController)
[self clearPasswords:[NSArray arrayWithObjects:viewController.modalViewController, nil]];
}
}
Using UIViews (no joy)
- (void)applicationWillResignActive:(UIApplication *)application
{
if(application.windows != nil)
[self clearPasswords:application.windows];
}
- (void)clearPasswords:(NSArray *)subviews
{
Class passwordPromptClass = [PasswordPromptController class];
for (UIView * subview in subviews)
{
if ([subview isKindOfClass:passwordPromptClass])
[(PasswordPromptController *)subview clearPassworAndPin];
}
}
Using UIViewController (no joy)
- (void)applicationWillResignActive:(UIApplication *)application
{
if(tabBarController.viewControllers != nil)
[self clearPasswords:tabBarController.viewControllers];
}
- (void)clearPasswords:(NSArray *)viewsControllers
{
Class passwordPromptClass = [PasswordPromptController class];
for (UIViewController * viewController in viewsControllers)
{
if ([viewController isKindOfClass:passwordPromptClass])
[(PasswordPromptController *)viewController clearPassworAndPin];
}
}
NSArray *array = [self.navigationController viewControllers];
yourViewController = [array objectAtIndex:yourChoiceOfIndex];
Hope it helps.
At the point where you present the PasswordPromptController as a modalViewController could you not store it as an instance variable? Then, in your applicationWillResignActive: callback you will have a handle to the VC to message against.
Be sure to release and nullify your reference to the PasswordPromptController reference when it gets dismissed.
you will have to go through the navigation stack to get the controllers.
Use UINavgationController below method to get all viewController in your navigation stack.
#property(nonatomic, copy) NSArray *viewControllers
hmm..., I have to say I like to give alternative suggestions/solutions as many folks have tried to answer you question specifically.
If you found it's not easy to find the PasswordPromptController by enumerating view controllers, you can just declare (alloc/init) that controller in your app delegate, whenever you need to use it in other controllers, get it through app delegate, do something like presenting as a modal view.
When you want to do something against it in your app delegate, e.g. clear the pwd, it's super easy because you have the reference to it.

ipad - dismissing a UIPopoverController

I have a button inside the content of a UIPopoverController. This button runs a method called myAction.
MyAction has the form
- (void) myAction:(id)sender
so, myAction receives the id of the caller button.
Now, inside this method I would like to dismiss the UIPopoverController, but the only thing I have is the ID of the caller button. Remember that the button is inside the UIPopoverController.
Is there a way to discover the ID of the UIPopoverController, given the button ID I already have?
thanks.
Unfortunately no. At least, not within the standard practices. You might be able to travel up the responder stack to find it, but it's a hack, it's buggy, and it's really, really messy.
If you want to dismiss a popover by pushing a button, some place relevant should keep a reference to the popover. Usually that would be the owner of the popover (not the controller showed within the popover). When the button is pressed, it can send a message to the owner controller, which can then dismiss the popover.
You might be tempted to have the controller displayed inside of the popover be the owner of its own popover, but coding this way is brittle, can get messy (again), and may result in retain loops so that neither ever gets released.
You can access the presenting popoverController by accessing "popoverController" with KVC.
[[self valueForKey:#"popoverController"] dismissPopoverAnimated:YES]
I have this working, and I do not think it is a hack. I have a standard split view iPad app. I then added a method on my detail controller (the owner of the pop over) to handle the dismissal.
On the standard split view architechture, both the root and detail view controllers are available via the app delegate. So I bound a button click inside the pop over to call a method which gets the app delegate. From there I call the method on the detail controller to dismiss the pop over.
This is the code for the method on the View Controller that is displayed inside the popover:
- (void) exitView: (id)sender {
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.detailViewController exitDrill];
}
Then the simple method to dismiss on the Detail View Controller:
- (void) exitDrill {
if(dtController != nil){
[dtController dismissPopoverAnimated: YES];
[dtController release];
}
}
I like the ability to do this because it give me a way to show a user how they can exit a pop over. This may not be necessary in future versions of the app; for right now, while this paradigm is still new to the platform, I prefer to let the users gexit a display in a couple fo different ways to make sure I minimize frustration.
As Ed Marty already wrote
If you want to dismiss a popover by pushing a button, some place relevant should keep a reference to the popover
This is very true; however, when showing a UIPopoverController, the class opening the popovercontroller keeps this resource already. So, what you could do is to use this class as the delegate class for your Popover Controller.
To do so, you could do the following, which I use in my code.
In the class opening the popover, this is my code:
- (void)showInformationForView:(Booking*)booking frame:(CGRect)rect
{
BookingDetailsViewController *bookingView = [[BookingDetailsViewController alloc] initWithStyle:UITableViewStyleGrouped booking:booking];
[bookingView setDelegate:self];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:bookingView];
self.popController = [[UIPopoverController alloc] initWithContentViewController:navController];
[self.popController setDelegate:self];
[self.popController setPopoverContentSize:CGSizeMake(320, 320)];
rect.size.width = 0;
[self.popController presentPopoverFromRect:rect inView:self.view permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
}
- (void)dismissPopoverAnimated:(BOOL)animated
{
[self.popController dismissPopoverAnimated:animated];
}
So what I am doing here is creating a UINavigationController and setting a BookingDetailsViewController as its rootViewController. Then I am also adding the current class as delegate to this BookingDetailsViewController.
The second thing I added is a dismissal method called dismissPopoverAnimated:animated.
In my BookingDetailsViewController.h I added the following code:
[...]
#property (nonatomic, strong) id delegate;
[...]
And in my BookingDetailsViewController.m I added this code:
[...]
#synthesize delegate = _delegate;
- (void)viewDidLoad
{
UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithTitle:#"Close" style:UIBarButtonItemStylePlain target:self action:#selector(closeView)];
[self.navigationItem setRightBarButtonItem:closeButton];
[super viewDidLoad];
}
- (void)closeView
{
if ([self.delegate respondsToSelector:#selector(dismissPopoverAnimated:)]) {
[self.delegate dismissPopoverAnimated:YES];
}
else {
NSLog(#"Cannot close the view, nu such dismiss method");
}
}
[...]
What happens is that when the "Close" button in the UINavigationController is pressed, the method closeView is called. This method check if the delegate responds to dismissPopoverAnimated:animated and if so, it calls it. If it does not respond to this method it will show a log message and do nothing more (so it wont crash).
I have written my code using ARC, hence there is no memory management.
I hope this helped you.