What's the difference between the back button on UINavigationController and PopTopViewController? - objective-c

I have a UINavigationController hierarchy, VC1 --> VC2.
VC1 has a table view that I need to reload when VC2 is done with its work, so VC1 has this code:
-(void)viewWillAppear:(BOOL)animated
{
[[self tableView] reloadData];
}
VC2 is essentially working with the server to create a new table row in VC1. When the done button in VC2 is pressed, I call [navController popViewControllerAnimated:YES]. So here's what happens from the user's perspective:
Visit VC2, use it to create a new row for the table in VC1. Press done.
The hierarchy successfully navigates back to VC1, but the tableview does not reload and display the new row.
However, if I then nav forward to VC2, and immediately hit the navController back button, the table does reload and show the new row.
So why does [tableview reload] work on 3 but not 2? Thanks so much.
==
More code in response to answer mentioned below:
In App delegate:
CWLandingVC *lvc = [[CWLandingVC alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:lvc];
[[self window] setRootViewController:navController];
In VC0:
-(void)toSessionMgmtViewController
{
TSessionMgmtViewController *tsmvc = [[TSessionMgmtViewController alloc] init];
[[self navigationController] pushViewController:tsmvc animated:YES];
}
In VC1:
- (IBAction)toCreateSessionView:(id)sender
{
TCreateSession *cs = [[TCreateSession alloc] init];
[[self navigationController] pushViewController:cs animated:YES];
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[self tableView] reloadData];
}
In VC2:
Finishes working with server...
UINavigationController *navControler = [self navigationController];
[navControler popViewControllerAnimated:YES];
Also, when VC2 is done working with the server, it updates a data store of TSessions called SessionListStore:
- (TSession *)addSession:(NSString *)code withName:(NSString *) name qs:(int)qs
{
TSession *s = [[TSession alloc] initWithName:name code:code numberQuestions:qs];
[_sessions setObject:s forKey:code];
return s;
}
where sessions is a NSNutatbleDictionary in SessionListStore.
Thanks so much in advance.

EDIT: The solution was to trigger the reloadData call from the completion block of Server call.
Please check this answer,
Popping ViewController doesn't call viewWillAppear when going back
Have you added navigation controller to your view controller or view controllers to your navigation controller?
Also, you can set the desired view controller as the delegate of your navigation controller and implement this method.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated

Related

Pop controller after back bar button is pressed

I have a UINavigationController ans a chain of 3 simple controllers. Each one has a button. When press a button a next controller is Pushed. ViewController1 -> ViewController2 -> ViewController3. When I push a back button on the 3rd view i want to move to the first view. Using of backBarButtonItem is obligatory. Here is the code for second controller:
#import "ViewController2.h"
static BOOL isBackButtonPressed;
#implementation ViewController2
- (void)viewDidLoad {
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:#"back from 3" style:UIBarButtonItemStyleBordered target:nil action:nil];
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
if (isBackButtonPressed) {
[self.navigationController popViewControllerAnimated:YES];
} else {
isBackButtonPressed = YES;
}
[super viewWillAppear:animated];
}
#end
But when I press back button on the third view I return to the second view instead of the first view. Could you help me to return to the first view pressing back button on the third view.
I tried suggestions from answers but they don't help.
Adding a selector to backBarButtonItem doesn't help because it is never called.
Adding a [self.navigationController popToRootViewControllerAnimated:YES] in viewWillDisappear methos also doesn't work. I don't know why. I think that the actual problem is how backBarButtonItem works.
Any other suggestions?
The behaviour I try to achieve exists in the calendar on iPhone. When you rotate iPhone to landscape you get to the weeek view. Then go to the event details, and rotate to the portrait. When you press back button you will get to a day view not to a week view, so a controller with weekview is skipped.
After countless number of tries my solution was simply not use backBarButtonItem! As whatever i do it always goes to previous viewController instead of calling its selector
Instead I use only leftBarButtonItem for navigation, as it guarantees calling my action.
Here an example
UIButton *backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 27, 22)];
[backButton setImage:[UIImage imageNamed:#"backbutton"] forState:UIControlStateNormal];
[backButton addTarget:self action:#selector(backButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
This certainly calls backButtonPressed action.. This works both for IOS 6 and 7
No need to register a new selector for the back button, just do:
-(void)viewWillDisappear{
if ( [self.navigationController.viewControllers containsObject:self] )
//It means that the view controller was popped (back button pressed or whatever)
//so we'll just pop one more view controller
[self.navigationController popViewControllerAnimated:NO];
}
in your ViewController3 viewWillDisappear method
Try using this in your third view controller, this way you check if you have pressed the back button and directly pop to the root view controller which as you stated is your first view controller.
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
[self.navigationController popToRootViewControllerAnimated:YES];
}
[super viewWillDisappear:animated];
}
I had the same problem as you beofre and fixed it like this:
You can capture the back button on the ViewController3 and before poping the view, remove ViewController2 from the navigation stack like this:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:#"back from 3" style:UIBarButtonItemStyleBordered target:self action:#selector(customBackPressed:)];
}
-(void)customBackPressed:(id)sender {
NSMutableArray *allViewControllers = [NSMutableArray arrayWithArray: navigationController.viewControllers];
for (UIViewController *vc in viewControllers)
{
// If vc is ViewController2 type, remove it
}
navigationController.viewControllers = allViewControllers;
[self.navigationController popViewControllerAnimated:YES];
}
Because ViewController2 is not in the stack anymore, it will jump to ViewController1.
Also, if ViewController1 is the root view controller, you can just do:
[self.navigationController popToRootViewControllerAnimated:YES];

