How to update progress bar in Cocoa during a long cycle?

I have a loop whilethat runs for many seconds, and therefore I want to update the progress bar (NSProgressIndicator) during this process, but it only updates once after the loop finishes. The same thing happens if I want to update the label text, by the way.

I believe my loop is interfering with other things in this app. There must be a different technique. Is it related to threads or something else? Am I on the right track? Can someone please give me a simple example how to "optimize" my application?

My application is a Cocoa application (Xcode 3.2.1) with these two methods in my Example_AppDelegate.m:

// This method runs when a start button is clicked.
- (IBAction) startIt: (id) sender {
    [progressbar setDoubleValue: 0.0];
    [progressbar startAnimation: sender];
    running = YES; // this is a instance variable

    int i = 0;
    while (running) {
        if (i ++> = processAmount) {// processAmount is something like 1000000
            running = NO;
            continue;
        }

        // Update progress bar
        double progr = (double) i / (double) processAmount;
        NSLog (@ "progr:% f", progr); // Logs values ​​between 0.0 and 1.0
        [progressbar setDoubleValue: progr];
        [progressbar needsDisplay]; // Do I need this?

        // Do some more hard work here ...
    }
}

// This method runs when a stop button is clicked, but as long
// as -startIt is busy, a click on the stop button does nothing.
- (IBAction) stopIt: (id) sender {
    NSLog (@ "Stop it!");
    running = NO;
    [progressbar stopAnimation: sender];
}

I am really new to Objective-C, Cocoa, and UI applications. Thanks so much for any helpful answer.

+5
source share
4 answers

If you are building Snow Leopard, the easiest solution, in my opinion, is to use blocks and Grand Central Dispatch.

The following code shows what your method looks like startIt:when using a GCD.

stopIt: , . , , , , , , , . , GCD. , , , , .

// This method runs when a start button is clicked.
- (IBAction)startIt:(id)sender {

    //Create the block that we wish to run on a different thread.
    void (^progressBlock)(void);
    progressBlock = ^{

    [progressbar setDoubleValue:0.0];
    [progressbar startAnimation:sender];
    running = YES; // this is a instance variable

    int i = 0;
    while (running) {
        if (i++ >= processAmount) { // processAmount is something like 1000000
            running = NO;
            continue;
        }

        // Update progress bar
        double progr = (double)i / (double)processAmount;
        NSLog(@"progr: %f", progr); // Logs values between 0.0 and 1.0

        //NOTE: It is important to let all UI updates occur on the main thread,
        //so we put the following UI updates on the main queue.
        dispatch_async(dispatch_get_main_queue(), ^{
            [progressbar setDoubleValue:progr];
            [progressbar setNeedsDisplay:YES];
        });

        // Do some more hard work here...
    }

    }; //end of progressBlock

    //Finally, run the block on a different thread.
    dispatch_queue_t queue = dispatch_get_global_queue(0,0);
    dispatch_async(queue,progressBlock);
}
+15

.

[progressbar setUsesThreadedAnimation:YES];
+3

, .

. - .

- , . NSOperation () NSOperationQueue.

- ?

. NSOperations , NSOperationQueue . : , . - .

. Cocoa.

+2

This worked for me, which is a combination of answers from others that don't seem to work (at least for me):

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
  //do something
  dispatch_async(dispatch_get_main_queue(), ^{
    progressBar.progress = (double)x / (double)[stockList count];            
  });  
  //do something else
});
+2
source

All Articles