Button causing EXC_BAD_ACCESS - iphone

I have trawled SO for an answer that makes sense to my question, so don't hate me if this is an easy one!
I am adding two views to the window:
self.appView = [[AppViewController alloc] initWithNibName:nil bundle:nil] ;
self.buttonBar = [[ButtonBar alloc] initWithNibName:nil bundle:nil];
[window insertSubview:[self.appView view] belowSubview:[self.launchScreen view]];
[window insertSubview:[self.buttonBar view] belowSubview:[self.launchScreen view]];
Before I remove the self.launchScreen.
When I add elements to the appView (it's a uitableviewcontroller) they work as expected, but when I add a button to the buttonBar (either in the XIB or via code), click them causes EXC_BAD_ACCESS.
Here's the init code from buttonBar (which is adding one button):
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.settingsButton = [[UIButton alloc] initWithFrame:CGRectMake(0,10,40,28)];
[self.settingsButton addTarget:self action:#selector(settings:) forControlEvents:UIControlEventTouchUpInside];
UIImage *btnImage = [UIImage imageNamed:#"play.png"];
[self.settingsButton setImage:btnImage forState:UIControlStateNormal];
[btnImage release];
[self.view addSubview:self.settingsButton];
}
return self;
}
My question is in two parts, 1) WHAT GIVES?! I've been at this way too long, and 2) How can I debug this stuff? I hate coming to SO to ask n00b questions when I'm sure XCODE's debugging tools would help me track this stuff down...

Use #selector(settings) instead.

I'm not exactly sure if this is the problem but I dont think you need the "retain" at the end in this line
self.settingsButton = [[[UIButton alloc] initWithFrame:CGRectMake(0,10,40,28)] retain];
I'm assuming settingsButton is already in your header file with a #property(retain) and you have synthesized it in your .m file.
release it in the dealloc method instead.

Related

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 fix iOS leak when flipping between two views

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.

App crashes at [UIWebView webView:didReceiveTitle:forFrame:]

I am implementing a simple in-app browser. In my home view (UITableViewController), I have something like:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
WebViewController *webViewController = [[WebViewController alloc] init];
switch (indexPath.row) {
case 0:
webViewController.stringURL = #"http://www.google.com";
break;
case 1:
webViewController.stringURL = #"http://www.bing.com";
break;
default:
webViewController.stringURL = #"http://stackoverflow.com";
break;
}
[self.navigationController pushViewController:webViewController animated:YES];
[webViewController release];
}
The app crashed after I repetitively navigated back and forth between my home view and webViewControllera few times.
Inside WebViewController class, I have nothing but a [UIWebView *webView] and a [UIActivityIndicatorView *activityIndicator]. Both are with attributes nonatomic, retain. Here is the implementation.
#import "WebViewController.h"
#implementation WebViewController
#synthesize webView, activityIndicator, stringURL;
- (void)dealloc
{
[self.webView release];
self.webView.delegate = nil;
[self.activityIndicator release];
[super dealloc];
}
-(void)loadView {
UIView *contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
self.view = contentView;
CGRect webFrame = [[UIScreen mainScreen] applicationFrame];
webFrame.origin.y = 0.0f;
self.webView = [[UIWebView alloc] initWithFrame:webFrame];
self.webView.backgroundColor = [UIColor blueColor];
self.webView.scalesPageToFit = YES;
self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
self.webView.delegate = self;
[self.view addSubview: self.webView];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.stringURL]]];
self.activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.activityIndicator.frame = CGRectMake(0.0, 0.0, 30.0, 30.0);
self.activityIndicator.center = self.view.center;
[self.view addSubview: self.activityIndicator];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadView];
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
// starting the load, show the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[activityIndicator startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// finished loading, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[activityIndicator stopAnimating];
}
#end
I just ran my app in Instruments using the Zombies template, which shows -[UIWebView webView:didReceiveTitle:forFrame:] is the Zombie call. But I still can’t figure out what is actually the problem.
(Please download trace if needed)
Any help is greatly appreciated!
[Update]:
As #7KV7 and #David pointed out, there is an obvious bug in my dealloc function. I should call self.webView.delegate=nil; first before I release self.webView. Sorry about that. Unfortunately, after I fix it, the app still crashes in the same way.
If I delete [webViewController release]; from the first code block, the crash actually is gone. But obviously, there will be memory leak.
First of all, remove that call to loadView in viewDidLoad. The framework will the call the method when it doesn't find a view provided in XIB file. Second, your loadView is filled with memory leaks. You are allocating, initializing and retaining an object every time the method is called. So you are taking ownership twice and releasing it only once in the dealloc.
The objects are not being properly deallocated. You should do something like alloc-init-autorelease to solve this. Next thing is the that every time the controller gets loaded, because of your call to loadView, you end up creating two web view objects and two requests. You lose reference to one of them as you reassign. Herein, lies the problem mentioned in the title. You aren't able to reset the delegate of a web view object that has your controller as a delegate. Imagine a request being completed soon after you leave. Here the message will go to a zombie object. This is a pretty good example for why you need to nil out your delegates.
- (void)dealloc
{
self.webView.delegate = nil;
[self.webView release];
[self.activityIndicator release];
[super dealloc];
}
Try this dealloc. You were releasing the webview and then setting the delegate as nil. You should first set the delegate as nil and then release it. Hope this solves the issue.
I think what's happening is that you are going back while the page is still loading so the controller gets deallocated and then the webview finishes loading.
Try calling [webView stopLoading] in your viewDidUnload method to make sure this isn't happening.
Don't know if it's the cause of your problem, but this is definitely wrong:
[self.webView release];
self.webView.delegate = nil;
You cannot (safely) refer to self.webView after you release it!
Instead of pushing webViewController,add its view to self.view .
Dont call [self loadView]; in viewDidLoad.

Extending UIVIewController of my own I got error accessing self.view

I have created a View controller with some methods I need to use often in different aplications. It works like a charm if I use it directly but when I try to create another UIViewController that extend my class I cannot access self.view anymore. This is the init method of the original class:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil slideFrom:(HalfViewControllerType) from {
self.slideFrom = from;
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
CGRect viewScreen = [self.view bounds];
[self moveTo:startPoint inDuration:0.0];
}
return self;
}
At the point of retrieving [self.view bounds] I got an EXC_BAD_ACCESS.
Even hacking the values manually it then fail to all the other self.view like transform animation and so on.
The call to create the view is this, but it never got after the init method:
SelectViewController *sVC = [[SelectViewController alloc] initWithNibName:#"SelectViewController" bundle:nil slideFrom:top];
[sVC setDelegate:self];
[self.view addSubview:sVC.view];
[sVC slideIn];
[sVC release];
Any help on understanding what I am doing wrong would be really appreciated.
Francesco.
I couldn't understand what was happening, so I re-wrote everything and now works. I can just guess I was releasing something I shouldn't have.