accessing variables/functions from subclass in Objective C - iphone

I am having a problem with accessing public variable 'activity', which is a UIActivityIndicatorView type, see class declaration below in QuickStartViewController.h:
#interface QuickStartViewController : UIViewController <ABPeoplePickerNavigationControllerDelegate> {
#public
IBOutlet UIActivityIndicatorView *activity;
}
#property (nonatomic, retain) UIActivityIndicatorView *activity;
#end
The function is called from another class:
#import "QuickStartViewController.h"
#interface NumberValidator : QuickStartViewController....
See below:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[activity startAnimating];
NSLog(#"This function is called, but [activity startAnimating] still doesn't work...");
}
Note: [activity startAnimating] works fine when called within the QuickStartViewController class.
Do you have any suggestions as to why [activity startAnimating] is not working?

The IBOutlet macro indicates that the UIActivityIndicatorView will be constructed and assigned when an instance of QuickStartViewController or NumberValidator are instantiated via NSBundle's +loadNibNamed:owner:options: or calling UIViewController's initWithNibName:bundle:
If you are not instantiating your NumberValidator via it's nib, then the activity property will not be assigned. If you are constructing it via a nib, then you have not assigned the outlet with an appropriate UIActivityIndicatorView in Interface Builder, by CTRL+Dragging your UIActivityIndicatorView to your controller.

I would start by setting a breakpoint in -connectionDidFinishLoading: and verifying that activity is not nil.

This probably works - i.e. the activity indicator starts animating. There may be another problem, though - the GUI is not refreshed until you stop processing the connectionDidFinishLoading method, and therefore it seems that [activity startAnimating] doesn't work. (You can test this by not calling [activity stopAnimating] - it should show up eventually.)
See e.g. this thread (connectionDidFinishLoading - how to force update UIView?) and my response.

Is it (a) not compiling, (b) crashing when it hits there, or (c) just not doing anything? My suspicion is that it's (c), and it's because you don't have an activity indicator there. Try logging the value of activity to the console, and verify its a valid object.

Thanks for the quick responses.
The connectionDidFinishLoading does successfully execute, and I have placed NSLogs to confirm. However, the startAnimating doesn't.
Note:
If I do [activity startAnimating]; within the following then it works...:
QuickStartViewController.m (not NumberValidator.m):
- (IBAction)showPicker:(id)sender {
[activity startAnimating];
...
}

Related

UIActivityIndicatorView proper usage

My question regards the use of activity indicator in an iPhone project.
I have a class that contains an UIActivityIndicatorView
#interface StatusView : UIView
{
UIActivityIndicatorView *indicator;
UILabel *textLabel;
}
- (id)initWithFrame:(CGRect)frame Text:(NSString*)text andShowIndicator:(BOOL)value;
In my business logic code I call [indicator startAnimating] and the frame appears at the bottom of the screen. The code also contains a dealloc method that releases the indicator
- (void)dealloc
{
[indicator release];
[super dealloc];
}
Most of the time the indicator works well, however there are a few occasions that never disappears.
Do I always need to explicitly call the stopAnimating method? Does the release handle it?
What is the proper usage?
stopAnimating: method stops the wheel of UIActivityIndicatorView and release release the object.In Objective-C each object has an internal counter that is used to keep track of all references used by the objects or object has. [object retain] increments the counter by 1 and [object release] decrements the counter by 1. When counter reaches to zero, dealloc is then called. release is about memory management while stopAnimating: is a functionality of UIActivityIndicatorView.
So if you want to stop animating your UIActivityIndicatorView you would have to call stopAnimating: method. In ARC dont have to do release so better to use ARC.
the best way when you are using this object is stopAnimating when you want to stop, remove from super view ( [activityObject removeFromsuperview] ) and finally release it. [ activityObject release];

UIActionSheet code crashes when moved from UIViewController file to separate class file

