Send message to a different class (Obj C) - iphone

I have a UITableViewController (OnTVViewController) who's viewDidLoad is similar to below (basically parses some XML in the background and shows an activity indicator while this is happening).:
- (void)viewDidLoad {
OnTVXMLParser *xmlParser = [[OnTVXMLParser alloc] init];
/* Runs the parse command in the background */
[NSThread detachNewThreadSelector:#selector(parse) toTarget:xmlParser withObject:self];
//[xmlParser parse];
// new view to disable user interaction during downloading.
loadView = [[UIView alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
loadView.backgroundColor = [UIColor darkGrayColor];
//Loader spinner
UIActivityIndicatorView *act = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[loadView addSubview:act];
act.center =loadView.center;
[self.view addSubview:loadView];
[self.view bringSubviewToFront:loadView];
[act startAnimating];
[act release];
[super viewDidLoad];
}
OnTVViewController also has this method to remove the activity indicator (just trying to log a message while debugging):
- (void)removeActivityView {
//[loadView removeFromSuperview];
NSLog(#"Should remove activity view here");
}
In my OnTVXMLParser class I have:
- (BOOL)parse{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"Sleeping for 5 seconds");
[NSThread sleepForTimeInterval:5.0];
NSLog(#"Sleep finished");
// Simulated some elapsed time. I want to remove the Activity View
[self performSelectorOnMainThread:#selector(removeActivityView) withObject:nil waitUntilDone:false];
// Create and initialize an NSURL with the RSS feed address and use it to instantiate NSXMLParser
NSURL *url = [[NSURL alloc] initWithString:#"http://aurl.com/xml"];
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
// Lots of parsing stuff snipped, this all runs fine
[pool release];
return YES;
}
Basically once the "parse" method on the XMLParser class has finished I want to call the removeActivityIndicator on the OnTVViewController object. It's probably really simple but I am new to iPhone programming and banging my head against the wall.
I understand I need to use performSelectorOnMainThread - but how do I reference the instance of OnTVViewController I want to target? I've imported the OnTVViewController header file into OnTVXMLParser.
At the moment I get the error:
-[OnTVViewController removeActivityView:]: unrecognized selector sent to instance 0x8840ba0'

Typically cocoa handles this with a delegate pattern. Basically add an ivar to the XML parser named delegate, and launching the parade set the delegate to self (the OnTVViewController), and then later use the delegate for all callbacks in the XML parser

The problem is that the selector called:
-[OnTVViewController removeActivityView:]: unrecognized selector sent to instance 0x8840ba0'
does not exist. You tell it to call this selector:
[self performSelectorOnMainThread:#selector(removeActivityView) withObject:nil waitUntilDone:false];
Which doesn't exist (note the :), because your method is named:
- (void)removeActivityView
Try calling it
- (void)removeActivityView:(id)dummy
and see what happens.

You might also want to consider using NSNotificationCenter. This allows you to add an observer for a given key (a string) and selector, then post a notification from other parts of your application.
Examples:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(methodToCall:) name:#"SomeKeyName" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomeKeyName" object:nil];

Related

webview delegate method is not fired in background process

I am developing one application.In that i am using my static library.And i run the app in background using below code
-(IBAction)sendKeyValuePair:(id)sender
{
[NSThread detachNewThreadSelector:#selector(startTheBackgroundJob) toTarget:self withObject:nil];
}
-(void)startTheBackgroundJob
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//create the object for library class and do something and release that object.
[pool release];
}
And int that library class i created one webview object and add that webview to my main class like below
web=[[UIWebView alloc]init];
//web.delegate=self;
web.frame=CGRectMake(1, 1, 100,100);
[web loadHTMLString:html_str baseURL:nil];
[main_View.view addSubview:web];
[html_str release];
[web release];
Here my problem is if i set the delegate as self then app is crashing.And if i didn't set then delegate methods are not firing.And delegate methods are implemented in library class only.I want to set the delegate as self and run that delegate methods in library class.How to do this one.
Add this
[web setDelegate:Your library class name instance];

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.

Update UILabel from applicationDidBecomeActive?

I want to update UILabel by clicking a reload button. Additionally, I want to update the label in background, because it is fetching the new data via XML from my website. Of course it would be nice to auto-update the label when the application is opened. And there is my problem:
I was able to make it work well when user were clicking the button manually. But I don't understand how to do the same by calling my method via "applicationDidBecomeActive". I tried to do it the same way, but it obviously doesn't work because my label is returned nil.
I suppose there is a problem of my understanding and the solution should be quite easy. Thanks for your input! Note: I am a beginner with Objective-C and have sometimes problems with "easy" things. ;-)
Below is a summary of the important code parts:
AppDelegate
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[MyViewController alloc] reloadButtonAction];
}
MyViewController
#synthesize label
- (void)reloadButtonAction {
[self performSelectorInBackground:#selector(updateData) withObject:nil];
}
- (void)updateData {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Parse the XML File and save the data via NSUserDefaults
[[XMLParser alloc] parseXMLFileAtURL];
// Update the labels
[self performSelectorOnMainThread:#selector(updateLabels) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)updateLabels {
NSUserDefaults *variable = [NSUserDefaults standardUserDefaults];
myLabel.text = [variable stringForKey:#"myLabelText"];
// myLabel is nil when calling all of this via AppDelegate
// so no changes to the myLabel are done in that case
// but: it works perfectly when called via button selector (see below)
NSLog(#"%#",myLabel.text);
}
-(void)viewDidLoad {
// Reload button in the center
UIButton *reloadButton = [UIButton buttonWithType:UIBarButtonSystemItemRefresh];
reloadButton.frame = CGRectMake(145,75,30,30);
[reloadButton setTitle:#"" forState:UIControlStateNormal];
[reloadButton addTarget:self action:#selector(reloadButtonAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:reloadButton];
}
First:
[[MyViewController alloc] reloadButtonAction];
Doesn't make sense. You allocate memory, without initializing an object. And then you want to call a method on it. Doesn't work
Use an instance for it:
[myViewControllerInstance reloadButtonAction];
In your app delegate you should have an reference to your rootcontroller instance if that is the object contains the reload method, use that instance.
Note:
Alloc only reserves space in the memory for an object which size the size of MyViewController instance. An init method will fill it.

encountering numerous leaks on iphone device when using NSOperationQueue and trying to change sliders / pickers etc

encountering numerous leaks on iphone device when using NSOperationQueue and trying to change sliders / pickers etc.
I am able to change labels without an issue, but if I try to change a slider or picker both created on interface builder I get these leaks.
Leaked Object # Address Size Responsible Library Responsible Frame
GeneralBlock-16 0x1b00a0 16 GraphicsServices GetFontNames
GeneralBlock-16 0x1aea90 16 WebCore WebThreadCurrentContext
GeneralBlock-16 0x1aea80 16 GraphicsServices GSFontGetFamilyName
GeneralBlock-64 0x1a7370 64 UIKit GetContextStack
code below
- (void)loadData {
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(firstRun)
object:nil];
[queue_ addOperation:operation];
[operation release];
}
- (void)firstRun {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
[self setSliders];
NSLog(#"firstRun method end");
[pool drain];
}
- (void)setSliders {
NSMutableArray *tempArray = [[[NSMutableArray alloc]init] autorelease];
aquaplannerAppDelegate *appDelegate = (aquaplannerAppDelegate *)[[UIApplication sharedApplication] delegate];
tempArray = appDelegate.settingsValuesArray;
freshMarineSegment.selectedSegmentIndex = [[tempArray objectAtIndex:0]intValue];
for (int i = 1; i <= 20; i++ ) {
UILabel *label = (UILabel *)[self.view viewWithTag:200+i]; // gets label based on tag
UISlider *slider = (UISlider *)[self.view viewWithTag:100+i]; // gets slider based on tag
slider.value = [[tempArray objectAtIndex:i]intValue];
label.text = [[[NSString alloc] initWithFormat:#"%#",[tempArray objectAtIndex:i]] autorelease];
[label release];
[slider release];
}
}
I'm assuming you're doing something else before setSliders for which you created the NSOperation, and you just omitted that code.
UIKit is not guaranteed to be thread safe, and you should only access your interface elements on the main thread. This is mentioned in a few places in the docs, but the most telling is in the Cocoa Fundamentals Guide:
All UIKit objects should be used on the main thread only.
So firstRun should look more like this:
- (void)firstRun {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
// Do something important here...
[self performSelectorOnMainThread:#selector(setSliders) withObject:nil waitUntilDone:NO];
NSLog(#"firstRun method end");
[pool drain];
}
Why are you using an NSMutableArray in setSliders? You're never actually changing the array, and mutable data structures can wreak havoc in threaded programming.
Also, I'd rename the setSliders method to something like updateSliders. It's a Cocoa style issue. Methods that start with "set" should be used for mutating a single instance variable/property.
You are releasing each label and slider in your for loop, even though you have not retained them. This is not correct. You only need to release memory that you have allocated, copied or retained. See the memory management programming guide for more details.
I would also change the way you initialize your temporary array. The designated initializer for NSMutableArray is -initWithCapacity:, not -init. There is also a class method designed to return an autoreleased instance for convenience:
NSMutableArray *tempArray = [NSMutableArray arrayWithCapcity:0];

Why does my UIActivityIndicatorView only display once?

I'm developing an iPhone app that features a tab-bar based navigation with five tabs. Each tab contains a UITableView whose data is retrieved remotely. Ideally, I would like to use a single UIActivityIndicatorView (a subview of the window) that is started/stopped during this remote retrieval - once per tab.
Here's how I set up the spinner in the AppDelegate:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[window addSubview:rootController.view];
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[activityIndicator setCenter:CGPointMake(160, 200)];
[window addSubview:activityIndicator];
[window makeKeyAndVisible];
}
Since my tabs were all performing a similiar function, I created a base class that all of my tabs' ViewControllers inherit from. Here is the method I'm using to do the remote retrieval:
- (void)parseXMLFileAtURL:(NSString *)URL {
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSLog(#"parseXMLFileAtURL started.");
[appDelegate.activityIndicator startAnimating];
NSLog(#"appDelegate.activityIndicator: %#", appDelegate.activityIndicator);
articles = [[NSMutableArray alloc] init];
NSURL *xmlURL = [NSURL URLWithString:URL];
rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
[rssParser setDelegate:self];
[rssParser setShouldProcessNamespaces:NO];
[rssParser setShouldReportNamespacePrefixes:NO];
[rssParser setShouldResolveExternalEntities:NO];
[rssParser parse];
NSLog(#"parseXMLFileAtURL finished.");
[appDelegate.activityIndicator stopAnimating];
[apool release];
}
This method is being called by each view controller as follows:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([articles count] == 0) {
NSString *path = #"http://www.myproject.com/rss1.xml";
[self performSelectorInBackground:#selector(parseXMLFileAtURL:) withObject:path];
}
}
This works great while the application loads the first tab's content. I'm presented with the empty table and the spinner. As soon as the content loads, the spinner goes away.
Strangely, when I click the second tab, the NSLog messages from the -parseXMLFileAtURL: method show up in the log, but the screen hangs on the first tab's view and I do not see the spinner. As soon as the content is done downloading, the second tab's view appears.
I suspect this has something to do with threading, with which I'm still becoming acquainted. Am I doing something obviously wrong here?
You should perform all actions on the activity indicator on the main thread using:
performSelectorOnMainThread:withObject:waitUntilDone: