how to fix iOS leak when flipping between two views - iphone

My iPhone app badly leaks when flipping back and forth between a main uiviewcontroller and a help uiviewcontroller .
Here is the source of the main view, followed by source of the help view.
MAIN VIEW - FLIP TO HELP.....................
// Changes from operational view to Help view.
- (IBAction)showHelp:(id)sender
{
// End trial mode:
self.stop_trial_if_started;
self.rename_trial_if_edited;
// Switch to trial help:
help_view_context = 0;
HelpView *controller = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
controller.delegate = self;
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
[controller release];
}
HELP VIEW - INIT.............................
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor];
help_scroll.editable = FALSE;
return;
}
HELP - RETURN TO MAIN VIEW.........................
// User clicked the button to return to operational view:
- (IBAction)done:(id)sender {
NSLog(#"help- done");
if( help_view_context == 0 ) {
[self.delegate trial_help_DidFinish:self];
}else{
[self.delegate file_help_DidFinish:self];
}
}
MAIN VIEW - RETURN FROM HELP...............................
// Inits operational view when user changes from Help view back to operational view.
- (void)trial_help_DidFinish:(HelpView *)controller {
NSLog(#"trial_help_DidFinish");
[self dismissModalViewControllerAnimated:YES];
self.init_trial_operation;
}

You are creating a controller with ref count of 1 and a local reference each time showHelp: is called:
HelpView *controller = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
you are losing your reference to it at the end of this method.
You happen to have references to it in done: (self) and *_help_didFinish (controller), but you never release it in either of those locations. Dismissing the controller is fine, but you also have to release it.
(Another option would be to never create a second one, and maintain an iVar to the original.)

You could well be leaking on this line
controller.delegate = self;
What is your property declaration for the delegate. If it's anything other than assign, then you either need to change it (preferred option) or make sure you are releasing it in the dealloc method of HelpView controller.

Related

UIImagePickerControllerCameraDeviceFront works every other time

This question is very similar to an existing question asked here UIImagePickerControllerCameraDeviceFront only works every other time I tried the solution presented but it didn't work for me
I have a simplest of a project with two view controllers. In the blue one I am displaying a small UIView with a UIImagePickerController in it. NOTE: I am displaying front facing camera when app is launched.
I hit the next button and go to orange view controller and when I hit the back button and come back to blue view controller the UIImagePickerController flips from Front to rear. I guess the reason is that it thinks its busy and moves to the rear cam. If I keep moving back and forth between the view controllers the camera keeps flipping front, back, front, back, front, back...
Here is my code and screenshots, what am I doing wrong?
In my *.h
#import <UIKit/UIKit.h>
#interface v1ViewController : UIViewController <UIImagePickerControllerDelegate>
{
UIImagePickerController *picpicker;
UIView *controllerView;
}
#property (nonatomic, retain) UIImagePickerController *picpicker;
#property (nonatomic, retain) UIView *controllerView;
#end
In my *.m file (This code is only used when blue colored view controller is displayed)
#import "v1ViewController.h"
#import <MobileCoreServices/UTCoreTypes.h>
#implementation v1ViewController
#synthesize picpicker;
#synthesize controllerView;
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
picpicker = [[UIImagePickerController alloc] init];
picpicker.delegate = self;
picpicker.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeImage, nil];
picpicker.sourceType = UIImagePickerControllerSourceTypeCamera;
picpicker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
picpicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
picpicker.showsCameraControls = NO;
picpicker.navigationBarHidden = NO;
picpicker.wantsFullScreenLayout = NO;
controllerView = picpicker.view;
[controllerView setFrame:CGRectMake(35, 31, 250, 250)];
controllerView.alpha = 0.0;
controllerView.transform = CGAffineTransformMakeScale(1.0, 1.0);
[self.view addSubview:controllerView];
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
controllerView.alpha = 1.0;
}
completion:nil
];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[picpicker dismissModalViewControllerAnimated:YES];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[picpicker dismissModalViewControllerAnimated:YES];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
} else {
return YES;
}
}
#end
You are dismissing the controller in both the viewDidDisappear and viewWillDisappear methods.
That could be the cause of your problem.
Although I do not have a device with a camera available right now to verify this, it seems that you're not dismissing the pickerview controller correctly. The documentation states that you should call dismissModalViewControllerAnimated: on the parent controller in order to dismiss the picker (though, calls to presented controllers will propagate to presenters - so this is not the problem), but in your case you're not displaying the controller modally in the first place so it will not work.
What I would try in this case is to release the picker instead (if not under ARC) and set it to nil (instead of calling [picpicker dismissModalViewControllerAnimated:YES];).
PS. In fact, it seems that there is a bigger problem with your design. Since each button is set to present the other party modally you are not dismissing any of the controllers ever. The controllers just keep stacking on each other. You should either consider to embed them in a navigation controller and have it handle the hierarchy or just set dismissModalViewControllerAnimated: (dismissViewControllerAnimated:completion: on iOS5+) as the action of the second controller's button instead of a modal segue.
This is a very simple issue. I don't know why this happens exactly, but it seems that UIImagePickerController was designed to recreated each time it's needed instead of keeping any reference to it, which seems logical if you think about it. Basically, you need to recreate and reconfigure your picker each time. Below I've pasted some code to give an image of what I mean.
Simple solution:
- (UIImagePickerController *)loadImagePicker {
UIImagePickerController *picpicker = [[UIImagePickerController alloc] init];
picpicker.delegate = self;
picpicker.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeImage, nil];
picpicker.sourceType = UIImagePickerControllerSourceTypeCamera;
picpicker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
picpicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
picpicker.showsCameraControls = NO;
picpicker.navigationBarHidden = NO;
picpicker.wantsFullScreenLayout = NO;
return picpicker;
}
and in:
-(void)viewWillAppear:(BOOL)animated{
if(!self.picpicker){
self.picpicker = [self loadImagePicker];
[self.view addSubview: self.picpicker];
}
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.picpicker removeFromSuperview];
self.picpicker = nil;
}

