I have two UIViewControllers in a storyboard (with a NavigationController).
I'm accessing the camera and taking a picture in ViewController1. I want to switch to ViewController2 and display the captured picture in a UIImageView.
ViewController2 H file
#interface ViewController2 : UIViewController
{
-IBOutlet UIImageView *matchImage;
}
-(IBAction)restart:(id)sender;
#property (retain, nonatomic) UIImageView *matchImage;
#end
ViewController1 M file
After my code to take the picture I attempt to switch views using the following syntax
ViewController2 *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"ViewController2"];
[self.navigationController pushViewController:vc animated:YES];
[vc.matchImage setImage:tempImage];
The first time I take a picture this works perfectly fine and I see the image in ViewController2.
However I have a back button on ViewController2 with the following syntax
ViewController2 M file
-(IBAction)restart:(id)sender
{
ViewController1 *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"ViewController1"];
[self.navigationController pushViewController:vc animated:YES];
}
This successfully returns to ViewController1 and I'm able to take a picture again and repeat the process. But after about 3-4 times repeating the process I get Received Memory Warning and the app crashes. Running the Analyzer in Xcode yields no issues.
If I comment out the following line
[vc.matchImage setImage:tempImage];
Then I can perform endless captures (over 50 with no issues) but of course ViewController2 does not show an image (it is blank). So this led me to believe I need to release matchImage (especially because it is a property with a retain attribute). But this didn't seem to help. Any help would be greatly appreciated. I'm still learning about memory management.
UPDATE
Here is a bit more info, after 3 iterations of back and forth switching between the views this is the debugger output. I put in NSLog statements in the two ViewController viewDidUnload and didReceiveMemoryWarning methods. So when the Received memory warning occurs it appears 3 "instances" of the 2 viewcontrollers are being unloaded. So it appears that for each iteration of switching between the viewcontrollers I'm getting a new copy. Is this what you would expect?
2012-10-22 08:23:32.008 TestApp[787:707] Received memory warning.
2012-10-22 08:23:32.015 TestApp[787:707] viewDidUnload ViewController1: 0xfd52250
2012-10-22 08:23:32.017 TestApp[787:707] didReceiveMemoryWarning ViewController1: 0xfd52250
2012-10-22 08:23:32.023 TestApp[787:707] viewDidUnload ViewController2
2012-10-22 08:23:32.025 TestApp[787:707] didReceiveMemoryWarning ViewController2
2012-10-22 08:23:32.028 TestApp[787:707] viewDidUnload ViewController1: 0x16dc30
2012-10-22 08:23:32.030 TestApp[787:707] didReceiveMemoryWarning ViewController1: 0x16dc30
2012-10-22 08:23:32.033 TestApp[787:707] viewDidUnload ViewController2
2012-10-22 08:23:32.037 TestApp[787:707] didReceiveMemoryWarning ViewController2
2012-10-22 08:23:32.040 TestApp[787:707] viewDidUnload ViewController1: 0x171de0
2012-10-22 08:23:32.042 TestApp[787:707] didReceiveMemoryWarning ViewController1: 0x171de0
2012-10-22 08:23:32.044 TestApp[787:707] didReceiveMemoryWarning ViewController2
2012-10-22 08:23:32.046 TestApp[787:707] didReceiveMemoryWarning ViewController1: 0xfd87580
What you're doing in your restart method is not going back to your previous controller but adding another copy of the first controller type to the navigation stack. Try telling the navigation controller to popViewControllerAnimated: instead.
Related
I have aUITabBarController as my main base view controller. Under the first tab, I have a UINavigationController which of course has a rootViewController associated with it, call it vcA. vcA has a button which fires a child view controller, vcB using the code:
[self performSegueWithIdentifier:#"PlacesDetailsSegue" sender:senderDictionary];
This appears to work, and I see in instruments that new allocations for vcB are occurring.
When I pop the view controller back to vcA, everything looks to work, but it appears that vcB is never released (i.e., dealloc is never called). So every time a go from vcA->vcB->vcA->vcB, the memory usage increases and increases.
I have some instance variables inside vcB, all of which I set to nil in dealloc. But since dealloc isn't being fired, they are never actually set to nil.
I do have a [UIView animationWith...] block which references the frame properties of self.view, but I have managed that using the code:
__unsafe_unretained BBCategoryViewController *weakSelf = self;
[UIView animateWithDuration:duration
animations:^{
[_feedTableView setFrame:CGRectMake(0,
_navBar.frame.size.height,
weakSelf.view.frame.size.width,
weakSelf.view.frame.size.height - kMenuBarHeight)
];
}
completion:nil];
Does anyone have any idea how I can go about finding out what objects are still being retained on a vcB pop?
For reference, my interface extension is:
#interface BBCategoryViewController () <UITableViewDataSource, UITableViewDelegate> {
UITableView *_feedTableView;
UIRefreshControl *_refreshControl;
BBSearchBar *_searchBar;
MKMapView *_mapView;
BBNavigationBar *_navBar;
NSString *_title;
NSString *_categoryId;
NSArray *_feedArray;
}
#end
and my dealloc (which is never fired) is:
-(void)dealloc {
NSLog(#"Dealloc: BBCategoryViewController\n");
_feedTableView = nil;
_refreshControl = nil;
_searchBar = nil;
_mapView = nil;
_navBar = nil;
_feedArray = nil;
}
UPDATE: This was actually due to a retain cycle in a child view, and not directly related to the view controllers as I first thought. Dan F led me to the correct answer here, in case anyone runs across something similar.
Your vcB should absolutely be deallocated, and dealloc called when you pop back to vcA. You must either be keeping a strong reference to it somewhere, or you're doing your "pop" incorrectly. If you're doing that with a segue, that could be your problem -- segues should not be used for going backwards (except unwind segues). So either use an unwind segue or use popViewControllerAnimated to go back to vcA. Also, when using ARC, there's no need to set your ivars to nil in dealloc.
My ViewController1 pushes ViewController2
ViewController2 *controller =
[[ViewController2 alloc] init];
[self.navigationController pushViewController:controller
animated:NO];
[controller release];
ViewController2 has UITableView ... in xib file I connected delegate with File's Owner. Also ViewController2 has Done button
- (IBAction)doneButtonPressed {
[self.navigationController popViewControllerAnimated:NO];
}
The problem is that if to click table rows and done button at the same time, sometimes didSelectRowAtIndexPath: method calls after that ViewController2 was popped, and I have SIGABRT error and this thing in logger :
[__NSCFSet tableView:didSelectRowAtIndexPath:]: unrecognized selector sent to instance 0x62579d0'
So how tableView:didSelectRowAtIndexPath can be called after I popped viewController2 ? It should be dead...
One easy fix is to do this:
- (IBAction)doneButtonPressed {
self.tableView.delegate = nil;
[self.navigationController popViewControllerAnimated:NO];
}
That way you guarantee that while you're leaving that view no more delegate calls will happen. You could also do this in the dealloc method for the view controller (probably a better place for it).
popViewController & pushViewController methods perform their tasks asynchronously. They use an animation block to slide the viewController in and out. The ViewController gets removed from its superview and gets released in the completion portion of the animation block. The crash is because of this delay (I think 0.3 seconds).
I am trying to implement a modal navigation controller as described in the Apple iOs Guide: Combined View Controller Interfaces
I have come to the conclusion that I am missing something both obvious and stupid as I simply cannot get anything to display, I get a blank white screen.
Swapping things out I can prove that the view controller that I am using as the navigation controllers RootViewController works fine on it's own (by adding it manually as a view subChild).
Further, implementing addSubView ([self.view addSubview:navController.view]) instead of presentModalViewController seems to work OK.
Can anyone point out my simple error because I am 5 minutes short of kicking my own face :D
header
#import <UIKit/UIKit.h>
#interface BaseViewController : UIViewController {
}
implementation
#import "BaseViewController.h"
#import "ScannedListViewController.h"
#import "ScannedItemViewController.h"
#implementation BaseViewController
- (void)viewDidLoad {
ScannedListViewController *listViewController = [[ScannedListViewController alloc] init];
ScannedItemViewController *itemViewController = [[ScannedItemViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:listViewController];
[navController pushViewController:itemViewController animated:NO];
[self presentModalViewController:navController animated:YES];
[listViewController release];
[itemViewController release];
[navController release];
[super viewDidLoad];
}
The RootControllerView is a basic test TableViewController with the following header
#interface ScannedListViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource>
Thank you in advance if your able to help
Why are you presenting something modally in a view controller's viewDidLoad method? I find that odd off the top. Generally you show a modal view controller in response to some action (like tapping a button).
Is there a reason you're showing a navigation controller with a second view controller already pushed on it after the root?
You should have [super viewDidLoad] as the first line, not the last line, of the method.
You do not need to have <UITableViewDelegate, UITableViewDataSource> after UITableViewController because it already adopts those protocols. Remove that bit.
Hi I have a very basic issue of memory management with my UIViewController (or any other object that I create);
The problem is that in Instruments my Object allocation graph is always rising even though I am calling release on then assigning them nil.
I have 2 UIViewController sub-classes each initializing with a NIB;
I add the first ViewController to the main window like [window addSubView:first.view];
Then in my first ViewController nib file I have a Button which loads the second ViewController like :
-(IBAction)loadSecondView{
if(second!=nil){ //second is set as an iVar and #property (nonatomic, retain)ViewController2* second;
[second release];
second=nil;
}
second=[[ViewController2 alloc]initWithNibName:#"ViewController2" bundle:nil];
[self.view addSubView:second.view];
}
In my (second) ViewController2 i have a button with an action method
-(IBAction) removeSecond{
[self.view removeFromSuperView];
}
Please let me know if the above scheme works in a managed way for memory...?
In Instruments It does not show release of any allocation and keeps the bar status graph keeps on rising.
First of all, why use this scheme when second is a property:
if(second!=nil){
[second release];
second=nil;
}
second=[[ViewController2* second]initWithNibName:#"ViewController2" bundle:nil];
A propery automatically releases it's old value when the setter is used. So this could be rewritten as:
if(self.second == nil) { //Prevents creating a new controller if there already is one.
self.second = [[[ViewController2 alloc] initWithNibName:#"ViewController2" bundle:nil] autorelease];
}
Also, what's up with [ViewController2* second]?
Why is that asterisk there, and what does the class method second do?
I have a custom viewController called SourceListViewController, and I'm adding it into a UINavigationController, the view of which is then added to the window of the iphone App. After passing the SourceListViewController to UINavigationController, I release the sourceListViewController.
SourceListViewController *sourceListVC = [[SourceListViewController alloc] initWithNibName:#"SourceListViewController" bundle:nil];
UINavigationController *navigationController = [[[UINavigationController alloc] initWithRootViewController:sourceListVC] autorelease];
[sourceListVC release];
When I do this, the app would crash after the view is loaded onto the phone. When I commented out the last line, the app work fine. Isn't initWithRootViewController supposed to retain the copy of sourceListVC?
You are autoreleasing navigationController. So if navigationController gets autoreleased (which will probably happen in the next runloop) then so will sourceListVC.