close modalvew controller and parentview

I am trying to open a modalview from a view like that,
SignupViewController *signUpView = [[SignupViewController alloc] initWithNibName:#"SignupViewController" bundle:nil];
[signUpView setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
self.parentViewController.view.transform = CGAffineTransformMakeScale(1.3, 1.3);
self.parentViewController.view.alpha = 0;
[UIView animateWithDuration:.35 animations:^{self.parentViewController.view.alpha = 1.0; self.parentViewController.view.transform = CGAffineTransformMakeScale(1, 1);}];
[self presentModalViewController:signUpView animated:YES];
After login i am closing the modalview and redirecting to anther view, but the parentview is still there,
[self dismissViewControllerAnimated:YES completion:^{
ToolsViewController *gototoolpage = [[ToolsViewController alloc] initWithNibName:#"ToolsViewController" bundle:nil];
[self.navigationController pushViewController:gototoolpage animated:YES];
}
How to dismiss the parentview also. Any idea
You code looks a little confused. What do you intend by references to parentViewController? Check the docs - it is the containing viewController, not the previous or presenting viewController. In a NavigationController context this would be the UINavigationController. In a modal view context there is no parentViewController, but there is a presenting ViewController. I am not sure what you intend by all of those calls to self.parentViewController.
In any case you should really be sending the dismiss request back to your presenting viewController via a delegate so that it is completely clear where the pushViewController message is being passed from and to.
In the header file of your signUpViewController declare a protocol:
#protocol SignUpViewControllerDelegate
- (void) dissmissSignUpVC;
#end
then in your presentingViewController, after
SignupViewController *signUpView = [[SignupViewController alloc] initWithNibName:#"SignupViewController" bundle:nil];
add
[signUpView setDelegate:self];
and implement the delegate method with the same code you now have in your completion block:
- (void) dissmissSignUpVC {
ToolsViewController *gototoolpage = [[ToolsViewController alloc]
initWithNibName:#"ToolsViewController" bundle:nil];
[self.navigationController pushViewController:gototoolpage animated:YES];
}
In signUpView invoke the delegate's method to dismiss:
[[self delegate] dissmissSignUpVC];
Watch out for those stacked animations, I suspect that only the first will be performed (i.e. gototollpage animated:YES might as well be gototoolpage animated:NO)
Perhaps anyway you should reconsider your logic. I imagine the user might have a confusing experience if you do this under-the-hood manipulation of viewControllers. Better that there is a UI control for the user to navigate to toolsViewController so they understand where they are?

Need a really simple navigation controller with a table view inside a tab bar controller

I have an app with a tab bar controller (2 tabs). In one tab view controller, a button leads to an alert window. I want one button of the alert window to call a table view containing possible answers. I want that table view to have a done button and a title. I think that means a navigation controller has to be used. But most everything I can find on navigation controllers assumes a much more complicated situation. Here's part of the alert window logic:
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 2) {
AnswersViewController *aVC = [[AnswersViewController alloc] init];
[self presentViewController:aVC
animated:YES
completion:NULL];
}
}
And AnswersViewController looks like this:
#interface AnswersViewController : UITableViewController
#end
#implementation AnswersViewController
- (id) init
{
self = [super initWithStyle:UITableViewStylePlain];
return self;
}
- (id) initWithStyle:(UITableViewStyle)style
{
return [self init];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[self view] setBackgroundColor:[UIColor redColor]];
}
#end
This code all works as expected (an empty red UITableView appears).
Two questions I guess: 1. Is there a simple modification to what I have that can give me a done button and title in my table view? 2. If I have to go to a navigation controller (probably), how can I make a bare-bones navigation controller with a done button and title and embed the table view within it? Oh, and I want to do this programatically. And I think I prefer the done button and title to be in the navigation bar, no tool bar desired. Thanks!
To get what you are looking for, you do need to use a UINavigationController. That will provide the UINavigationBar where you can display a title and also buttons.
To implement this with a UINavigationController, you want to do smoothing like this (assuming you are using ARC, so you don't need to worry about memory management):
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 2) {
AnswersViewController *aVC = [[AnswersViewController alloc] init];
//Make our done button
//Target is this same class, tapping the button will call dismissAnswersViewController:
aVC.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(dismissAnswersViewController:)];
//Set the title of the view controller
aVC.title = #"Answers";
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:aVC];
[self presentViewController:aNavigationController
animated:YES
completion:NULL];
}
}
Then you would also implement - (void)dismissAnswersViewController:(id)sender in the same class as the UIAlertView delegate method (based on the implementation I have here).
Hope this helps!