I have searched and searched the board(s) and am not able to figure this out. It has got to be something simple and right in front of me.
I am trying clean up my code and make it more reusable. I was taking some UIActionSheet code that works from a UIViewController and making its own object file. Works fine, until I add UIActionSheetDelegate methods.
When a button is pressed, instead of firing the actionSheetCancel method, it crashes with no stack trace. Every time.
My code is below. Any help would be appreciated. My guess has been it is because I am not using the xcode storyboard tool to connect things together, but I would think this is legal.
egcTestSheet.h:
#import <UIKit/UIKit.h>
#interface egcTestSheet : NSObject <UIActionSheetDelegate> {
}
- (void) showSheet:(UITabBar *) tabBar
displayTitle:(NSString *) name;
#end
egcTestSheet.m
#import "egcTestSheet.h"
#implementation egcTestSheet
-(void) showSheet:(UITabBar *)tabBar displayTitle:(NSString *)name{
UIActionSheet *menu = [[UIActionSheet alloc] initWithTitle:name
delegate:self
cancelButtonTitle:#"Done"
destructiveButtonTitle:#"Cancel"otherButtonTitles:nil];
[menu showFromTabBar:tabBar];
[menu setBounds:CGRectMake(0,0,320, 700)];
}
// actionsheet delegate protocol item
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex: (NSInteger)buttonIndex{
NSLog(#"button index = %d", buttonIndex);
}
- (void)actionSheetCancel:(UIActionSheet *)actionSheet{
NSLog(#"in action canceled method");
}
#end
call code from a UIViewController object:
egcTestSheet *sheet = [[egcTestSheet alloc] init];
[sheet showSheet:self.tabBarController.tabBar displayTitle:#"new test"];
Your action sheet is probably being released as it is dismissed (are you using ARC?). This means when it tries to call it's delegate to inform said delegate of its dismissal/selection, it is trying to call self. Self is a dangling pointer by this time, because it has been released.
In the view controller that is presenting/calling this action sheet, set a property to keep a reference to the action sheet. Set the property to nil on dismissal of the action sheet.

Objective-C on iPhone - tab bar crash

I'm relatively new to Objective-C and coding. I've tried doing a little dev on my own but have now become stuck on what is probably a rookie error.
I've created a tab bar controller with 5 views, one such view is a UIWebView. I've got the Webview working and it loads, but when I select a different tab, the app crashes. Please find my code below and any help would be appreciated!
#import <UIKit/UIKit.h>
#interface LiveViewController : UIViewController {
IBOutlet UIWebView *liveView;
}
#property (nonatomic, retain) UIWebView *liveView;
#end
#import "LiveViewController.h"
#implementation LiveViewController
#synthesize liveView;
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
/*
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization.
}
return self;
}
*/
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[self.liveView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.britishseapower.co.uk/live/"]]];
[super viewDidLoad];
}
- (void)webViewDidStartLoad:(UIWebView *)liveView
{
// starting the load, show the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)liveView
{
// finished loading, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)liveView:(UIWebView *)liveView didFailLoadWithError:(NSError *)error
{
// load error, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// report the error inside the webview
NSString* errorString = [NSString stringWithFormat:
#"<html><center><font size=+5 color='red'>An error occurred:<br>%#</font></center></html>",
error.localizedDescription];
[self.liveView loadHTMLString:errorString baseURL:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
if ( [self.liveView loading] ) {
[self.liveView stopLoading];
}
self.liveView.delegate = nil; // disconnect the delegate as the webview is hidden
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)dealloc {
[liveView release];
[UIWebView release];
[LiveViewController release];
[super dealloc];
}
#end
Many thanks,
Ryan
[UIWebView release]; [LiveViewController release];
This is what make your app crash.
It's not valid to send a release message to a class itself.
What you've done with [liveView release]; is enough (with the call to [super dealloc];.)
You should also set the delegate to nil in the dealloc method as in the viewWillDisappear method self.liveView.delegate = nil;. This way you're sure to avoid any further message sent to the LiveViewController from the UIWebView.
You should read a bit more of documentation on Objective-C to better understand how it works.
Not sure if this is related but I noticed that you aren't setting yourself as the delegate anywhere in code which means that it must be connected in Interface Builder. Now when the view disappears, you are breaking that connection, but if the view were to re-appear and wasn't previously unloaded that connection will remain broken.
One of the most common reasons why an app may crash is to refer to or send a message to an object that has been already released from the memory. And this type of bug can be easily located using NSZombieEnabled and looking into the console message. So if you haven't already tried that, that's the first thing you must do.
The problem could be in LiveViewController but could be in the other view controllers as well. I wouldn't believe the problem is 100% in LiveViewController because the view controller wouldn't try releasing its view when the view is not shown unless it gets a memory warning. And you run the app using the simulator, it's unlikely it will have a memory warning unless you simulate one.
You would probably know that a view controller never create a view unless the view is used by an object. One of the other view controllers may have a silly bug in its view loading process which causes a crash. Or, you might have released another view controller by mistake. Make 100% sure that the other view controllers have no problem showing their views on their own, when you keep changing between their views (without showing LiveViewController).
So what I would do is to try NSZombieEnabled and check if it accesses a released object, and if it does, what the object is. Also, I will make a double check that the problem is related to LiveViewController. If it doesn't help I would log a message when LiveViewController and its liveView is deallocated (for liveView you need to subclass it). Because delegate property almost always does not retain an object, if the LiveViewController object is released (which shouldn't happen) and liveView still has a reference to it in the delegate property it will make a crash.
Crashes like this are almost always related to releasing an object that has already been released and deallocated.
Let XCode help you find the error. In XCode 4:
- In the toolbar, select the scheme list, and select 'Edit Scheme'
- Select the 'Run Your.app' in the list on the left.
- Under 'Environment Variables', add the following name/value pairs in the appropriate columns:
CFZombieLevel 3
NSZombieEnabled YES
Now when debug your app, you will get a message telling when -release is called on an object that already has a -retainCount of zero. Now you have a good clue to start your investigation.
Note that these flags prevent objects from being deallocated, so it is best to turn them on as needed to prevent out of memory errors.

Can't programmatically hide UIButton created with IB

My iOS UIButton is correctly linked from IB to an IBOutlet in my view controller, as I can change its title from my code. Ie:
[self.myButton setTitle:#"new title" forState:UIControlStateNormal]; //works
However,
[self.myButton setHidden:YES]; //doesn't work
//or
self.myButton.hidden = YES; //doesn't work
What's going on? How can I make myButton disappear?
Update: some additional info
Here's the code related in to my UIButton:
in my .h file
IBOutlet UIButton *myButton;
-(IBAction)pushedMyButton:(id)sender;
#property (nonatomic,retain) UIButton *myButton;
in my .m file
#synthesize myButton;
- (void)pushedMyButton:(id)sender{
self.myButton.hidden = YES;
}
- (void)dealloc{
[self.myButton release];
}
Ok I found a workaround that works but I still don't know why my original code wasn't working in the first place. I used Grand Central Dispatch to dispatch a block containing the hide call on the main queue, like this:
dispatch_async(dispatch_get_main_queue(), ^{
self.myButton.hidden = YES; //works
});
Interesting. None of the initial code in my IBOutlet was wrapped in GCD blocks though. Any ideas?
That should work, try rename it and hide it just to check that there aren't two buttons on top of each other.
I had this same problem and found the solution was to put the hidden in the right place, in my case in the viewDidLoad function.
User Interface (UI) API (UIKit ...) methods have to be run on Main Thread!
So this will run on Main thread (as *dispatch_get_main_queue*):
dispatch_async(dispatch_get_main_queue(), ^{
self.myButton.hidden = YES; //works
});
BUT usually we do something like this:
[self performSelectorOnMainThread:#selector(showButton) withObject:nil waitUntilDone:NO];
[self performSelectorOnMainThread:#selector(hideButton) withObject:nil waitUntilDone:NO];
-(void)showButton
{
myButton.hidden = NO;
}
-(void)hideButton
{
myButton.hidden = YES;
}
As per Apple's documentation: http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/uiview/uiview.html
"
Threading Considerations
Manipulations to your application’s user interface must occur on the main thread. Thus, you should always call the methods of the UIView class from code running in the main thread of your application. The only time this may not be strictly necessary is when creating the view object itself but all other manipulations should occur on the main thread.
"
What worked for me is putting the manipulating code in viewDidLoad instead of initWithNibName, like this:
- (void)viewDidLoad
{
btnRestart.enabled = false;
}
Had the same problem: button.hidden = YES didn't hide.
Solved it when I defined it in the .h file using #property and #synthesize in the .m file thus making it self.button.
Now self.button.hidden = YES works

Delegate not being called

I've a viewcontroller "ResultsViewController" with a button called emailbutton. when this button is pressed, i want a function to be called from another view called "Illusions_AppViewController" (both these viewcontrollers are not linked).
Therefore i defined a protocol in the "ResultsViewController.h":
#protocol ResultsViewDelegate <NSObject>
#optional
- (void) resultspage;
#end
#interface ResultsViewController : UIViewController
{
id<ResultsViewDelegate> mydelegate;
UIButton *emailButton;
}
#property(nonatomic,retain) IBOutlet UIButton *emailButton;
#property (nonatomic,assign) id<ResultsViewDelegate> mydelegate;
#end
In the ResultsViewController.m :
-(IBAction)emailButtonPressed:(id)sender
{
NSLog(#"entered emailbuttonpressed");// the app enters this method and gets hanged
if ([mydelegate respondsToSelector:#selector(resultspage)]) {
NSLog(#"entered respondstoselector");// this is never displayed in the log-showing that the delegates doesnt respond to selector
[mydelegate resultspage];
}
}
In my other view, "Illusions_AppViewController.m":
- (void)resultspage{
NSLog(#"Entered results page");
ResultsPageController *resultspagecontroller = [[ResultsPageController alloc] initWithNibName:#"ResultsPageController" bundle:nil];
resultspagecontroller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:resultspagecontroller animated:YES];
}
Would appreciate if anyone can help me with this. I've no clue of why the delegate is not called. the app gets hanged as soon as i press the emailbutton. Thanks!
The implementation/use of delegates is wrong. Please refer to this tutorial.
Hope this helps.
Thanks,
Madhup
or is there any other way to get this done. i just need the results page function to be called whenever the email button is pressed. i tried using this way:
ResultsViewController.m
-(IBAction)emailButtonPressed:(id)sender
{
NSLog(#"entered emailbuttonpressed");
illusions_AppViewController *illusionsview = [[illusions_AppViewController alloc]init];
[illusionsview performSelector:#selector(resultspage)];
}
Now the results page function gets called, but the resultspagecontroller that it needs to display as a modalviewcontroller never appears.the app hangs, and no errors in the console either.
To answer your second question, you are on the right track. Simply create an instance of your Illusions_AppViewController and call the illusionsView method in it instead using:
- (IBAction)emailButtonPressed {
illusions_AppViewController *illusionsview = [[illusions_AppViewController alloc]init];
[illusionsview resultspage];
[illusionsview release];
}