Close one modal ViewController and open another in same method. Possible? - iphone

I have a situation where I want to open one modal controller from the other. But when the user closes either, they should go back to the parent controller of both modal controllers.
So I have the parent controller in charge of this. The method on the parent does something similar to the code below when the user clicks a button on the first modal controller.
[self.navigationController dismissModalViewController:YES];
SecondModalViewController *c = [[SecondModalViewController alloc] init];
[self.navigationController presentModalViewController:c animated:YES];
[c release];
The only thing that happens is the first view closes, but I don't see the second view open. Is it possible to close a modal ViewController and open a second one in the same method? If so, how?

i think that by the time you close the first view controller, the [self.navigation presentmodal..] dosen't have a self to open the new one from.
you could do that:
use a boolean variable to state whether the next time this controller appears (when you close the one you're currently trying to open), it should close, and implements that feature in the viewDidApper: method, like so:
#interface FirstViewController : UIViewController
{
//...
BOOL close;
}
and in the .m file,
-(void)viewDidAppear:(BOOL)animated{
if(close)
[self dismissModalViewControllerAnimated:NO];
else
[super viewDidAppear:animated];
}
now, to open your new controller, do that:
-(IBAction)openSecondController:(id)sender{
//..
SecondViewController* controller = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
close = YES;
[self presentModalViewController:controller animated:NO];
}
now, when you close the second one, the first one will close also. doesn't have any visible side affects in my apps..
not the cleanest way, but i didn't find a better one.
cheers

I do that same thing all the time without a problem, though mine have animated:NO... I'm guessing your issue is because you have animated:YES on both. What effect are you looking for exactly? Do you want to see one get animated away, and then the other get animated in? If so, you need to execute the presentModalViewController with a delay.
Otherwise, you should just be able to present the second modal view controller without closing the first one at all. When you call dismissModalViewController; it should dismiss both.

I'm finding exactly the same response. It looks like standard behaviour.
The way I got around it is to use performSelector:withObject:afterDelay:
[self performSelector:#selector(presentController:) withObject:navController afterDelay:0.5f];
The only drawback was I had to set a presentController function which then did the presentModalViewController:animated:
I tried to use a NSInvocation but I kept on getting a bad access error when I tried to pass a BOOL for the animated argument.
The main thing is it works and my client is happy.

You can use handle uiviewcontroller closed and open another with using Unwind Segues.
https://developer.apple.com/library/content/technotes/tn2298/_index.html

Related

When trying to view table view after viewing another, app crashes

i have a tough one for you today. I have two tableViews in my app the first is on the first page. There are two other pages the user drills down to get to the second table view. After i leave the first table view, i can press the back buttons to get back perfectly until i reach the second table view. As soon as i drill down to the second table view and then try to return to the first via pressing the back buttons. As soon as i get to the last back button to return to the first table view, the app crashes. The code for the back buttons is simply:
- (IBAction)goBack:(id)sender {
[self dismissModalViewControllerAnimated:YES];
}
Any Help Would be greatly appreciated!! Thanks everyone!! :D
Whenever I create a modal view controller from a XIB, the automatic #property generator duplicates things in the Dealloc method, thus throwing an EXC_BAD_ACCESS when the view is dismissed. Make sure you aren't releasing something twice.
Sounds like your are releasing something too early. Open you app in instruments (command + i) and run a zombie test.
As soon as you see zombie has been messaged expand the right panel and have a look at the user code (your code) blocks. Indicated by the back person icon.
Double click that and it will indicate what it was trying to access that had already been released.
Are your tableviews being displayed in a modal window? If not, why are you calling [self dismissModalViewControllerAnimated:YES]? Shouldn't you be calling [[self navigationController] popViewControllerAnimated:YES]?
If you're using a UINavigationController, the back button functionality should be provided automatically.
if you are using [[self navigationController] popViewControllerAnimated:YES] to
then for back you write as mentioned below:
(IBAction)goBack:(id)sender {
// Tell the controller to go back
[self.navigationController popViewControllerAnimated:YES];
}
if you are using [self presentmodalviewcontroller: animated:]
then only [self dismissModalViewControllerAnimated:YES] will work
you try this [[self navigationController] popViewControllerAnimated:YES]

Presenting a ModalViewController inside a ModalViewController

I have a view which is presented as a modal view controller which takes username and password credentials. I want this view to check the delegate, and if the user hasn't previously set an unlock pin for the app, to then show the change pin view as a modal view controller. This is my code...
+(void)presentCredentialsViewController:(UIViewController *)vc{
CredentialsViewController *cvc = [[CredentialsViewController alloc] init];
[vc presentModalViewController:cvc animated:FALSE];
}
and then in CredentialsViewController
-(void)viewDidLoad{
[super viewDidLoad];
if([appDelegate.pin isEqualToString: #""]){
UserPrefsViewController *upvc = [[UserPrefsViewController alloc] init];
upvc.cancelButton.hidden = true;
[self presentModalViewController:upvc animated:FALSE];
}
}
But for some reason it doesn't work. The debugger steps through the code without error, never the less, the second modal view controller isn't displayed.
First, I would suggest checking that your appDelegate.pin is blank and not nil. If it is nil, the if statement would not be satisfied and your second ModalView would not be presented.
You may also want to try the previous suggestion, calling presentModalViewController from viewDidAppear, or setting a delay if leaving it in viewDidLoad. It is possible that the CredentialsViewController is trying to present the second view when it has not yet presented itself.
The if statement is being hit and the second PresentModalViewController is executing without error, but it just wasn't displaying. I did try putting the code in ViewDidAppear and a load of other places as well, such as applicationWillBecomeActive etc. Although not actually crashing the code, still none of these approaches would show the view controller. In the end I have opted for this:
start with pin of #""
on applicationDidEnterBackground check if pin has been set
if yes
PresentModalViewController: PinViewController
if no
do nothing
Bit of a hack but it will do for now. I suppose I should put some sort of notification in somewhere warning that the pin hasn't been set. The suggestion about the delay may possibly work I suppose. I might give it a go in the future. Thanks guys....points up!

iPhone UINavigation Issue - nested push animation can result in corrupted navigation bar

I keep getting the following errors:
2011-04-02 14:55:23.350 AppName[42430:207] nested push animation can result in corrupted navigation bar
2011-04-02 14:55:23.352 AppName[42430:207] nested push animation can result in corrupted navigation bar
2011-04-02 14:55:23.729 AppName[42430:207] Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
2011-04-02 14:55:23.729 AppName[42430:207] Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
Here is what I am doing. From a view controller, I call the following when a certain button is pushed:
EventsViewController *viewController = [[EventsViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
navController.navigationBar.tintColor = [UIColor blackColor];
[self presentModalViewController:navController animated:YES];
[viewController release];
[navController release];
Then, if a certain button is pushed in EventsController, I call:
SingleEventViewController *viewController = [[SingleEventViewController alloc] initWithEvent:[currentEvents objectAtIndex:indexPath.row]];
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
Then, if a certain button is pushed in SingleEventViewController, I call:
EventMapView* viewController = [[EventMapView alloc] initWithCoordinates];
[[self navigationController] pushViewController:viewController animated:YES];
[viewController release];
So yea, it's obvious that there's nested push animations, but isn't this the right way to go about it? I checked out Apple's DrillDownSave code and this appears to be how they're doing it. Does it matter that I use init methods instead of viewDidLoad methods?
Calling pushViewController before viewDidAppear is unsafe.
ACCIDENTLY TRIGGERING THE SAME SEGUE TWICE
Once in code, and once from interface builder, but both at the same time...
I was getting the same error as the rest of you. Only my problem was I was accidentally firing the same segue, twice. Once from interface builder, and once from within my code.
I have a UITableView. When a cell is selected, a segue in interface builder fires. Heres my problem, I had the segue set up to be directly fired off clicking the CELL ITSELf, inside interface builder, then in my code, I had under didSelectRowAtIndexPath, code that would fire that same segue... like so...
[self performSegueWithIdentifier:#"MySegue" sender:tableView];
That means when didSelectRowAtIndexPath gets called because a row was selected, it fires the segue with the above line of code. Then interface builder, also triggers the segue, because its connected directly to the cell object in interface builder. To stop interface builder from directly firing the segue. You have to connect the segue from the top of the view controller, not nested down inside coming off of the cell itself.
So if you are having this problem for the same reason as me, that is, you are calling the same segue twice, you can fix this by unlinking the connection from the CELL DIRECTLY, to your segue, and having the segue connection originate at the top of the table hierarchy in IB, rather than nested inside the cell. Connect the segue from you View Controller itself, to the segue. If you have done this correct, when you select the segue, it should highlight the ENTIRE view it is coming from, not just the cell.
Now Apples documentation states thus under the performSegueWithIdentifier:sender: reference:
Apps normally do not need to trigger segues directly. Instead, you configure an object in Interface Builder associated with the view controller, such as a control embedded in its view hierarchy, to trigger the segue. However, you can call this method to trigger a segue programmatically, perhaps in response to some action that cannot be specified in the storyboard resource file. For example, you might call it from a custom action handler used to process shake or accelerometer events.
In my case, I have a search button for my UITableView, and whether the segue is called when the search results table is present, or the normal table view is present, had to be determined. So I needed to trigger the segue directly.
So remove the embedded control from interface builder, and just stick it on the view controller itself, then trigger the segue in your code!
Now, no more double segues! And no more errors.
I had the same problem / error message as you did just now, was looking for a solution and ended up at this thread, however, for me I found that the solution is actually having only one animated:YES when doing a nested push (I put animated:YES only for the final push), hope this helps
cheers.
I've figured it out. Apparently if you call -pushViewController from outside of the -didSelectRowAtIndexPath method of a UITableViewDelegate, it doesn't work. Moving the call into that function worked. Weird.
I happened upon this same problem that resulted from a button in a nib being connected to two different actions. It tried loading both view controllers, thereby corrupting the stack.
What do you mean when you say you use init methods instead of viewDidLoad methods?
If you're pushing a new view controller before the old push has bad a chance to be actioned, you will get this sort of error. So putting certain code into init and doing things prematurely could certainly get you the error being reported.
At the point where init is being run on a view controller, the view hasn't been loaded yet!
Um I had this issue, and Im new to the whole iOS dev scene. But after looking at my connections inspector (with file's owner) in the interface builder i saw that as I had copied a button it had the previous buttons method assigned to it as well as the new method I had created. I guess that was where the nested aspect of my problem came from, as it was executing 2 different methods both of which pushed a view onto the Nav Controller. I know this has already been answered but I figured I would put this up just in case anyone else had a silly mistake like mine.
This has already been answered, but I thought this might help others as I got the same error but without using table views. I finally figured out the problem.
I had an existing button whose IBAction invoked a pushViewController. I had created a new button by copying the existing button. The new button also had an action that invoked pushViewController. When the new button was tapped (touch up inside) and the view controller was pushed, I got this error. I deleted the new button, created it from scratch, bound it to the existing outlets and actions, and the error went away.
Ran into the same problem. In my case I was missing a break in the switch statement so two segues were fired at the same time. Easy fix for me.
My problem had to do with the keyboard being active.
This was caused for me by pushing a ViewController from a textField's delegate method:
-(void)textFieldDidBeginEditing:(UITextField *)textField{
FilterLocationViewController *destViewController = (FilterLocationViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"FilterLocationViewController"];
[self.navigationController pushViewController:destViewController animated:YES];
}
By changing the code to this:
-(void)textFieldDidBeginEditing:(UITextField *)textField{
[_textFieldLocation resignFirstResponder]; //adding this line
FilterLocationViewController *destViewController = (FilterLocationViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"FilterLocationViewController"];
[self.navigationController pushViewController:destViewController animated:YES];
}
(adding the line [textField resignFirstResponder];) the problem went away.
Basically the lesson is that you shouldn't modify the navigationController stack if the keyboard is out.
Recently, I've faced the same problem. The reason was: -I was trying to pop view controller twice by mistake. you can check this crash by setting breakpoints on push and pop View controllers
1) Perhaps you could try passing the necessary variables as properties before pushing the UIViewController rather than using the init methods with parameters. Most likely you will need these parameters beyond your init method anyway.
Also, in your initWithCoordinates: method you are missing the parameters. Possibly your custom init methods are a part of the problem.
2) Just because you mentioned viewDidLoad -- this method is for initialization after a view has loaded . If you create the UIViewController in code, as it seems you do, you should use loadView to set up your subviews.
This was happening for me because of my UIControlEvents
[button addTarget:self action:#selector(callSecondView) forControlEvents:UIControlEventAllTouchEvents];
I had to change the UIControlEventAllTouchEvents to UIControlEventTouchUpInside or however you want your button to work if you had the issue because of a UIButton call.
My Solution was
[self performSelector:#selector(moveTo) withObject:nil
afterDelay:0.5];
Don't know about other's. I think most of the People using StoryBoard is facing such Problem. I am using XIB.
In my case The Problem Was, when I was moving to another view using push,
I was also using
[self.navigationController popViewControllerAnimated:YES];
in the ViewWillDisappear of the current View at the same time. Just remove it and it works fine.
I was using POP, because of the requirement and the Flow.
The Hierarchy was 1 -> 2 ->3
I was on view 2 and wanted to move to view 3. In that case I encountered this error.
In my case I was both setting the push segue from the storyboard and programatically. Hopefully that'll help anyone
I had this error message too, and the navigation bar and navigation controller transitions were weird. My setup was a bunch of Navigation Controllers embedded in a Tab bar Controller. The problem was that I didn't call super.viewDidLoad() in my Tab bar Controller implementation of viewDidLoad.
Calling super is something the docs clearly point out that you should do when overriding viewDidLoad, and I learned this the hard way.
Maybe this can help someone else too!
I know that this was answered, but it could help others.
I had the same problem, but it was caused because I was using a bad event for an info button.
I was using "UIControlEventAllTouchEvents" and this generated two push of the same view into the navigation controller. The correct event was "UIControlEventTouchUpInside". I'm new to iOS.
This resolves the problem:
https://github.com/nexuspod/SafeTransition
If you push (or pop) a view controller with animation(animated:YES) it doesn't complete right away, and bad things happen if you do another push or pop before the animation completes.
To reproduce this bug, try pushing or popping two view controllers at the same time. Example:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIViewController *vc = [[UIViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
You will receive this error:
2014-07-03 11:54:25.051 Demo[2840:60b] nested push animation can
result in corrupted navigation bar 2014-07-03 11:54:25.406
Demo[2840:60b] Finishing up a navigation transition in an unexpected
state. Navigation Bar subview tree might get corrupted.
Just add the code files into your project and makes your navigation controller as a subclass of APBaseNavigationController, and you'll be good to do.
Just to complete the list, here is another reason which can cause "nested push animation can result in corrupted navigation bar":
I did setup several NavigationController within a TabBarController and set
the selectedIndex within the storyboard Identifiy Properties. After moving active Tab to Code error disappeared.

Help Menu iPhone

I have a design question/technical question about my iPhone app.
I have a pretty simple (read really really simple) single view application. And it does everything that I need it to do. However I find myself in need of a help view. And I really don't quite know what to do!
I have a simple helpButton() method in my main view controller, and I really just want to display a scrollview with a bunch of images that show what to do during the use of my app. However, should I make a new viewcontroller class? How do I call it from my method?
Really I was thinking of an unfortunately simple method, just putting a scrollview behind everything and hiding it. Then showing it when the IBAction is called. Horrible...
Sorry if this is elementary, I haven't needed to do anything more yet!
You can push a modalViewController. To do that just make a new viewController with the scrollview and associated data in it, then
MyViewController *myViewController = [[MyViewController alloc] init];
[self presentModalViewController:myViewController animated:YES];
Create an IBAction in your new viewController and a hooked up button to that action to dismiss the modalView (something like this:
IBAction done {
[self dismissModalViewControllerAnimated:YES];
}
A couple options:
1) Create a new UIView object, either programmatically, or even in your existing XIB file. Use the [self.view addSubview:view] method to display it.
2) Create a new UIViewController with its own XIB file. Use [self presentModalViewController:anaimated:] to display it.
Either way, you'll need to add something to the new view to dismiss it when you're done.

Is parentViewController always a Navigation controller?

I was kind of scratching my head at this a week ago, and now with a little bit more Cocoa experience under my belt I feel like I have an inkling as to what might be going on.
I'm making an application that is driven by a UINavigationController. In the AppDelegate, I create an instance of this class, using "page 1" as the Root View Controller.
UINavigationController *aNavigationController = [[UINavigationController alloc]
initWithRootViewController:page1ViewController];
Now here's where I'm having the problem. From "page 1" I'd like to use a modal view controller that slides over the interface and then disappears once the user has made an edit. I do that using code like this, inside of Page1ViewController:
[self presentModalViewController:myModalViewController animated:YES];
When the Modal View Controller is gone, I want a value on "Page 1" to change based on what the user entered in the Modal View Controller. So, I wrote some code like this, which resides in the Modal View Controller:
[self.parentViewController dismissModalViewControllerAnimated:YES];
[self.parentViewController doSomethingPleaseWithSomeData:someData];
The update to page 1 wasn't happening, and it took me a long time to realize that the "doSomethingPleaseWithSomeData" message was not being sent to Page1ViewController, but the Navigation Controller.
Is this always to be expected when using Navigation Controllers? Did I perhaps configure something improperly? Is there an easy way to get at the View Controller that I want (in this case, Page1ViewController).
I would recommend using the delegation pattern to solve your problem. Create a property
#property (nonatomic, assign) id <MyModalViewDelegate> delegate;
And a corresponding protocol
#protocol MyModalViewDelegate
#optional
- (void)myModalViewControllerDidFinish:(MyModalViewController *)aModalViewController;
#end
When the user finishes with your view (e.g. taps the save button), send this message:
if ([self.delegate respondsToSelector:#selector(myModalViewControllerDidFinish:)])
[self.delegate myModalViewControllerDidFinish:self];
Now, set the delegate to the view controller that should manage the whole thing, and it will be notified when the view controller is finished. Note that you'll need your view controller to dismiss the modal view controller. But, logically, that makes sense, since it was the object that presented the modal view controller in the first place.
This is how Apple solves this problem in, for example, the UIImagePickerController and UIPersonPickerController.
There are a couple of ways you can handle this. The simplest is probably just to add a UIViewController property into myModalViewController and set it to page1Controller before you present it:
myModalViewController.logicalParent = self; //page1Controller
[self presentModalViewController:myModalViewController animated:YES];
Just make sure you add the appropriate instance variable #property, and #synthesize for logicalParent to myModalViewController, then you will have a way to communicate data back to the ViewController that triggered the modal dialog. This is also for passing data back and forth between different levels of navigation before you push and pop them on the stack.
The one important thing to worry about when doing this is that it is easy to get retain loops if you are not careful. Depending on exactly how you structure this you might need to use assign properties.
I just ran into this same problem. It definitely seems that if you put a UIViewController embedded in a NavigationController, then when, from that UIViewController you present another UIViewController modally, the presentee thinks that the presenter is the NavigationController. In other words, parentViewController is incorrect.
I bet this is a bug: either that, or the documentation seems incomplete. I will inquire.
Just ran into the same problem. I believe this is a bug. My scenario is the following:
A navigation hierarchy with A, B and C view controllers in this order. On C there's a button that would open a modal view controller called D. Once D is presented the navigation controller drops C from its hierarchy which is a terrible behavior. Once D gets dismissed, the navigation controller instantiates a new C type view controller and pushes it into its hierarchy to recover the original one. Terrible. My solution is hacking the navigation hierarchy this way (a very bad solution but works well. with a 2 dimension array you could implement stacking modals):
- (void)presentModalViewController:(UIViewController *)c {
[self.navigationHierarchy removeAllObjects];
[self.navigationHierarchy addObjectsFromArray:[navigation viewControllers]];
[navigation setViewControllers:[NSArray array] animated:YES];
[navigation presentModalViewController:c animated:YES];
}
- (void)dismissModalViewController {
[navigation dismissModalViewControllerAnimated:YES];
[navigation setViewControllers:[NSArray arrayWithArray:self.navigationHierarchy] animated:YES];
}
These two methods are defined where I maintain the main navigation hiererchy: the app delegate. navigation and navigationhierarchy are defined this way:
NSMutableArray *navigationHierarchy;
UINavigationController *navigation;