Cannot access UITabBarController from modal ViewController - ios

I have a UITabBarController-based application in which my login page is presented modally by the default tab's VC(UITabBarController index 0), and is dismissed modally by dismissViewControllerAnimated:.
From my settings page I have a logout button, and if the user immediately logs back in I have to call [self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil]; and then reset the UITabBarController's selectedIndex property in order to get rid of the settings page and also go back to the starting tabBar tab. So I do this:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString* responseString = [[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding];
/*Succesful Login*/
if([responseString isEqualToString:#"success"])
{
UITabBarController *tabBarController = (UITabBarController *)self.presentingViewController;
if (tabBarController){
NSLog(#"I have a tab bar~");
[self.tabBarController setSelectedIndex:0];
}
else{
NSLog(#"I don't have a tab bar~");
}
//dismisses from a second, immediate, re-login attempt
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
//dismisses from first login attempt
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(#"Succesful login.");
}
In this case, the method setSeletedIndex: does not work because this methodology essentially creates a new instance of the tabBarController, when it is the original that is actually displayed. I CAN NOT seem to access the already existing UITabBarController which was before my modally presented login page.
EDIT:
I have tried using this conditional to check its existence:
if (self.tabBarController){
NSLog(#"I have a tab bar~");
//[self.tabBarController setSelectedIndex:0];
}
else{
NSLog(#"I don't have a tab bar~");
}
where I have even changed self.tabBarController to self.presentingViewController.tabBarController and get nil, and self.presentingViewController.presentingViewController.tabBarController and get nil, how am I to access the previously existing UITabBarController?
Objective: Access the application's UITabBarController that is present before the presentation of the login page. (Access the Appdelegate's UITabBarController)

Your use case is a natural fit for delegation.
In the header file for your Login, define a delegate. E.g.,
#class Login
#protocol LoginDelegate <NSObject>
#required
- (void) userDoneWithLoginController:(Login *) controller;
#end
#interface Login: UIViewController
#property (weak, nonatomic) id<LoginDelegate> viewDelegate;
#end
Then your presenting view controller segue can set itself as the delegate to the login view controller by setting this "viewDelegate" object inside the prepareForSegue for the presenting view controller before control passes to the Login page. [Let me know if you want this clarified.]
In addition the presenting view controller will implement the required method--which can simply dismiss the login page when called
-(void) userDoneWithLoginController:(Login *) controller
{
[controller dismissViewControllerAnimated:YES completion:nil];
}
Once you've done this, the login page simply calls its delegate method when it is done, thusly:
[self.viewDelegate userDoneWithLoginController:self];
Viola!
This is preferred way to dismiss a modal view in iOS. The calling controller should be the controller dismissing the view. Try to avoid having view controllers dismiss themselves. Instead, have their calling/presenting controllers (delegates) dismiss them.

The following should work.
// check that the vc presenting your current modal vc is a tab bar controller
if ([self.presentingViewController isKindOfClass:[UITabBarController class]]) {
// get the pointer of type UITabBarController
UITabBarController *tabBarController = (UITabBarController *)self.presentingViewController;
// set to the desired tab
tabBarController.selectedIndex = 2;
}
// dismiss the current modal vc
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];

I believe I found my solution.
I was able to access the app's UITabBarController from the Appdelegate like so:
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[[appDelegate myTabBar] setSelectedIndex:0];

Related

Create a Logout Button

NB: First App;
I am currently trying to create a log out button for an app that I have created. Essentially, on load, the user is presented with LoginViewController.xib with 2 text fields and a button, given that the text fields meet the arguments, when the button is pushed, the following argument is executed:
if (success) {
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate.window setRootViewController:appDelegate.tabBarController];
}
This works fine, and the user is taken into the app with a tab controller that switches between 3 xib's (Home, Settings, Table).
In the settings tab, I have a button "LogOut" which when pressed, I would like the user to be returned to the "LoginViewController.xib" but I can't seem to find any way of doing this from tutorials on youtube or on the web.
Please see below for the Settings coding;
SettingsViewController.h:
#import <UIKit/UIKit.h>
#interface SettingsViewController : UIViewController
- (IBAction)LogOutClick:(id)sender;
#end
SettingsViewController.m:
#import "SettingsViewController.h"
#interface SettingsViewController ()
#end
#implementation SettingsViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)LogOutClick:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
Calling dismissViewControllerAnimated: is not going to help as your tab bar controller/settings view controller is not presented by any view controllers. The original login controller, as far as I can tell from the code you have supplied, no longer exists in memory and, as a result, you can not "return" to it.
You have two options: one is to present the login view controller on the tab bar controller, and the other is to change the window's root view controller to the login controller. For example
//Present over tab bar
- (IBAction)LogOutClick:(id)sender {
LoginViewController *loginController = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
[self presentViewController:loginController animated:YES completion:nil];
}
//Switch root view controller
- (IBAction)LogOutClick:(id)sender {
LoginViewController *loginController = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
[appDelegate.window setRootViewController:loginController];
}
In my opinion, the first method is the better way of doing things. After the login controller has been dismissed, you can then populate the tab bar controller's views with the proper data. However, as the login controller already has a method that switches the root view controller of the window, it may be easier for you to just switch the root view controller back to the login controller.

UITabBar disappears after pushed to new view controller

I have an UITabBarController that has 3 buttons. The second button points to ViewController1 which is connected to another view called ViewController2. After I tap a button in ViewController2 I programmatically present ViewController1 again, that works perfect except one thing. After I "arrived" to ViewController1 the tab bar disappears.
I'm using this method to navigate back to ViewController1. (exactly I navigate to its navigation controller, but already tried with the view)
- (void)presentViewControllerAnimated:(BOOL)animated {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"storyboard" bundle:nil];
UINavigationController *firstViewNavigationController = [storyboard instantiateViewControllerWithIdentifier:#"destination"];
[self presentViewController:firstViewNavigationController animated:animated completion:nil];
}
I call here the first method
- (void)didTapButton:(id)sender {
UIButton *button = (UIButton *)sender;
CGPoint pointInSuperview = [button.superview convertPoint:button.center toView:self.tableView];
[self presentViewControllerAnimated:YES];
}
This method hides the tab bar in the ViewController2, I already tried without it, therefore there is no problem with it.
-(BOOL)hidesBottomBarWhenPushed
{
return YES;
}
I can't figure out why this thing happens, I think it's a fair solution, that worked well for a several times when I needed to present views. I've read it can happen with segues, but I'm doing it with code without segues.
Actually your code works right. There should not be tab bar when you present FirstViewController from SecondViewController. Because when you call instantiateViewControllerWithIdentifier its basically creates a new instance of that view controller, and of course, there is no tab bar.
The right way to go back to your first view controller is to pop SecondViewController (or dismiss it, if it presented modally). So your final code should be like this
- (void)didTapButton:(id)sender {
// If this view controller (i.e. SecondViewController) was pushed, like in your case, then
[self.navigationController popViewControllerAnimated:YES];
// If this view controller was presented modally, then
// [self dismissViewControllerAnimated:YES completion:nil];
}
And of course, your view controller hierarchy in storyboard must be like this:
-- UINavigationController -> FirstViewController -> SecondViewController
|
->UITabBarController____|
-...
-...
I've tried the same and got the same result.
My solution was simple, on the push do this :
UINavigationController *firstViewNavigationController = [storyboard instantiateViewControllerWithIdentifier:#"destination"];
firstViewNavigationController.hidesBottomBarWhenPushed = true; // Insert this and set it to what you want to do
[self presentViewController:firstViewNavigationController animated:animated completion:nil];
and then remove your
-(BOOL)hidesBottomBarWhenPushed
{
return YES;
}

Custom Delegate Callback

I'm new to iOS programming and I'm facing a problem
I'm having a problem with custom delegate.
I'm trying to make a simple custom where it return data to the previous view controller and pop the current view controller.
I have 2 navigation view controller
1 - main view controller
2 - Adding
and here is the protocol that is written in the adding view controller
#protocol AddingDelegate <NSObject>
#required
-(void)setInformation:(Adding *)controller withObject:(Conference *)info;
and here is the where I called it in adding view controller
-(IBAction)addingConference
{
NSLog(#"Adding Button Pressed");
conferenceObject = [[Conference alloc]init];
conferenceObject.name = [NameTX text];
conferenceObject.city = [CityTX text];
conferenceObject.description = [Dectription text];
NSMutableArray *info = [[NSMutableArray alloc] init];
[info addObject:conferenceObject];
[self.delegate setInformation:self withObject:conferenceObject];
NSLog(#"adding Conference method is done");
}
I wrote the delegate at the interface in the main view controller
#interface MainViewController : UITableViewController <AddingDelegate>
#end
and here where I declared the delegate method
-(void)setInformation:(Adding *)controller withArray:(NSMutableArray *)info
{
NSLog(#"in the main view at the delegate");
[self.navigationController popToRootViewControllerAnimated:YES];
NSLog(#"Should be popped right now");
}
and this is the prepare for segue method
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"AddObject"]) {
UINavigationController *navigation = segue.destinationViewController;
Adding *addingViewController = [[navigation viewControllers]objectAtIndex:0];
addingViewController.delegate = self;
}
}
now the problem is when I push the adding on top of the stack and then fill the information and press done the adding view controller doesn't pop to show main view controller.
I tried to log everything and the logs from the main view controller doesn't show .
Please help me
What I notice here is that in the implementation of prepareForSegue:sender: the segue's destinationViewController is a navigation controller. This makes me think that your segue is not pushing the AddingController on the current navigation stack but it's presenting a new one instead. This means the new navigation controller containing the AddingController is presented modally and as such, when you try to pop the navigation stack nothing seems to happen because you're operating on the wrong navigation stack. If that is the case you have two options: 1. change [self.navigationController popToRootViewControllerAnimated:YES]; for [self dismissViewControllerAnimated:YES completion:nil]; or 2. change the segue to be a push segue instead of a modal segue and point the segue directly to the AddingController.
In Adding.m
#class Adding;
#protocol AddingDelegate
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller;
#end
#interface Adding : UIViewController
#property (weak, nonatomic) id <AddingDelegate> delegate; // have you forgot this one
#end
and use
[self dismissViewControllerAnimated:YES completion:nil];
You need dismiss if you want get back to previous screen and make sure you have added Navigation controller

iOS Delegate Does Not Push or Present a View

I have a HomeView and a HomeDropDownView.
HomeDropDownView is shown as a drop-down view over the HomeView.
HomeView is a delegate of HomeDropDownView.
When I do an action in HomeDropDownView I want to call a delegate method in HomeView and have that delegate method present a third view controller, TestViewController from it's navigation controller.
If I try to launch TestViewController from anywhere in the class it works fine - except from the delegate method.
There are animations in HomeDropDownView but putting the call to the delegate method in the complition does not make the view controller appear. And in the case that I'm using this the animation's don't fire anyway; there's only a resizing without animation.
TestViewController's init does get called as well as the viewDidLoad but not the viewWillAppear and the view dose not appear.
Code:
HomeDropDownView
- (void)finalAction {
...
[self callDelegateAction];
...
- (void)calldelegateAction {
if ([self.delegate respondsToSelector:#selector(launchTestView)] ) {
[self.delegate launchTestView];
} else {
DLog(#"Error out to the user.");
}
}
HomeView
- (void)launchTestView {
//[self listSubviewsOfView:self.parentViewController.view];
NSLog(#"delegate method | self: %#", self);
TestViewController *tvc = [[TestViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
//[self.navigationController presentViewController:tvc animated:YES completion:nil];
//[self.view.window.rootViewController presentViewController:tvc animated:YES completion:nil];
//[self.navigationController pushViewController:tvc animated:YES];
AppDelegate *appdelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appdelegate.tabBarController.navigationController presentViewController:tvc animated:YES completion:^() {
NSLog(#"Done!");
}];
}
None of the above approaches work. But if I put the exact same code into the viewDidAppear or put it in a button action method, it will work fine. At the time of calling the delegate method's self is HomeView and all the subviews, including the nav controller do seem to be there. This is in a tabcontroller-based project but I think that any of the above are acceptable ways to call the nav controller still.
What am I missing? Why does my delegate method not want to push/present a viewcontroller on HomeView's Nav controller? It's probably something I'm missing but I can't find a reason in the Apple Docs or any other thread.
Thanks for the help!
Sadly this turned out to be that HomeView was being changed underneath the execution of the message. So by the time the HomeView got the message call it was no longer the same HomeView object that had requested action in the first place. So it was not the same delegate.
This was done so that it would appear to the user that the same view was being used for different things.
But this is a good example of why you should not destroy and re-create critical views. We should have been using the same view and reloading the objects instead if we knew that we would be sending messages. Or had some notion of a control structure.

Back to RootViewController from Modal View Controller

From Home view - my RootViewController - I open up 2 ViewControllers one after another as user progresses in navigation hierarchy like so:
1) SecondViewController is pushed by button connected in my Storyboard
2) ThirdViewController is presented modally
[self performSegueWithIdentifier:#"NextViewController" sender:nil];
So, the picture is: RootViewController -> SecondViewController -> ThirdViewController
Now in my ThirdViewController I want to have a button to go back 2 times to my RootViewController, i.e. go home. But this does not work:
[self.navigationController popToRootViewControllerAnimated:YES];
Only this guy goes back once to SecondViewController
[self.navigationController popViewControllerAnimated:YES];
How can I remove both modal and pushed view controllers at the same time?
I had a similar situation, where I had a number of view controllers pushed onto the navigation controller stack, and then the last view was presented modally. On the modal screen, I have a Cancel button that goes back to the root view controller.
In the modal view controller, I have an action that is triggered when the Cancel button is tapped:
- (IBAction)cancel:(id)sender
{
[self.delegate modalViewControllerDidCancel];
}
In the header of this modal view controller, I declare a protocol:
#protocol ModalViewControllerDelegate
- (void)modalViewControllerDidCancel;
#end
And then the last view controller in the navigation stack (the one that presented the modal view) should implement the ModalViewControllerDelegate protocol:
- (void)modalViewControllerDidCancel
{
[self dismissViewControllerAnimated:NO completion:nil];
[self.navigationController popToRootViewControllerAnimated:YES];
}
This method above is the important part. It gets the presenting view controller to dismiss the modal view, and then it pops back to the root view controller. Note that I pass NO to dismissViewControllerAnimated: and YES to popToRootViewControllerAnimated: to get a smoother animation from modal view to root view.
I had the same requirement but was using custom segues between the view controllers. I came across with the concept of "Unwind Segue" which I think came with iOS6. If you are targeting iOS6 and above these links might help:
What are Unwind segues for and how do you use them?
http://chrisrisner.com/Unwinding-with-iOS-and-Storyboards
Thanks.
Assuming your AppDelegate is called AppDelegate, then you can do the following which will reset the rootviewcontroller for the app window as the view RootViewController
AppDelegate *appDel = (AppDelegate*)[[UIApplication sharedApplication] delegate];
RootViewController *rootView = [[RootViewController alloc] init];
[appDel.window setRootViewController:rootView];

Resources