views:

638

answers:

3

I am able to download a ZIP file from the internet. Post processing is done in connectionDidFinishLoading and works OK except no UIView elements are updated. For example, I set statusUpdate.text = @"Uncompressing file" but that change does not appear until after connectionDidFinishLoading has completed. Similarly, the UIProgressView and UIActivityIndicatorView objects are not updated until this method ends.

Is there any way to force an update of the UIView from within this method? I tried setting [self.view setNeedsDisplay] but that didn't work. It appears to be running in the main thread. All other commands here work just fine - the only problem is updating the UI.

Thanks!

Update: here is the code that is NOT updating the UIVIEW:

-(void)viewWillAppear:(BOOL)animated {
    timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(processUpdate:) userInfo:nil repeats:YES];
    downloadComplete = NO;
    statusText.text = @"";

}

-(void)processUpdate:(NSTimer *)theTimer {
    if (! downloadComplete) {
        return;
    }

    [timer invalidate];
    statusText.text = @"Processing update file."; 
    progress.progress = 0.0;
        totalFiles = [newFiles count];
    for (id fileName in newFiles) {
        count++;
            progress.progress = (float)count / (float)totalFiles;
        // ... process code goes here ...
         }
}

At then end of processUpdate, I set downloadComplete = YES. This builds & runs without errors and works as intended except nothing updates in the UIVIEW until after processUpdate completes, then everything updates at once.

Thanks for your help so far!

A: 

While you are in connectionDidFinishLoading nothing else happens in the application run loop. Control needs to be passed back to the run loop so it can orchestrate the UI updating.

Just flag the data transfer as complete and the views for updating. Defer any heavy processing of the downloaded data to it's own thread.

The application will call your views back letting them refresh their contents later in the run loop. Implement drawRect on your own custom views as appropriate.

Niels Castle
A: 

If you're receiving connectionDidFinishLoading in the main thread, you're out of luck. Unless you return from this method, nothing will be refreshed in the UI.

On the other hand, if you run the connection in a separate thread, then you can safely update the UI using the following code:

UIProgressView *prog = ... <your progress view reference> ...
[prog performSelectorOnMainThread:@selector(setProgress:)
                       withObject:[NSNumber numberWithFloat:0.5f]
                    waitUntilDone:NO];

Be careful not to update the UI from a non-main thread - always use the performSelectorOnMainThread method!

Adam Woś
A: 

As Niels said, you must return control to the run loop if you want to see views update. But don't start detaching new threads unless you really need to. I recommend this approach:

- (void)connectionDidFinishLoading:(NSConnection *)connection {
    statusUpdate.text = @"Uncompressing file";
    [self performSelector:@selector(doUncompress) withObject:nil afterDelay:0];
}

- (void)doUncompress {
    // Do work in 100 ms chunks
    BOOL isFinished = NO;
    NSDate *breakTime = [NSDate dateWithTimeIntervalSinceNow:100];
    while (!isFinished && [breakTime timeIntervalSinceNow] > 0) {
        // do some work
    }
    if (! isFinished) {
        statusUpdate.text = // here you could update with % complete
        // better yet, update a progress bar
        [self performSelector:@selector(doUncompress) withObject:nil afterDelay:0];
    } else {
        statusUpdate.text = @"Done!";
        // clean up
    }
}

The basic idea is that you do work in small chunks. You return from your method to allow the run loop to execute periodically. The calls to performSelector: will ensure that control eventually comes back to your object.

Note that a risk of doing this is that a user could press a button or interact with the UI in some way that you might not expect. It may be helpful to call UIApplication's beginIgnoringInteractionEvents to ignore input while you're working... unless you want to be really nice and offer a cancel button that sets a flag that you check in your doUncompress method...

You could also try running the run loop yourself, calling [[NSRunLoop currentRunLoop] runUntilDate:...] every so often, but I've never tried that in my own code.

benzado
waltcrit
I had the loop condition wrong (should have been `> 0` instead of `< 0`). I fixed my answer; maybe that is what's wrong with your code? Otherwise I don't have enough info to tell.
benzado
This works, thank you!
waltcrit
You're welcome. Please take a moment to up-vote the useful answers and mark one of them as accepted (the green checkmark).
benzado
My apologies, I was wrong, this still isn't working. (My update file had nothing that needed updating and so was exiting too quickly.) I set up a timer in the main loop to run processUpdate if the download is complete. I checked and it is running in the main thread but it still doesn't update the UI. Again, this is after the download is complete. Everything else works, just not the UI update (progress bar, textfield text, etc). I even wrote a dummy routine to update a fake progressbar and call it from several different places without success. Any other thoughts?
waltcrit
Hard to say without code. Consider editing your question and include the source of the dummy download that isn't working.
benzado