NewView showing in PopOver instead of newView

I have the right view selected from my PopOver but instead of showing in Parent View it is showing in PopOverView.
Here are some screenshots:
After I select the Feedback option instead showing the view in my parentview which in my case is SecondViewController (grey background screen) it is showing in the PopOver itself.
For information I have Three VC's namely FirstViewController, SecondViewController and ThirdViewController each assigned to each TabBar item. I want to make SecondViewController as parent for the PopOver.
Here is my code where I create PopOver in my AppDelegate.m
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
if([viewController isKindOfClass:[SecondViewController class]]){
NSInteger index = [[self tabBarController] selectedIndex];
CGRect buttonFrame = [[[[[self tabBarController] tabBar] subviews] objectAtIndex:index+1] frame];
PopOverViewController *popoverContentController = [[PopOverViewController alloc]init];
UINavigationController *navcon = [[UINavigationController alloc]initWithRootViewController:popoverContentController];
popoverContentController.contentSizeForViewInPopover = CGSizeMake(250, 85);
popover = [[UIPopoverController alloc]initWithContentViewController:navcon];
NSLog(#"X:%f Y:%f",buttonFrame.origin.x,buttonFrame.origin.y);
[popover presentPopoverFromRect:buttonFrame inView:self.tabBarController.tabBar permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
}
}
And the code in my PopOverController.m where I am making a choice of which new view to show
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
sendFeedback *sendEmailViewController = [[sendFeedback alloc]initWithNibName:#"sendFeedback" bundle:nil];
downLoad *downloadFilelViewController = [[downLoad alloc]initWithNibName:#"downLoad" bundle:nil];
if (indexPath.row == 0)
[self.navigationController pushViewController:sendEmailViewController animated:YES];
else
[self.navigationController pushViewController:downloadFilelViewController animated:YES];
}
Can anyone let me know how I can make the reference to my parent view (SecondViewcontroller) so that the new view shows in my parent view rather than the popover view.
Thanks
You insert into popover UINavigationController and in didSelectRowAtIndexPath push newly created controller to existed stack (into popover UINavigationController), but not into tab bar.
In order to push newly created controller to second tab you need to assign second tab (now it is SecondViewController) to new UINavigationController and use exactly this navigation stack.
Code will look like this:
UINavigationController *navController = (UINavigationController*)[tabBarController.viewControllers objectAtIndex:1];
[navController pushViewController:downloadFilelViewController animated:YES];

Why can't I dismiss the previous view on a navigation stack?

This is probably going to be very obvious, but here goes. I have two ViewControllers, one that shows the details of a person with a [+] button that opens up another ViewController that manages the edits of the Person. When the person about to be deleted, an alert is presented and if OK is selected, the edit view should disappear and the DetailView should also disappear/transition back to the PersonsListView (not shown in code below).
I can get the EditView to dismiss, but I can't get the DetailView to dismiss as well.
Any help is appreciated.
The two ViewControllers:
#implementation PersonDetailViewController
...
- (void)editPerson
{
PersonDetailEditViewController *editView = [[PersonDetailEditViewController alloc] initWithTenant:self.person];
[editView setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:editView];
[self presentModalViewController:navController animated:YES];
}
...
#end
and
#implementation PersonDetailEditViewController
...
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) {
[Person deletePerson:self.person];
NSError *error;
[self.person.managedObjectContext save:&error];
[[self navigationController] dismissModalViewControllerAnimated:YES];
// *** HERE I ALSO WANT TO DISMISS THE DETAILVIEW ***
}
}
...
#end
Assuming that PersonDetailViewController is presented in a navigation controller you should be able to do what you want by adding the following line:
[self.navigationController popViewControllerAnimated:YES];