UITableView reloadData causing all animations to stop working - iphone

Alright this is a really weird one so I am going to layout what is happening and then give some code after. For my example I am going to use a static amount of views, 2.
The Basics
I have a UIPageControl with X many Subviews added. On each subviews viewDidLoad is an NSXMLParse to grab an XML feed. Once the feed is obtained, it's parsed and the table is reloaded using the parsed array. There is also a Settings button on each view. When the Settings button is pressed, UIModalTransitionStyleCoverVertical:Animated:YES is run and a UINavigationController slides up into view with full animation. Dismiss also shows animation sliding out back to the previous view. If you are in Settings, you can PushViews two levels deep (Slide In Animation).
The Problem
A random amount of the time, when the app is built and run (Not Resumed) when you tap the Settings button, the Animation does not occur. Everything is functional except for all Core Animations are removed. DismissModal simply swaps back to the previous screen. PushView in the NavigationController no longer has any animation, the next view simply appears.
If you quit the app (Kill Process) and relaunch it, it may work fine for a period of time but at some point when you tap the Settings button, it will lose all animations.
The Details
I started with Apples PageControl Application for the groundwork. It creates a dynamic amount of views based on user settings.
- (void)awakeFromNib
{
kNumberOfPages = 2;
// view controllers are created lazily
// in the meantime, load the array with placeholders which will be replaced on demand
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (int i = 0; i < kNumberOfPages; i++)
{
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
// a page is the width of the scroll view
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.scrollsToTop = NO;
scrollView.delegate = self;
pageControl.numberOfPages = kNumberOfPages;
pageControl.currentPage = 0;
// pages are created on demand
// load the visible page
// load the page on either side to avoid flashes when the user starts scrolling
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
- (void)loadScrollViewWithPage:(int)page
{
if (page < 0)
return;
if (page >= kNumberOfPages)
return;
// replace the placeholder if necessary
SecondViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
controller = [[SecondViewController alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// add the controller's view to the scroll view
if (controller.view.superview == nil)
{
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
As each view is generated, it runs an NSXMLParse in its viewDidLoad. Everything works fine up to this point. Both views are generated and you can swipe between them.
If you push the Settings Button
- (IBAction)settingsButtonPressed:(id)sender;
{
SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:settingsViewController];
navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:navigationController animated:YES];
[settingsViewController release];
[navigationController release];
}
At this point, SettingsViewController appears into view. However, sometimes it slides up with its proper animation. Other times it will simply appear and all further core animations are broken until the process is restarted.
I went through and checked all of NSXMLParse and have narrowed down the problem to one line. On each of my subviews, is a tableView, after the XML Parsing is done, I created an array with the results and ran [self.tableview reloadData]. If I comment out that line, the table obviously only loads blank but it doesn't have any issues with Animations.
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSMutableArray *tableData = ARRAY_GENERATED_HERE;
[self.tableView reloadData];
}
My Testing
I will note from my tests, everything is fine if kNumberOfPages is set to 1 instead of 2. Only 1 view gets generated, the Animation glitch never occurs. Add a second view in, usually within opening Settings five times, it will glitch.
Still haven't come to a solution but it has to do with [tableView reloadData]. Any insight would be great.
Daniel pointed out something that makes sense.
My XML is fetched in the viewDidLoad using:
[NSThread detachNewThreadSelector:#selector(parseXMLFileAtURL:) toTarget:self withObject:path];
- (void)parseXMLFileAtURL:(NSString *)URL
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
stories = [[NSMutableArray alloc] init];
//you must then convert the path to a proper NSURL or it won't work
NSURL *xmlURL = [NSURL URLWithString:URL];
// here, for some reason you have to use NSClassFromString when trying to alloc NSXMLParser, otherwise you will get an object not found error
// this may be necessary only for the toolchain
rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[rssParser setDelegate:self];
// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[rssParser setShouldProcessNamespaces:NO];
[rssParser setShouldReportNamespacePrefixes:NO];
[rssParser setShouldResolveExternalEntities:NO];
[rssParser parse];
[pool release];
}

From your comment, you said you are running the parser in a background thread...UIKit is not thread safe and i suspect that is whats causing your problems...try making the reloadData call on the main thread, you can use NSObjects performSelectorInMainThread to do this...
[self performSelectorOnMainThread:#selector(operationComplete) withObject:nil waitUntilDone:false];

Related

MBProgress HUD crashing application on reloadData on tableView

I am using this piece of code to display an MBProgressHUD over the top of one of my views while I download data from a web-service, the only problem is that occasionally this code will cause the app to hang, doing nothing while the HUD displays "Downloading" and the screen is locked. Also, if I have something like a keyboard being displayed to the user when I press the refresh button (refresh button performs the download) then the application crashes on the line:
[self.tableView reloadData];
My code:
//Checks for network connection then displays HUD while executing pullAndDisplayData method
- (IBAction) update {
UIAlertView *errorView;
if([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] == NotReachable) {
errorView = [[UIAlertView alloc]
initWithTitle: #"Network Error"
message: #"No Network connection availible!"
delegate: self
cancelButtonTitle: #"OK" otherButtonTitles: nil];
[errorView show];
}
else
{
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Downloading";
HUD.minSize = CGSizeMake(135.f, 135.f);
[HUD showWhileExecuting:#selector(pullAndDisplayData) onTarget:self withObject:nil animated:YES];
}
}
//Downloads this users data from the web-service
- (void) pullAndDisplayData{
// Indeterminate mode
ExpensesDataDownloader *downloader = [[ExpensesDataDownloader alloc] init];
[downloader pullAndDisplayData];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([[defaults objectForKey:#"canExportCSVServer"] isEqualToString:#"1"])
{
}
[self.tableView reloadData];
// Switch to determinate mode
HUD.mode = MBProgressHUDModeDeterminate;
HUD.labelText = #"Updating";
float progress = 0.0f;
while (progress < 1.0f)
{
progress += 0.01f;
HUD.progress = progress;
usleep(15000);
}
// The sample image is based on the work by www.pixelpressicons.com, http://creativecommons.org/licenses/by/2.5/ca/
// Make the customViews 37 by 37 pixels for best results (those are the bounds of the build-in progress indicators)
HUD.customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"37x-Checkmark.png"]];
HUD.mode = MBProgressHUDModeCustomView;
HUD.labelText = #"Completed";
sleep(2);
}
Any help would be much appreciated.
Jack
pullAndDisplayData method is running on a separate thread. This is so that MBProgressHUD can use UI thread to show itself. You should always update your UI from main (UI) thread. Use performSelectorOnMainThread: method to call [self.tableView reloadData]; and other UI stuff. I am assuming that [downloader pullAndDisplayData]; is synchronous call.
From MBprogressHUD API
/**
* Shows the HUD while a background task is executing in a new thread, then hides the HUD.
*
* This method also takes care of autorelease pools so your method does not have to be concerned with setting up a
* pool.
*
* #param method The method to be executed while the HUD is shown. This method will be executed in a new thread.
* #param target The object that the target method belongs to.
* #param object An optional object to be passed to the method.
* #param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will not use
* animations while (dis)appearing.
*/
- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated;
Since you are using this method, your pullAndDisplayData is executed in a new thread. This could cause the strange problem you have (I suppose). You are updating the UI elements from a background thread and this is not good. UI elements would be updated form the main thread. Use the background thread to downlaod data only.
Insetad of using it, try to use GCD (Grand Central Dispatch)
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// download operation here...
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
// reload data here...
});
});
For further info see Usage section for the MBProgressHUD.
The problem might be occurring due to some memory issue, You try using SVProgressHUD for this, it is the extended version of MBProgressHUD:
You just need to do like this :
- (void) pullAndDisplayData{
[SVProgressHUD showWithStatus:#"Downloading..."];
// Indeterminate mode
ExpensesDataDownloader *downloader = [[ExpensesDataDownloader alloc] init];
[downloader pullAndDisplayData];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([[defaults objectForKey:#"canExportCSVServer"] isEqualToString:#"1"])
{
}
[self.tableView reloadData];
// Switch to determinate mode
HUD.mode = MBProgressHUDModeDeterminate;
HUD.labelText = #"Updating";
float progress = 0.0f;
while (progress < 1.0f)
{
progress += 0.01f;
HUD.progress = progress;
usleep(15000);
}
// The sample image is based on the work by www.pixelpressicons.com, http://creativecommons.org/licenses/by/2.5/ca/
// Make the customViews 37 by 37 pixels for best results (those are the bounds of the build-in progress indicators)
HUD.customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"37x-Checkmark.png"]];
HUD.mode = MBProgressHUDModeCustomView;
HUD.labelText = #"Completed";
sleep(2);
[SVProgressHUD dismiss];
}
No need to allocate or release anything. It works just like that!!!
All the best!!!

Adding a UIActivityIndicator to a modal view (ELCimagepicker)

I've added the ELCimagepicker (https://github.com/Fingertips/ELCImagePickerController) to my project and it works perfectly, allowing the user to select multiple images for a slideshow. But when you click 'Save', there can be a lengthy delay depending on how many photos were added.
I've been trying to add a UIActivityIndicator when the user clicks 'Save', but having trouble due to the modal view that is presented. I can call a method from the activity that ELCimagepicker presents (ELCImagePickerController) and this gets actioned by the activity handling the presenting of the image picker. But whenever I try to add to the view, it isn't shown as the modal is on top of the activity indicator.
I've tried using bringSubviewToFront, I've tried adding the code directly to the imagepicker method file with [[self parentViewController] addSubView], but no luck.
Here's the latest code I tried: (indicator is declared in the .h file as UIActivityIndicator *indicator)
indicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
indicator.hidden=false;
[self.navigationController.view addSubview:self.indicator];
[self.navigationController.view bringSubviewToFront:self.indicator];
[indicator startAnimating];
if([delegate respondsToSelector:#selector(elcImagePickerController:showIndicator:)]) {
[delegate performSelector:#selector(elcImagePickerController:showIndicator:) withObject:self withObject:#"test"];
}
Has anyone had any success with adding a UIActivityIndicator on top of the ELCimagepicker, or another modal view handled by another class?
I've tried MBProgressHUD but couldn't get that working quite right either - it would show up when I used it in the ELCimagepicker class, but crashed on removal with:
bool _WebTryThreadLock(bool), 0x42368e0: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
Any help would be fantastic.
Thanks.
I have figure out your problem. You can do this as below..
-(void)selectedAssets:(NSArray*)_assets {
UIActivityIndicatorView * activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
UIViewController * topView = [self.viewControllers objectAtIndex:[self.viewControllers count]-1];
activityIndicator.center = CGPointMake(topView.view.frame.size.width/2, topView.view.frame.size.height/2);
[activityIndicator setHidden:NO];
[topView.view addSubview:activityIndicator];
[topView.view bringSubviewToFront:activityIndicator];
[activityIndicator startAnimating];
[self performSelector:#selector(doProcess:) withObject:_assets afterDelay:2.1];
}
- (void) doProcess:(NSArray *)_assets {
NSMutableArray *returnArray = [[[NSMutableArray alloc] init] autorelease];
for(ALAsset *asset in _assets) {
NSMutableDictionary *workingDictionary = [[NSMutableDictionary alloc] init];
[workingDictionary setObject:[asset valueForProperty:ALAssetPropertyType] forKey:#"UIImagePickerControllerMediaType"];
[workingDictionary setObject:[UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage]] forKey:#"UIImagePickerControllerOriginalImage"];
[workingDictionary setObject:[[asset valueForProperty:ALAssetPropertyURLs] valueForKey:[[[asset valueForProperty:ALAssetPropertyURLs] allKeys] objectAtIndex:0]] forKey:#"UIImagePickerControllerReferenceURL"];
[returnArray addObject:workingDictionary];
[workingDictionary release];
}
[self popToRootViewControllerAnimated:NO];
[[self parentViewController] dismissModalViewControllerAnimated:YES];
if([delegate respondsToSelector:#selector(elcImagePickerController:didFinishPickingMediaWithInfo:)]) {
[delegate performSelector:#selector(elcImagePickerController:didFinishPickingMediaWithInfo:) withObject:self withObject:[NSArray arrayWithArray:returnArray]];
}
}
Let me know if this answer help you ...
Thanks,
MinuMaster
It looks like you are updating UI on a background thread. All UIKit updates are to be done in the main thread. So I recommend you execute methods doing UI updates using performSelectorOnMainThread:withObject:.
I solved the issue like this
activityIndicatorObject = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
// Set Center Position for ActivityIndicator
activityIndicatorObject.center = CGPointMake(150, 250);
activityIndicatorObject.backgroundColor=[UIColor grayColor];
// Add ActivityIndicator to your view
[self.view addSubview:activityIndicatorObject];
activityIndicatorObject.hidesWhenStopped=NO;
[activityIndicatorObject startAnimating];

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.

Possible to explain code flow in this example ?? Memory leak? Where?

Referring to this PageControl example could somebody please explain the code flow? Instruments is giving me a leak here so looking for some help.
Re: this tutorial:
http://www.edumobile.org/iphone/iphone-programming-tutorials/pagecontrol-example-in-iphone/
We init an array to Null objects in our AppDidFinishLaunching method...
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < kNumberOfPages; i++) {
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
and then call:
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
Here is the implementation for loadScrollViewWithPage:
- (void)loadScrollViewWithPage:(int)page {
if (page < 0) return;
if (page >= kNumberOfPages) return;
PageControlExampleViewControl *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[PageControlExampleViewControl alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
if (nil == controller.view.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
Instruments is giving me a leak in this implementation on the following line:
if (nil == controller.view.superview) {
Anyone know why this would be a reported leak in Instruments? My code is identical.
Also after the initial call [self loadScrollViewWithPage:0];, on the first pass through and creating the object, BOTH if clauses are passed and entered.
How is this possible? If we enter the first if clause, we alloc and create our controller and end by **releasing* it ([controller release]).
Shouldn't the next line (if (nil == controller.view.superview)) produce an EXC_BAD_ACCESS error seeing as we just RELEASED controller above?
SCREENSHOT FROM INSTRUMENTS:
I don't know why Instruments would be reporting a leak on that line, unless it's just noticing that controller.view was allocated by that line (accessing a UIViewController's view property automatically loads the view if necessary) and not yet freed (which it shouldn't be as long as scrollView exists and controller.view remains as a subview of it).
It is correct that it goes through both if clauses. The first if checks whether a view controller actually exists for that page index, and if not it creates one (but does not add it to the scrollView). The second checks if the view for the view controller for the page index has already been added to the scrollView, and if not it adds it.
The reason it does not crash is because [viewControllers replaceObjectAtIndex:page withObject:controller] adds the controller to an NSMutableArray, which retains the controller. It might be slightly less confusing to do it like this instead:
if ((NSNull *)controller == [NSNull null]) {
controller = [[[PageControlExampleViewControl alloc] initWithPageNumber:page] autorelease];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
It seems to me that you are not properly releasing the scrollView.
How is this possible? If we enter the first if clause, we alloc and create our controller and end by *releasing it ([controller release]).
Shouldn't the next line (if (nil == controller.view.superview)) produce an EXC_BAD_ACCESS error seeing as we just RELEASED controller above?
look at the line between alloc and release.
[viewControllers replaceObjectAtIndex:page withObject:controller];
the viewControllers array will retain the controller.
But imho it's no good code. for exactly the reason you've stated. Not very clear at first sight.

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: