I'm displaying UIControllerView subclass when a button is pressed from a another UIViewController like this:
- (IBAction)openNextLevelViewController
{
NSLog(#"openNextlevelViewController");
[self.navigationController pushViewController:nextLevelViewController animated:YES];
}
And the app will return from that view on a button push which trigger this method:
-(IBAction) returnToStart {
NSLog(#"returnToStart method called");
[self.navigationController popViewControllerAnimated:YES];
}
The problem is that the displayed view not getting destroyed/deallocated on the pop. As a result, when it gets pushed, it's not executing the viewDidLoad, which initiates some variables. This may be causing a related problem where, the second time through, when the user presses the return button, the "pop" no longer causes a return to the previous controller.
What's the best way to deal with this? I could move the initialization code to the "willAppear" method, but it seems as if that could be called almost randomly.
Well, its not getting released because nextLevelViewController is still being retained somewhere else. Most likely in your nextLevelViewController variable.
- (IBAction)openNextLevelViewController
{
NSLog(#"openNextlevelViewController");
// assuming you have nib already set up
UIViewController *nextLevelViewController = [[NextLevelViewController alloc] init];
// RETAIN COUNT = 1
// navigationController retains your controller
[self.navigationController pushViewController:nextLevelViewController animated:YES];
// RETAIN COUNT = 2
// Don't need it any more let navigation controller handle it.
[nextLevelViewController release]
// RETAIN COUNT = 1 (by NavigationController)
}
Further On
-(IBAction) returnToStart {
[self.navigationController popViewControllerAnimated:YES];
// RETAIN COUNT = 0, dealloc will be called on your viewController, make sure to release all your retained objects.
}
Now when your controller gets popped, it SHOULD get released (shouldn't have been retained anywhere else). And next time you call openNExtLevelViewController, it'll be initializing a new instance of your viewController anyway.
I'm a fan of releasing viewController when it is no longer needed (displayed), instead of holding it in memory. Let navigationController and TabBarController handle viewControllers whenever possible.
Related
I'm developing an application which will work based on maps. So once user opens MapViewController then I will load some data every 5 seconds.
I'm using navigation controller(Push view controller).
So every time when user goes to MapViewController viewdidload method calling. I don't want like that.
That's why I'm trying to avoid viewdidload method like tabbarcontroller.
Is there any way to achieve this?
viewDidLoad is getting called because your MapViewController is getting deallocated when you pop it off of the top of your navigation controller. When you recreate the view controller, it's getting allocated all over again, and the view loads again. If you keep a reference to MapViewController in the class containing your navigation controller, then ARC will not deallocate the object, and you can use this reference to push it back onto the stack so viewDidLoad will not get called again.
Edit: Adding code for reference:
MapViewContoller *mapViewController; // declared globally so there's a strong reference.
- (void) openMapViewController {
if (!mapViewController) {
mapViewController = [self.storyboard instantiateViewControllerWithIdentifier: MapViewControllerID];
}
[self.navigationController pushViewController: mapViewController, animated: YES];
}
Try this
-(void)clickForPush{
// declarre viewcontroller e.g 'saveRecipeVC' instance globally in interface
if (!saveRecipeVC) {
saveRecipeVC = [self.storyboard instantiateViewControllerWithIdentifier:SaveRecipeVCID];
}
[self.navigationController pushViewController:saveRecipeVC animated:YES];
}
viewDidLoad is intended to use when,not possible or efficient to configure 100% of an interface in a XIB. Sometimes, a particular property you wish to set on a view isn't available in a XIB. Sometimes, you use auto layout, and you realize that the editor for that is actually worse than writing auto layout code. Sometimes, you need to modify an image before you set it as the background of a button.
If you dont want to do these things make your viewDidLoad empty. Than avoiding. Or
Add code conditionaly into your viewDidLoad.
(void)viewDidLoad {
[super viewDidLoad];
if(condition) {
// put your code here
}
}
I have a root view that loads two view controller. e.g.:FirstVC,SecondVC.
I am showing FirstVC as the root view controller when the app launches, on some action on FirstVC I load SecondVC by removing first.
For loading SecondVC I first remove FirstVC by
[FirstVCobj.view removeFromSuperView];
[FirstVCobj release];
FirstVCobj = nil;
After that I allocate and create SecondVC
Now only after calling SecondVC's viewdidload() is FirstVC's dealloc() method called.
Is this the right execution path, or is it due to some mistake I have made?
The above is exactly how I remove and create my view controllers.
i assume it is a UIView you're talking about.
addSubview retains the view
removeFromSuperView releases or AUTORELEASES it -- an implementation detail you don't control
to 'see' it: wrap it in a pool of your own
#autoreleasepool {
[FirstVCobj.view removeFromSuperView];
[FirstVCobj release];
FirstVCobj = nil;
}
[FirstVCobj removeFromParentAndCleanup:YES];
Check with this might work.
I have 2 ViewControllers
ViewControllerWithCollectionView (FIRST) and ModalViewControllerToEditCellContent (SECOND)
I segue from FIRST to SECOND modally. Edit cell. Return.
After dismissing SECOND controller, edited cell doesn't get updated until i call
[collection reloadData]; somewhere manually.
Tried to put it in viewWillAppear:animated:, when i check log, it's not called (after dismissing SECOND)
I've tried various solutions, but i can't brake thru (maybe I'm just too exhausted). I sense that I'm missing something basic.
EDIT dismiss button
- (IBAction)modalViewControllerDismiss
{
self.sticker.text = self.text.text; //using textFields text
self.sticker.title = self.titleText.text;// title
//tried this also
CBSStickerViewController *pvc = (CBSStickerViewController *)self.stickerViewController;
//tried passing reference of **FIRST** controller
[pvc.cv reloadData];//called reloadData
//nothing
[self dismissViewControllerAnimated:YES completion:^{}];
}
It's tough to tell from the posted code what's wrong with the pointer to the first view controller that you passed to the second. You should also be able to refer in the second view controller to self.presentingViewController. Either way, the prettier design is to find a way for the first view controller to learn that a change has been made and update it's own views.
There are a couple approaches, but I'll suggest the delegate pattern here. The second view controller can be setup to have the first view controller do work for it, namely reload a table view. Here's how it looks in almost-code:
// SecondVc.h
#protocol SecondVcDelegate;
#interface SecondVC : UIViewController
#property(weak, nonatomic) id<SecondVcDelegate>delegate; // this will be an instance of the first vc
// other properties
#end
#protocol SecondVcDelegate <NSObject>
- (void)secondVcDidChangeTheSticker:(SecondVc *)vc;
#end
Now the second vc uses this to ask the first vc to do work for it, but the second vc remains pretty dumb about the details of the first vc's implementation. We don't refer to the first vc's UITableView here, or any of it's views, and we don't tell any tables to reload.
// SecondVc.m
- (IBAction)modalViewControllerDismiss {
self.sticker.text = self.text.text; //using textFields text
self.sticker.title = self.titleText.text;// title
[self.delegate secondVcDidChangeTheSticker:self];
[self dismissViewControllerAnimated:YES completion:^{}];
}
All that must be done now is for the first vc to do what it must to be a delegate:
// FirstVc.h
#import "SecondVc.h"
#interface FirstVc :UIViewController <SecondVcDelegate> // declare itself a delegate
// etc.
// FirstVc.m
// wherever you decide to present the second vc
- (void)presentSecondVc {
SecondVc *secondVc = // however you do this now, maybe get it from storyboard?
vc.delegate = self; // that's the back pointer you were trying to achieve
[self presentViewController:secondVc animated:YES completion:nil];
}
Finally, the punch line. Implement the delegate method. Here you do the work that second vc wants by reloading the table view
- (void) secondVcDidChangeTheSticker:(SecondVc *)vc {
[self.tableView reloadData]; // i think you might call this "cv", which isn't a terrific name if it's a table view
}
In an application where several UIViewControllers work together,
firstViewController added to root. Till here its fine now I want to go to secondViewController I dont want to use UINavigationController or UITabBarController. I have already read the View Controller Programming Guide but it does not specify without using UINavigationController, UITabBarController and story board.
and when user want to move from secondViewController to firstViewController how secondViewController will be destroyed?
Apple Doc also does not specify how UIViewController is release or destroyed? It only tell the life cycle inside UIViewController.
If you are concerned about how UIViewController is release or destroyed then here is a scenario for you:-
Here is a button tap method in FirstViewController that presents SecondViewController (using pushViewController,presentModalViewController etc)
In FirstViewController.m file
- (IBAction)btnTapped {
SecondViewController * secondView = [[SecondViewController alloc]initWithNibName:#"SecondViewController" bundle:nil];
NSLog(#"Before Present Retain Count:%d",[secondView retainCount]);
[self presentModalViewController:secondView animated:YES];
NSLog(#"After Present Retain Count:%d",[secondView retainCount]);
[secondView release]; //not releasing here is memory leak(Use build and analyze)
}
Now In SecondViewController.m file
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"View Load Retain Count %d",[self retainCount]);
}
- (void)dealloc {
[super dealloc];
NSLog(#"View Dealloc Retain Count %d",[self retainCount]);
}
After Running the code:
Before Push Retain Count:1
View Load Retain Count 3
After Push Retain Count:4
View Dealloc Retain Count 1
If you are allocating and initializing a ViewController, You are the owner of its lifecycle and you have to release it after push or modalPresent.
In the above output at the time of alloc init retain count of SecondViewController is One,,,,surprisingly but logically its retain count remains One even after it has been deallocated (see dealloc method), So require a release in FirstViewController to completely destroy it.
Other way to present a new view controller is doing it like a modal view controller(notice that self is firstViewController):
[self presentModalViewController:secondViewController animated:YES];
then, when you wan to come back to the firstViewController and destroy the secondViewController, you have to dismiss the view controller(from the secondViewController):
[self dismissModalViewControllerAnimated:YES];
Hope that helps.
You can use UINavigationController to move to secondViewController and come back by setting the UINavigationController property 'navigationBarHidden' to YES. Which will hide the navigation bar. Releasing and destroying of view controllers will takes care by this.
Then, you can take other strategy, is not the best to build your view controller hierarchy, but it also could work. You can overlay the secondViewContrller's view over the firstViewController's and make the secondViewController become the child from the firstViewController:
//...
[self addChildViewController:secondViewController];
[self.view addSubview:secondViewContrller.view];
//...
And when you want to remove the view controller, you have to remove the view and ask for the view controller to remove from his parent:
//...
[self.view removeFromSuperview];
[self removeFromParentViewController];
//...
But then you will have to control the view hierarchy by your own(putting and removing views and view controllers by your own).
I am working with Automatic Reference Counting.
I have a custom UIViewController subclass and whenever I call -presentViewController: animated:completion: or remove its view from the superview I would like to NSLog something like "I am dealloced" so I know that the view controller has successfully been removed. I have implemented the -dealloc method in my view controller. However I started a test project where I just had two UIViewController instances (no retain cycles) and -dealloc is not called either when I push the second UIViewController modally or when I remove the superview or when I remove it from the parent view controller. Am I missing something ? In my original project (not the test case) Instruments shows me that those controllers leave a memory footprint that I can't get rid off.
If you want to switch view controllers, and have the one you're switching away from be deallocated, then just switch the root view controller of the window. So, if you're in VC1 and want to go to VC2, then do this in VC1:
VC2 *vc2 = [[VC2 alloc] init]; // or however else is appropriate to get an instance of this class
self.view.window.rootViewController = vc2;
If you haven't created any property to point to vc1, then it will be deallocated after making this switch.
If you want to use a modal presentation or a modal segue (to get the animation when you switch controllers), you can still get the initial controller to be deallocated by switching the root view controller after the presentation from the viewDidAppear method of vc2:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.view.window.rootViewController = self;
}
To get a print when the View Controller is deallocated you can implement the dealloc method as
- (void) dealloc {
NSLog(#"The instance of MyViewController was deallocated");
}
Then to get a print when the View Controller left the view you can implement
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSLog(#"The instance of MyViewController left the main view")
}
If you use -presentViewController:animated:completion: you are retaining the parentViewController every time you call this method. ModalViewControllers are simply pushed on top of the other ViewController.
ModalViewControllers are only designed for some kind of information / User Input and stuff like that. If you want to dealloc the ParentViewController you have to deal with your own implementation.
dealloc method isn't called when the class is retained (or something in this class is retained) and not reeleased. It is justly for projects with both ARC and without it. So check your code twice.