iPhone - self.view is nil in initWithNibName subclass, and viewDidLoad not called

I have this cascade :
Somewhere in the app
- (void) go
{
MyCustomViewController* controller = [[MyCustomViewController alloc] init];
controller.delegate = self;
[controller run];
}
in MyCustomViewController
- (id) init
{
// there is an if statement here in real to choose another XIB if needed
// but I only display here the code that is called in my tests
self = [super initWithNibName:#"MyXIB" bundle:nil];
if (!self) return nil;
self.delegate = nil;
return self;
}
- (void) run
{
// do things with self.view
}
MyCustomViewController inherits GenericWindowController
in GenericWindowController
/*- (id) initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil
{
if (!(self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) return nil;
self.view.userInteractionEnabled = NO; // THE APP CRASHES HERE ! self.view is nil
...
return self;
}*/
// METHOD created following first answers : NOT CALLED
- (void) viewDidLoad
{
self.view.userInteractionEnabled = NO;
// and many other things done with self.view
}
MyXIB has its File's owner set to MyCustomViewController and the view is connected.
All files are included and checked into the project.
GenericWindowController is designed to make some standard stuff.
MyCustomViewController extends this stuff to work with a custom View, as designed in MyXIB.
Why self.view is nil in GenericWindowController ?
Why viewDidLoad is not called ?
A view controller should not try to access its view in initWithNibName:bundle:. It should wait until viewDidLoad.
self.view is only valid after viewDidLoad -- in init... it is still nil.
Incredible !!! The problem was because of a missing localization for the XIB. Added the localization, and no problem anymore.

Reducing many similar methods to one parametrized method

I'm studying Objective-C.
A collegue left my company and I "inherited" her code. In a project, in a UIViewController class I found this code:
-(IBAction)goToSect1:(id)sender{
sect1=[[Sect1ViewController alloc]initWithNibName:#"SECT-1" bundle:nil];
[self presentModalViewController:sect1View animated:YES];
}
-(IBAction)goToSect2:(id)sender{
sect2=[[Sect2ViewController alloc]initWithNibName:#"SECT-2" bundle:nil];
[self presentModalViewController:sect2View animated:YES];
}
-(IBAction)goToSect3:(id)sender{
sect3=[[Sect3ViewController alloc]initWithNibName:#"SECT-3" bundle:nil];
[self presentModalViewController:sect3View animated:YES];
}
-(IBAction)goToSect4:(id)sender{
sect4=[[Sect4ViewController alloc]initWithNibName:#"SECT-4" bundle:nil];
[self presentModalViewController:sect4View animated:YES];
}
-(IBAction)goToSect5:(id)sender{
sect5=[[Sect5ViewController alloc]initWithNibName:#"SECT-5" bundle:nil];
[self presentModalViewController:sect5View animated:YES];
}
-(IBAction)goToSect6:(id)sender{
sect6=[[Sect6ViewController alloc]initWithNibName:#"SECT-6" bundle:nil];
[self presentModalViewController:sect6View animated:YES];
}
I consider this a little crazy, since we have six methods doing fundamentally the same thing. Is there a way to have only one parametrized method? How? Should I consider using the sender tag and a switch inside the body of my new method?
Thanks, any help is appreciated.
-(IBAction)goToThisViewControllerByUsingThisSection:(id)sender{
// Here I assume you are using this function with UIButton or other controls.
// If so, then you have to assign a tag for each section button or do as needed by your app.
// If you call this method in didselectrowatindexpath in delegate of UITableView then simply do use:
// indexPath.section instead of sender.tag
NSString *sectionWithViewController=[NSString stringWithFormat:#"Sect%iViewController",sender.tag];
Class classNam=NSClassFromString(sectionWithViewController);
UIViewController *unKnownViewController=[[classNam alloc]initWithNibName:[NSString stringWithFormat:#"SECT-%i",sender.tag] bundle:nil];
[self presentModalViewController:unKnownViewController animated:YES];
}
I wouldn't alter it at all. Those are IBActions, meaning that something in your interface is binded to them, more than likely buttons. So you have 6 buttons on your UI, each lead to a different view controller.
With the current way you have it, you can alter the individual functionality of each button without having to worry about affecting the others. Otherwise you wild be binding all those buttons to the same method, and then inside the method have to do a check for which button it is. At best, you will get rid of 5 lines of code and introduce 6 more. So the change will actually reduce readability and increase your number of lines of code.
In my opinion you should just leave it like that. The other option would be to have a single method like this:
-(IBAction)goToSection:(id)sender{
//Check the sender and then allocate depending on sender...
}
And simply bind the same method to all the buttons...
In my opinion that would look a lot worst.
There is not much you can do as mentioned by others.
Some suggestions:
If you use appropriate naming or change your init this line can be from
[[Sect1ViewController alloc] initWithNibName:#"SECT-1" bundle:nil];
to
[[Sect1ViewController alloc] init];
Either name the xib after the controller like Sect1ViewController or Sect1View or change your init for the class
// Sect1ViewController.m
- (id)init
{
self = [super initWithNibName:#"SECT-1" bundle:nil];
if (self) {
// ...
}
return self
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
return [self init];
}
Generally a viewController has a better idea what xib it should use than any calling code and this keep encapsulation.
May be a bad idea but included so you can see some things you can do
This structure of the method is repeating. I am not 100% you need to hold onto the viewController you are creating as the presenting controller will take a
retain on it.
With this in mind we can DRY this code up slightly like so
-(IBAction)goToSect1:(id)sender{
[self presentClass:[Sect1ViewController class]];
}
-(IBAction)goToSect2:(id)sender{
[self presentClass:[Sect2ViewController class]];
}
-(IBAction)goToSect3:(id)sender{
[self presentClass:[Sect3ViewController class]];
}
-(IBAction)goToSect4:(id)sender{
[self presentClass:[Sect4ViewController class]];
}
-(IBAction)goToSect5:(id)sender{
[self presentClass:[Sect5ViewController class]];
}
-(IBAction)goToSect6:(id)sender{
[self presentClass:[Sect6ViewController class]];
}
- (void)presentClass:(Class)class;
{
UIViewController *viewController = [[class alloc] init];
[self presentModalViewController:viewController animated:YES];
[viewController release]; viewController = nil;
}
}
This does not scale well as for every new button you need to add a new action even though they do the same thing - yuk
try that:
-(IBAction)goToSect:(id)sender{
if (sender /*do your check here*/)
{
sect=[[Sect1ViewController alloc]initWithNibName:#"SECT-1" bundle:nil];
}else if (sender /*another check*/){
sect=[[Sect2ViewController alloc]initWithNibName:#"SECT-2" bundle:nil];
} //other checks
[self presentModalViewController:sect1View animated:YES];
}
you will need all these if-checks because there are different ViewControllers. If they are pretty much the same I'd just leave 1 and pass/change some parameters.
hope it helps

UINavigationController and pushing an openGL UIView = never ending loops

I am trying to push an opengl UIView to my navigation controller like this
GraphViewController *gvc = [[GraphViewController alloc] initWithTicker:[listOfItems objectAtIndex:indexPath.row]];
[self.navigationController pushViewController:gvc animated:YES];
[gvc release];
The initWithTicker method looks like this
-(id) initWithTicker:(NSString*)ticker{
self = [super initWithNibName:nil bundle:nil];
if (self) {
self.title = ticker;
EAGLView *eagl = [[EAGLView alloc] initWithFrame:[UIScreen mainScreen].bounds];
eagl.animationInterval = 1.0 / 60.0;
[eagl startAnimation];
self.view = eagl;
}
return self;
}
When I go back and forward in my UINavigationController, the drawView method (in EAGLView) keeps looping. Furthermore, if I pushViewController again, the first one does not stop and a new one is created! I've tried making this an instance variable so only one is created and it has the same effect. I would be grateful if anyone has insight as to why this is happening
sergio Suggestion:
-(id) initWithTicker:(NSString*)ticker{
self = [super initWithNibName:nil bundle:nil];
if (self) {
self.title = ticker;
}
return self;
}
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
eagl = [[EAGLView alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.view = eagl;
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
eagl.animationInterval = 1.0 / 60.0;
[eagl startAnimation];
[super viewDidLoad];
}
same behaviour.
---This is how I fixed my drawView looping problem--
-(void)viewDidAppear:(BOOL)animated {
[eagl startAnimation];
[super viewDidAppear:animated];
}
-(void)viewDidDisappear:(BOOL)animated {
[eagl stopAnimation];
[super viewDidDisappear:animated];
}
--Craigs solution --
if(graphView == nil){
graphView = [[GraphViewController alloc] initWithTicker:[listOfItems objectAtIndex:indexPath.row]];
}else{
[graphView release];
graphView = [[GraphViewController alloc] initWithTicker:[listOfItems objectAtIndex:indexPath.row]];
}
Are you creating a new GraphViewController every time you want to push one onto your navigation stack? If so, it doesn't really matter how you're handling the creation of your EAGLView instance variable, since you're never going to be interacting with that view controller again anyway.
For example:
User taps something, a new GraphViewController is pushed on the stack
User goes back, this view controller continues to run
Return to 1. and repeat (thus creating a SECOND GraphViewController, and then a third, and then a fourth... etc.)
What you should probably be doing is maintaining your GraphViewController as an instance variable, and only creating it once. This will ensure that you're in turn only creating one EAGLView.
if (_graphViewController == nil) {
_graphViewController = [[GraphViewController alloc] initWithTicker:[listOfItems objectAtIndex:indexPath.row]];
}
[self.navigationController pushViewController:_graphViewController animated:YES];
Then, be sure to release the view controller in your dealloc method if you're going to be maintaining it as an ivar.
Would you try executing this code of yours:
EAGLView *eagl = [[EAGLView alloc] initWithFrame:[UIScreen mainScreen].bounds];
eagl.animationInterval = 1.0 / 60.0;
[eagl startAnimation];
self.view = eagl;
inside of loadView? I am not sure about why your view is behaving like you say, but that is the place where you are supposed to build your UI... so it might make a difference...
Furthermore, I would call [eagl startAnimation]; only in viewDidLoad...
Hope it helps...

How to dismiss a UIPopover correctly?

i have added a UIPopover to my navigationbar when the user touches the rightbarbuttonitem.
I want to know whats the cleanest coding for showing and dismissing the popover. With the code below, it works, but not correct.
If the user touches the rightbarbuttonitem first, the popover appears. Now he can choose a cell,.. works. Or you can touch outside the popover and it disappears. Both ways correctly.
But when touching the rightbarbuttonitem and after that, the same touch on that icon again, you have to touch it twice to bring up that popover again. (Not on the first touch).
How should that correctfunction look like? Thanks for your time..
-(void) downloads:(UIBarButtonItem*)button{
NSLog(#"downloads");
if(tableViewController == nil) {
tableViewController = [[[TableViewController alloc] initWithStyle:UITableViewStylePlain] autorelease];
self.popoverController = [[[UIPopoverController alloc] initWithContentViewController:tableViewController]autorelease] ;
[self.popoverController presentPopoverFromBarButtonItem:barButtonItem permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
tableViewController.delegate = self;
}else {
[self.popoverController dismissPopoverAnimated:NO];
tableViewController.delegate = nil;
tableViewController = nil;
//[self.popoverController release];
//self.popoverController = nil;
}
}
//delegate
-(void) selectedTable:(NSString*)text{
NSLog(#"selectedTable: %#", text);
[self.popoverController dismissPopoverAnimated:YES];
}
Comments
Your downloads: method checks for tableViewController value and make decision if UIPopoverController should be displayed or dismissed. But yours selectedTable: method does dismiss popoverController and does not set tableViewController to nil thus it doesn't work as expected by you.
And I also don't see UIPopoverControllerDelegate implementation, especially popoverControllerDidDismissPopover: which will tell you when UIPopoverController was dismissed (by touching outside your UIPopoverController for example).
Memory Management
Read memory management guide. [self.popoverController release] with self.popoverController = nil leads to crash - one release and second release during setting to nil. (I assume your popoverController property retains).
#Chiefly Izzy: Thanks for your help. You point me in the right direction.
Here´s the code i´m using. It works great. Hope that memory management is clean, too. Isn´t it?
-(void) downloads:(UIBarButtonItem*)button{
if([self.popoverController isPopoverVisible])
{
[self.popoverController dismissPopoverAnimated:YES];
[self.popoverController.delegate popoverControllerDidDismissPopover:self.popoverController];
return;
}
// PopOver Bookmarks-Icon
if(tableViewController == nil) {
tableViewController = [[PdfTableViewController alloc] initWithStyle:UITableViewStylePlain]; //autorelease];
self.popoverController = [[UIPopoverController alloc] initWithContentViewController:tableViewController]; //autorelease] ;
[self.popoverController presentPopoverFromBarButtonItem:barButtonItem permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
popoverController.delegate = self;
tableViewController.delegate = self;
}
/* Using "isPopoverVisible" from top or that else. both works
else {
[self.popoverController dismissPopoverAnimated:YES];
[self.popoverController.delegate popoverControllerDidDismissPopover:self.popoverController];
}
*/
}
-(void) selectedTableView:(NSString*)text{
NSLog(#"selectedTableView: %#", text);
[self.popoverController dismissPopoverAnimated:YES];
[self.popoverController.delegate popoverControllerDidDismissPopover:self.popoverController];
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popover{
NSLog(#"popoverControllerDidDismissPopover");
tableViewController.delegate = nil;
[tableViewController release];
tableViewController = nil;
popoverController.delegate = nil;
[popoverController release];
popoverController = nil;
}