connectionDidFinishLoading - how to force update U

2019-03-22 04:02发布

问题:

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!

回答1:

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.



回答2:

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.



回答3:

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!



回答4:

Do exactly what you're doing with the timer, just dispatch your processing code to a new thread with ConnectionDidFinish:. Timers can update the UI since they're run from the main thread.



回答5:

The problem turned out to that the UI isn't updated in a for() loop. See the answer in this thread for a simple solution!