views:

637

answers:

4

I have a tableView that loads an XML feed as follows:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    if ([stories count] == 0) {
        NSString *path = @"http://myurl.com/file.xml";
        [self parseXMLFileAtURL:path];
    }
}

I'd like to have the spinner to show on the top bar at the app launch and dissapear once the data is displayed on my tableView.

I thought that putting the beginning at viewDidAppear and the end at -(void)parserDidEndDocument:(NSXMLParser *)parser but it didn't work.

I'd appreciate a good explained solution on how to implement this solution.

+2  A: 

I typically implement an NSTimer that will call my spinner method, which I fire off right before I go into doing the heavy work (the work that will typically block the main thread).

The NSTimer fires and my spinner method is called. When the main work is finished, I disable the spinner.

Code for that is like:

IBOutlet UIActiviyIndicatorView *loginIndicator;

{
    ...
    [loginIndicator startAnimating];

    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(executeAuthenticationRequest) 
                                   userInfo:nil repeats:NO];   
    ...
}

- (void) executeAuthenticationRequest
{
    /* Simulate doing a network call. */
    sleep(3);

    [loginIndicator stopAnimating];

    ...
}

You can also do:

IBOutlet NSProgressIndicator *pIndicator;

Start:

[pIndicator startAnimation:self];
[pIndicator setHidden:NO];

And Stop:

[pIndicator stopAnimation:self];
[pIndicator setHidden:YES];
Mr-sk
If you schedule a timer and then block the main thread, the timer won't be fired. Or are you saying you put the blocking work in the timerDidFire: method?
benzado
The sleep(3) call is where I'm pretending to be doing the work.
Mr-sk
A: 

In Cocoa (and most other app frameworks) the user interface is updated by the main thread. When you manipulate views, they are typically not redrawn until control returns to the run loop and the screen is updated.

Because you are parsing the XML in the main thread, you are not allowing the screen to update, and that is why your activity indicator is not appearing.

You should be able to fix it by doing the following:

  1. In viewDidAppear, show/animate the spinner and then call

    [self performSelector:@selector(myXMLParsingMethod) withObject:nil afterDelay:0];

  2. In myXMLParsingMethod, parse your XML, then hide/stop the spinner.

This way, control will return to the run loop before parsing begins, to allow the spinner to begin animating.

benzado
+4  A: 

Here's the problem: NSXMLParser is a synchronous API. That means that as soon as you call parse on your NSXMLParser, that thread is going to be totally stuck parsing xml, which means no UI updates.

Here's how I usually solve this:

- (void) startThingsUp {
  //put the spinner onto the screen
  //start the spinner animating

  NSString *path = @"http://myurl.com/file.xml";
  [self performSelectorInBackground:@selector(parseXMLFileAtURL:) withObject:path];
}

- (void) parseXMLFileAtURL:(NSString *)path {
  //do stuff
  [xmlParser parse];
  [self performSelectorOnMainThread:@selector(doneParsing) withObject:nil waitUntilDone:NO];
}

- (void) doneParsing {
  //stop the spinner
  //remove it from the screen
}

I've used this method many times, and it works beautifully.

Dave DeLong
Thank you! this is exactly what I needed.
Cy.
+2  A: 

Starting a new thread can be overkilling and a source of complexity if you want to do things that are supposed to start on the main thread.

In my own code, I need to start a MailComposer by pushing a button but it can take some time to appear and I want to make sure the UIActivityIndicator is spinning meanwhile.

This is what I do :

-(void)submit_Clicked:(id)event { [self.spinner startAnimating]; [self performSelector:@selector(displayComposerSheet) withObject:nil afterDelay:0]; }

It will queue displayComposerSheet instead of executing it straight away. Enough for the spinner to start animating !

Xav