UINavigationController is nil after pop - ios

My project is complex (a bit too much probably) but the question is simple, here's the code
...
self.vc = [[ClassName alloc] init];
if ([[self.navigationController.viewControllers lastObject] isKindOfClass:[ClassName class]]) {
[self backAndGo:self.vc title:#"Title"];
}
else {
[self vai:self.vc title:#"title"];
}
...
in this code i've to go directly in the next view if the current view is different than ClassName, otherwise go back of 1 view and the go to the next.
- (void)backAndGo:(id)view title:(NSString *)title
- (void)backAndGo:(id)view title:(NSString *)title
{
NSLog(#"before %#,%d",[self.navigationController viewControllers],[[self.navigationController viewControllers] count]);
[self.navigationController popViewControllerAnimated:NO];
ALCParentViewController *viewController = (ALCParentViewController *)view;
[self.navigationController pushViewController:viewController animated:YES];
NSLog(#"after %#,%d",[self.navigationController viewControllers],[[self.navigationController viewControllers] count]);
}
ALCParentViewController is a parent class of vc, in this method the first log of navigatin controller is correct with all the stack of viewcontrollers, and the popViewController is executed but in the second log navigationController is null with 0 element obviously, and the pushViewController method is not executed, why? any ideas?
- (void)vai:(id)view title:(NSString *)title
- (void)vai:(id)view title:(NSString *)title
{
ALCParentViewController *viewController = (ALCParentViewController *)view;
[self.navigationController pushViewController:viewController animated:YES];
}
This is instead the other method to reach directly the second view, and it works correctly, another courius thing (almost for me) is that dispite the nil pushviewcontroller, navigation (push and pop) in other view works correctly...
EDIT 1:
As lucaslt89 said i've put a breakpoint in the second log and make "po self.navigationController" in the consolle, here the result
(lldb) po self.navigationController
$0 = 0x00000000 <nil>
(lldb)
it's nil, but i can see that in the log without breakpoint...
EDIT 2:
(lldb) po self.navigationController
$0 = 0x099787d0 <UINavigationController: 0x99787d0>
(lldb) po auxNavController
$1 = 0x099787d0 <UINavigationController: 0x99787d0>
(lldb)
after operations suggested by lucaslt89, the two address are the same

According to your last edit, one solution is to hold a reference to the navigation controller. Your backAndGo method should be like that
- (void)backAndGo:(id)view title:(NSString *)title
{
NSLog(#"before %#,%d",[self.navigationController viewControllers],[[self.navigationController viewControllers] count]);
UINavigationController *auxNavController = self.navigationController;
[self.navigationController popViewControllerAnimated:NO];
ALCParentViewController *viewController = (ALCParentViewController *)view;
[auxNavController pushViewController:viewController animated:YES];
NSLog(#"after %#,%d",[auxNavController viewControllers],[[auxNavController viewControllers] count]);
}
If you debug that, the address of self.navigationController prior to the pop should be the same as the auxNavController after the pop. Try that and tell us your results!

Ok, even when the question is closed, i'd like to make an explanation for future visitors. According to the documentation: ...This property is nil if the view controller is not embedded inside a navigation controller.
That's exactly what you do with the pop instruction. So, basically, before loosing a reference to the navigation controller because of the pop, make sure to store a reference to it.

On your backAndGo method:
First you pop a viewController, and then you push another ViewController.
The promblem is, after you pop a viewController, then the "self" is nil. So if you get the navigationController by using "self.navigationController" it will return nothing.
The reason is beacause "self" is nil, instead of the navigationController is empty. That is why the answer of lucastl89 is effective. He hold a navigationController object by a reference, not search it by using "self".

Related

Removing ViewController in Navigation Stack

In navigation stack , i am having 6 view controllers
like
A->B->C->D->E->F
At the view controller F, I want to go back to the view controller B, how can I do this? I want to remove the view controllers one by one. Thanks in Advance!
Use this:
for (UIViewController *controller in self.navigationController.viewControllers)
{
if ([controller isKindOfClass:[B class]])
{
[self.navigationController popToViewController:controller animated:YES];
break;
}
}
If you want to pop directly to B Class ViewController then try following.
for (UIViewController *VC in [self.navigationController viewControllers])
{
// here B is ViewCotroller Class Name
if ([VC isKindOfClass:[B class]])
{
[self.navigationController popToViewController:VC animated:TRUE];
break;
}
}
[self.navigationController viewControllers] returns the array of ViewControllers of current navigation stack. I used For (each) loop to find out our view controller (which is B ViewController) from array. If it is match than We will perform the Pop operation to that ViewController.
You can use responder chain to reach to your targetVC and pop the ones above it.
Thanks for the comments Rin and Kirandeep Kumar , i have tried like this ,it is working now
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
But i want to remove all the data in that view controller i.e objectAtindex:1
Please share how to do this
Thanks
Another answer is , for removing the viewcontroller we able to use notification also
[[NSNotificationCenter defaultCenter]postNotificationName:#"new" object:nil];
i have posted the notification in fVC and in BVC i registered and observed the notification and that method i removes the viewcontroller and clear that data
Try it:
[self.navigationController popToViewController:vcB animated:Yes];

Button not responding to selector

UPDATED FOR COMMENTS
I am trying to make this code work, but whenever I press the "nextButton", the program ends and I cannot figure out what went wrong. Can you please look at my code and figure out if anything is wrong.
- (void)viewDidLoad {
[super viewDidLoad];
Player1.placeholder = #"Player 1";
Player2.placeholder = #"Player 2";
Notes1.placeholder = #"Notes";
Notes2.placeholder = #"Notes";
[nextButton setAction:#selector(nextButtonPressed:)];
}
-(IBAction)nextButtonPressed:(id)sender {
if ([self.delegateP respondsToSelector: #selector(addPlayerViewController:didFinishEnteringPlayer1:didFinishEneteringPlayer2:)]) {
[self.delegateP addPlayerViewController:self didFinishEnteringPlayer1:Player1.text didFinishEneteringPlayer2:Player2.text];
NSLog(#"Works");
}
[self performSelector:#selector(nextButtonPressed:) withObject:nil afterDelay:0.25];
}
I also have another question to do with this code. To pass information through different view controllers using delegates, do you have to use a button or a bar button item?
The error that is displaying:
-[UIStoryboardPushSegueTemplate nextButtonPressed:]: unrecognized selector sent to instance
First you can´t put both lines:
[self presentModalViewController:gameDetails animated:YES];
[self.navigationController pushViewController: gameDetails animated:YES];
Second go to your viewController PlayersViewController and click in storyboard click on editor (top menú XCode) Embed In > Navigation Controller now you with your code change the next lines:
-(void) nextButtonPressed {
NSLog(#"Hi my friend");
[self performSegueWithIdentifier:#"YOUR_SEGUE_NAME_HERE" sender:#"hi"];}
And create new function:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"YOUR_SEGUE_NAME_HERE"])
{
// Get reference to the destination view controller
YourViewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setMyObjectHere:sender];
}}
The ViewController "YourViewController.h :
#interface YourViewController : UIViewController
#property (nonatomic, strong) NSString *MyObjectHere;
#end
Try setting a symbolic breakpoint on all Objective-C exceptions and see if that gives you your crash line numbers and error messages again.
The code you posted for setting up the target/action on your bar button item looks correct.
Edit: Disregard this part about changing the selector.
(I'm leaving it for continuity, but ignore it.)
One thing you might try is changing the selector to
:#selector(nextButtonPressed:) (Colon added)
And then adding the button as a parameter to your action method:
-(IBAction) nextButtonPressed: (UIButton *) sender
Action methods should work with or without the sender parameter, but
it's worth trying.
(Also you should add the IBAction keyword for clarity even if you're not hooking up the action in IB.)
EDIT: rmaddy pointed out that your code is trying to both modally present and push the same view controller. Don't do that. Do one thing or the other.
You should use either
[self presentModalViewController:gameDetails animated:YES];
(To present it as a modal)
or
[self.navigationController pushViewController: gameDetails animated:YES];
(To push it onto the navigation stack.
But not both. Doing both is almost certainly the cause of your crash.

Change between two view controllers programmatically at the first launch

I have two view controllers. First is empty, second contains a text field. If this field is empty, I need to move to the second controller automatically.
I tried this:
NSUInteger VCcount = self.navigationController.viewControllers.count;
UIViewController *btVC = self.navigationController.viewControllers[VCcount-2];
if([self.btViewController.Text.text isEqualToString:#""])
{
[self.navigationController pushViewController:btVC animated:YES];
}
and this:
UIViewController* btVC = [storyboard instantiateViewControllerWithIdentifier:#"BTViewController"];
But at the first launch program knows only current controller and doesn't know about thesecond.
How can I get there?
See the example below:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.textField.text = #"some text";
if (self.textField.text.length == 0) {
UIViewController *secondVC = [self.storyboard instantiateViewControllerWithIdentifier:#"secondVC"];
[self.navigationController pushViewController:secondVC animated:NO];
}
}
If you comment out the assignment of "some text" to the textField, the secondVC will be instantiated and presented. I don't know what exactly you are trying to achieve with first two lines of your presented code.
If the secondVC already exist, you can just take a reference to it by knowing its index on the stack, or inspecting viewControllers array of your navigationController before you push it to the stack. If it does not exist, you just instantiate it from storyboard (with the correct identifier) and push it.

Changing content of UINavigationController's Stack result in crash

I have a UINavigationController with 4 items:
(root)mainvc -> callerlistvc -> addcallerformvc -> verifycallervc (in that specific order)
When I am on the verifycallervc screen, if I press back, I want to go back to callerlistvc.
Here is the catch however, the back button should be a system button.. So.. as far as I know I cannot replace the action with a selector calling poptoviewcontroller:animated (only works on a custom uibarbuttonitem)
So then I thought of manipulating the stack (pretty interesting and challenging too!) So here is what I did...
So currently Im on the verifycallervc screen... and this gets called.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSMutableArray *allViewControllers = [self.navigationController.viewControllers mutableCopy];
__block UIViewController *mainvc = nil;
__block UIViewController *callerlistvc = nil;
__block UIViewController *addcallerformvc = nil;
[allViewControllers enumerateObjectsUsingBlock:^(UIViewController *vc, NSUInteger idx, BOOL *stop) {
if ([vc isKindOfClass:[MainVC class]]) {
mainvc = vc;
} else if ([vc isKindOfClass:[CallerListVC class]]) {
callerlistvc = vc;
} else if ([vc isKindOfClass:[AddCallerFormVC class]]) {
addcallerformvc = vc;
}
}];
[self.navigationController setViewControllers:#[ mainvc, callerlistvc, self]];
}
After I did that, I pressed back normally and was now on the callerlistvc... great.
Unfortunately when I press the button (push-segued to addcallerformvc)... it results in a crash EXC_BAD_ACCESS.
I also tried a different approach by first manipulating the variable callerlistvc like so before adding it in the setViewControllers method
callerlistvc = [[UIStoryboard storyboardWithName:#"main" bundle:nil] instantiateViewControllerWithIdentifier:#"CallerListVC"];
But the result is the same.
I have added breakpoints and it goes like this...
CallerListVC:
tappedShowAddCallerListButton
performSegueWithIdentifier
prepareForSegue // identifier string is correct, destinationVC is not nil
then AddCallerFormVC:
4. viewDidLoad
5. viewWillAppear // properties not nil
after that EXC_BAD_ACCESS occurs
How can I make this work?
The better approach in this case would be to use a custom UINavigationController class and extend the popViewControllerAnimated: method. In this method either call the super method or pop to the specific view controller (super class method) based on a check. That way you have your system nav buttons and also control where the tack should pop to.
Don't override anything in verifycallervc and instead do following with normal back on verifycallervc
Override viewWillAppear or viewDidAppear for addcallerformvc like this
- (void)viewDidAppear:(BOOL)animated
{
if (![self isBeingPresented]) {
[self.navigationController popViewControllerAnimated:YES];
}
}
Reference :
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/RespondingtoDisplay-Notifications/RespondingtoDisplay-Notifications.html#//apple_ref/doc/uid/TP40007457-CH12-SW7
Note: not tested, don't have XCode right now....

popToViewController is not working "Tried to pop to a view controller that doesn't exist"

I am trying to use popToViewController and it I keep getting the error "Tried to pop to a view controller that doesn't exist"?
I am in a Settings view and when the user clicks "Sign Out" I dismiss the Settings VC and segue back to the mainView where an unwind segue method is called. In the unwind segue method I call the following.
-(IBAction)endSettingsViaLogout:(UIStoryboardSegue *)segue {
//[self performSegueWithIdentifier:#"mainToLoginSegue" sender:self];
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
//[self.navigationController popViewControllerAnimated:YES];
DLog(#"User finished with search");
}
When the poptoVC is called I get the "Tried to pop to a view controller that doesn't exist".
I NSLog the self.navigationController.viewControllers and I can see the stack of VC's and the one I want to pop to is in there?
/// UPDATE //////
Ok here is what I have found. If my segue to Settings is a regular "push" segue then the code works and I get popped back to where I want. If I do a custom segue, having come from the left side of the screen then it stops working. Even with the custom segue the self.navigationcontroller.viewcontrollers shows its in the stack. So why can't I pop back to it? Or how can I pop back to it with the custom segue?
Do I get the view controller hierarchy right?
On your basis UINavigationViewController you have set a main view controller as root view controller.
Then a settings view controller has been pushed upon this.
Via "Sign out" the settings view controller is being segued back to the main view controller.
If so, you are actually trying pop "back" to a view controller, that does not exist, since you already have reached the root view controller of your navigation controller stack. In this case, all previously initialized controllers have been popped from the stack and you would have to reinitialize and push the desired view controller.
If I am missing some important point, it would be helpful, if you would describe your actual view controller stack at the moment the "Sign out" option is available. Furthermore, what exactly is printed on the console if you log the self.navigationController.viewControllers array?
well that basically tells objectatindex 1 does not exist:
things to try:
objectatindex:0
or
nslog(#"%d",[[self.navigationController.viewControllers]count]);//add it before the popline and see if it works
if its the topview controller then try below:
[navigationController topViewController] instead
NSArray *viewControllers = [[self navigationController] viewControllers];
for( int i=0;i<[viewControllers count];i++)
{
id obj=[viewControllers objectAtIndex:[viewControllers count]-i-1];
if([obj isKindOfClass:[OrderCheckOutViewController class]]){
[[self navigationController] popToViewController:obj animated:YES];
return;
}
}
you can use the snippet to pop out to targetVC's next viewController in the navigationController's stack.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
BOOL findIt = NO;
UIViewController *targetVC = nil;
for (UIViewController *subVC in self.navigationController.viewControllers) {
if (findIt) {
break;
}
if (subVC == xxx) {
findIt = YES;
}else{
targetVC = subVC;
}
}
[self.navigationController popToViewController:targetVC animated:NO];
});

Resources