4

I want to illustrate the progress on MBProgressHUD item, but when i triger this method :

- (IBAction)signInBttn:(id)sender {

    MBProgressHUD *hudd = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
    hudd.mode = MBProgressHUDModeAnnularDeterminate;
    hudd.labelText = @"Loading";

    __block float value = 0;
    for (int j = 0; j<2000; j++) {
        dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            for (int i = 0; i<20000 ; i++) {

            }
            value += 0.001;
            dispatch_async( dispatch_get_main_queue(), ^{
                hudd.progress = value;
            });

        });
    }        
}

hud appears fully to 100%. This is only for my information, I dont have idea how to create background task which calculate something and when he done with e.g. 40% the HUD is refreshing to 40% of his progress. I hope I made ​​myself clear, and if anyone has time to help improve my code, thanks a lot for any answers

user3163231
  • 115
  • 1
  • 5
  • Same underlying issue as [Dynamically updating a UILabel](http://stackoverflow.com/q/6336991/) The UI doesn't get redrawn until control returns to the run loop. The correct way to do this is with a timer of some kind. – jscs Jan 15 '14 at 19:41
  • Is the empty `for` loop supposed to be a "pause"? The compiler is very likely optimizing that right out of existence. – jscs Jan 15 '14 at 19:42
  • yes i want to in simple way simulate advanced calculation – user3163231 Jan 15 '14 at 19:44
  • This is very important for my to use GCD in this code – user3163231 Jan 15 '14 at 19:47
  • use `sleep(seconds);` if you want the program to pause, stick that in the for loop with the dispatch_get_main_queue and that will update it every x seconds – Fonix Jan 15 '14 at 19:47
  • Sleeping the main thread is a great way to get your app terminated by the system, @Fonix. – jscs Jan 15 '14 at 19:48
  • i didnt mean in the main thread, i meant along side the dispatch_get_main_queue() in a forloop – Fonix Jan 15 '14 at 19:49
  • 1
    @Fonix you are genious ! it work I have repleced empty `for` to `sleep`.!!! thanks – user3163231 Jan 15 '14 at 19:49
  • alternatively you can use `usleep(microseconds);` for a more refined sleep – Fonix Jan 15 '14 at 19:53

2 Answers2

3

In this case, you can solve the problem by decoupling the updating of the counter from the updating of your HUD in your UI. Apple refers to this as "updating the state asynchronously" in WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC.

Generally this isn't necessary (most of the time the stuff we're doing asynchronously is slow enough that we don't have problems), but if doing something that is running faster than the UI can hope to keep up with, you create a "dispatch source" for this. I'm going to illustrate it with a UIProgressView, but the same applies to pretty much any UI:

// create source for which we'll be incrementing a counter,
// and tell it to run the event handler in the main loop
// (because we're going to be updating the UI)

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());

// specify what you want the even handler to do (i.e. update the HUD or progress bar)

dispatch_source_set_event_handler(source, ^{
    self.iterations += dispatch_source_get_data(source);
    [self.progressView setProgress: (float) self.iterations / kMaxIterations];
});

// start the dispatch source

dispatch_resume(source);

// now, initiate the process that will update the source

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    for (long i = 0; i < kMaxIterations; i++)
    {
        // presumably, do something meaningful here

        // now increment counter (and the event handler will take care of the UI)

        dispatch_source_merge_data(source, 1);
    }

    // when all done, cancel the dispatch source

    dispatch_source_cancel(source);
});

In my example, iterations is just a long property:

@property (nonatomic) long iterations;

And I defined my kMaxIterations constant as follows:

static long const kMaxIterations = 10000000l;
Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

First off, if you want to delay execution use dispatch_after: Apple Doc since it could be that Clang is optimizing your loop (i.e. by making it not exist).

Within that block call dispatch_sync on the main thread to update the UI, since dispatch_async is not guaranteed to execute 'evenly'. Something like this ought to work...

for (...) {
    dispatch_after(<some formula of i>, DEFAULT_PRIORITY, ^{
         dispatch_sync(MAIN_QUEUE, ^{ hudd.progress = value });
    }
}
Ryan Dignard
  • 639
  • 1
  • 5
  • 16
  • Using `dispatch_sync` works, but constrains the speed of the background process to the speed that UI updates take place. – Rob Jan 15 '14 at 21:03