I have been developing iphone applications for around 3months now and theres a few things that stump me and i don't really have an idea how to work round them.
I have a navigation controller controlling the views in my application however every screen that is loaded, used then pushed back loses all the information as it seems to be reinstantiated... I believe this is a possible memory management issue?
But how to i create an app that navigates and retains all information in its views until the application is closed.
Thanks :)
Possible you didn't keep a reference to the view controller, the issue is for UIVIewController not to be released.
Make the view controller an ivar you will instanciate only one time when you push it on stack.
// in .h
MyViewController *mVC;
// in .m
// maybe when the user selects a row in a tableview
if(mVC == nil) {
// first time use, alloc/init
mVC = [[MyViewController ....];
}
// then push on the stack
[self.navigationController ....];
Of course don't forget to release it later.
In this part:
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
You will probably have something like this... Instead, make your myViewController a class's property so you have a reference to it. And drop the [myViewController release]; statement.
Possibly your app is receiving a didReceiveMemoryWarning.
In such cases, when the super class is called, the framework does memory cleaning by unloading all the views that are not currently displayed. This could explain the behavior you are seeing.
To check it further, override didReceiveMemoryWarning in one of your view controllers or applicationDidReceiveMemoryWarning in your app delegate, and put a breakpoint in it. Don't forget to call [super...] appropriately, otherwise pretty soon your app will be killed. What you should see in this way is that the views do not disappear before hitting the breakpoint, and do disappear after that.
If the hypothesis is correct, you should find a way to save the state of your view in viewDidUnload and restore it in viewDidLoad. Look also at didReceiveMemoryWarning reference.
Try to save data in NSUserDefaults it its small or use plist or it its too small like 5-10 objects save in in some variable in appDelegate, and if its too large use sqlite and for saving something like images of files like xml use Document directory
The UINavigationController works like a stack: you push and pop UIViewControllers on it. That means when a UIViewController get popped, it will have its retain count decremented by 1, and if no other object holds a reference to it, it will be deallocated. You can avoid the UIViewControllers getting dealloced by keeping a reference to them yourself by calling -retain on the objects, for instance in your appDelegate.
You can use NSUserDefaults to save the states of the UIControls in the view.
So whenever u r loading a view, set the values to the controls so that it looks like it resume from the place where we left.
I will like my application that always starts as the first time I open it. I have several view controllers and when I exit my application and I open it again I see the view controller where I left of. Maybe I have to call the method applicationWillTerminate method.
I use this code to open a new view:
UIViewController *control = [[SomeViewController alloc] initWithNibName:#"SomeViewController"
bundle:nil];
UINavigationController *navControl = [[UINavigationController alloc]
initWithRootViewController:control];
[self presentModalViewController:navControl animated:NO];
[navControl setNavigationBarHidden:YES];
[control release];
[navControl release];
this code works great when linking it to buttons. But when I place that code in the applicationDidBecomeActive method it does not work.
The easiest way is to set UIApplicationExitsOnSuspend in Info.plist.
That really isn't the expected behaviour, though. Users expect to see the app "where they left off", especially if they've only briefly left the app (e.g. because they got a phone call).
Your code snippet adds a view controller, but is unlikely to work since your app delegate is not a UIViewController. It also doesn't do anything about removing the old view controllers.
EDIT: If all you need to do is display a splash screen (or something), then it's something like this:
In -applicationDidEnterBackground:, add a "splash screen" view (not a view controller) to self.window. (iOS takes a "screenshot" after you return from -applicationDidEnterBackground: and uses this in the app-switch animation; you want this to be what the user sees when switching back to your app)
In -applicationWillEnterForeground:, do whatever animations you want and eventually remove the view from the window (call -removeFromSuperview).
EDIT 2: The same will work in -applicationWillResignActive:/-applicationWillBecomeActive:, except this happens on a sleep/wake event, which might not be what you want...
I'd avoid using view controllers for this, because trying to shoehorn a view controller in the view controller hierarchy is likely to be problematic (for example, you have to figure out which VC to present it from, and you have to do the "right thing" if the user backgrounds your app while the VC is on screen, and...)
The reason it doesn't work in applicationDidBecomeActive is that method is only sent to the Application delegate, which doesn't know about presentModalViewController.
I suggest instead that in your appDelegate, implement applicationWillEnterForeground:, which should restore the state to a newly launched application (equivalent to what the state is at the end of application:didFinishLaunchingWithOptions: ).
OR...(edits)
If you just want a certain viewController to run (which is still loaded, right?)...For example, if you have a tab controller and just want to go to the root of the first view controller, put the following code into applicationWillEnterForeground:
UITabBarController * myTabBar = self.tabBarController;
myTabBar.selectedIndex = 0;
[[myTabBar.viewControllers objectAtIndex:0] popToRootViewControllerAnimated:NO];
Temporary solution:
I made my application crash on applicationWillResignActive method and it works. My application needs to run an animation when I launch it. But this works because next time the application runs it starts like the first time I opened it.
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.
I load view controllers all the time in the following format:
-(void)loadSelectUser {
MyViewController *nextController = [[MyViewController alloc] initWithStyle:UITableViewStyleGrouped];
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
[delegate.navigationController pushViewController:nextController animated:YES];
[nextController release];
}
And I've never had an issue with that. But right now I'm dealing with the issue that the next view doesn't load completely. The navigation bar shows up and the viewDidLoad and the numberOfSectionsInTableView methods are both called. That is it. The table doesn't show up, it still shows the previous view.
I imagine this means there is a memory leak or something not connected properly. Is this the right path to be looking? If so, what is your best suggestion for debugging this issue. My code has no error messages so I'm not sure where to start. I load the view properly in a different controller, but for some reason it doesn't do it after this particular view*.
*This view happens to do a lot data manipulation with downloading objects, saving them and such. But again, it looks like it is all working properly. What would mess up the navigation controller loading the next view completely?
Oh, and just to mess things up more, some times, it works properly. But I run it one more time and it doesn't do it again.
Update: TechZen comment about the proper way to push a new view controller seemed to help a little. There is a higher rate of it working, unless I am pushing a tableviewcontroller. Depending on the action my view will push a UITableViewController or a UIViewController with a nib file. The second usually (not always) works.
Also, in a different view I am adding a modal view. But when I try to dismiss it using [self dismissModalViewControllerAnimated:YES]; it doesn't always work. Again it's hit or miss on it. Anyone have an idea of what would be causing the transition of windows to be finicky?
Calling the app delegate to get the navigation controller is unnecessary and risky. Any view controller on a navigation controller stack has a populated navigationController property so you can just use self.navigationController.
It's risky to call the app delegate's navigation controller because you have no guarantee that you will get the same navigation controller as the one that currently holds the view controller calling the push. You could in theory end up with two overlapping and conflict navigation controllers.
Switch the code to self.navigationController and see if that fixes the problem.
i am using UIviewcontroller subclasses. In my main view i have 3 buttons, each button will load a different nib. and each new nib is having one back button to come back to main view.
when i click one the back button of any view to move to the main view the dealloc of that view is not getting called? i didnt understood this.
can anyone explain when those views dealloc will be called?
if the dealloc method hasn't been called, it means that your retained your viewController object by hands. for example, in this case dealloc will not be called after clicking back button to return
MyViewController *controller = [[MyViewController alloc] init];
[self.navigationController pushViewController:controller animated:YES];
You should add
[controller release];
to this code to be sure that your instance of viewController will be deallocated. If you are absolutely sure, that you had sent equal number of alloc(or any message that increases object's retainCount) and release messages for your object and dealloc method doesn't be called anyway, it will be more complex. I hope that this answer will help. If you will find that your situation is "more complex", post a comment, then I'll try to explain with more details.
I too would like to dive deeper into understanding memory management details (below surface level) where it comes to controllers being pushed on and off of the stack. I built my framework from the text, "Beginning iPhone 3 Development" by Mark and LaMarche, but that text effectively re-uses sub-controllers and their dealloc methods never get called.
I have noticed that repeated use of a sub-controller with a NIB containing a UIWebView that calls Google's web directions url ... eventually results in a memory warning and my data is lost. This involves repeated "reuse" of the sub-controller.
If you can point me as well to in depth text that goes into nav controller and sub view memory management, that would be excellent.