Adding a completion call back for MBProgressHUD - ios

I would like to add a completion callback on my method so that the progress of the HUD knows it is complete.
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeAnnularDeterminate;
hud.labelText = #"Loading";
[self doSomethingInBackgroundWithProgressCallback:^(float progress) {
hud.progress = progress;
} completionCallback:^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
}];
What would I need to add to my method to confirm it is complete, or trigger this completionCallback from above?
In this case my method could be anything for example:
-(void)doSomethignInBackgroundWithProgressCallback {
sleep(100);
}

In case of HUD you can use its delegate function hudWasHidden which will when you use HUD like this -
[HUD showWhileExecuting:#selector(your_function) onTarget:self withObject:nil animated:YES];
and if you want to know how to use callbacks in objective c then follow this post -
http://stackoverflow.com/questions/1015608/how-to-perform-callbacks-in-objective-c
completion callback method -
- (void) doSomethingInBackground:(void (^) (void)) completion
{
// do your job here
completion();
}

Related

how to use MBProgressHUD to pause the screen when the loading animating

I have a problem.
I use MBProgressHUD in my tableviewcontroller.
I want that the MBProgressHUD loading animating then user can't touch anything and wait to get server data to show on the tableview.
I want to pause screen until hud hideAnimated.
Thanks!
This is my code:
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.listTableView animated:YES];
hud.label.text = #"Loading";
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self getInfo:nil];
});
hud.userInteractionEnabled = YES;
[hud hideAnimated:YES afterDelay:2];
You shouldn't use [hud hideAnimated:YES afterDelay:2] in this case. There is no point in dispatch_after either.
The thing is that your request may finish really quick (like 0.1 sec) and the hud will be still shown for another 1.9 sec. Or it may stay pending for a really long time, like 30 seconds or whatever timeout you set, so your code will hide the hud when it should actually be shown.
What you should do is to call completion blocks (or delegate methods) when the request is finished (successfully or not).
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.label.text = #"Loading";
[self getInfoWithSuccess:^{
[hud hideAnimated:YES];
//your code here
} failure:^(NSError *error) {
[hud hideAnimated:YES];
//show error message if needed
}];
Request method itself:
- (void) getInfoWithSuccess: (void(^)()) success failure: (void(^)(NSError *)) failure {
//do async request here and call success or failure block when it's finished
}

Sleep function in iOS with Spinner

I have a spinner function to start and stop from beginning of process to end. But since process takes milliseconds, i can't really keep spinner turning.
[NSThread sleepForTimeInterval:2.0]; //method 1
sleep(2); //method2
Then i used Sleep methods to standby the code and let spinner turn, but it stops the Thread, then spinner will stop as well. This is the code:
if (indexPath.row == 1)
{
[MBProgressHUD showHUDAddedTo:self.view animated:YES]; //spinner starts
EmployeeDataSource *aaa=[[EmployeeDataSource alloc]init];
[aaa Logout: ^(BOOL results){
if(results == YES){
[NSThread sleepForTimeInterval:2.0]; //sleeping thread
//if logout succesfull go to login page
LoginViewController *lv = [self.storyboard instantiateViewControllerWithIdentifier:#"loginsb"];
[self presentViewController:lv animated:NO completion:nil];
[MBProgressHUD hideAllHUDsForView:self.view animated:YES]; //spinner ends
}
else{
NSLog(#"logout not succesfull");
}
}];
I want [MBProgressHUD showHUDAddedTo:self.view animated:YES]; to work for 2 seconds at least, but it ends in less than a second since normal process is fast? How can i extend this time, sleep method seems not suitable. Do you have any idea? Thank you.
i think you should do your stuff after some delay.
for that please use this
[self performSelector:#selector(method) withObject:nil afterDelay:1.0];
here Delay is in second, so you can set it accordingly.
Or you can go with this as well.
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
//Do you stuff here...
});
May this help you.

Allow UI updates iOS

Let's say I do something like this:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[self methodThatTakesLotsOfTime];
[MBProgressHUD hideHUDForView:self.view animated:YES];
This will won't allow enough time for the HUD to animate in and show. Using sleep(100) or an NSTimer will only add to the issue. I can't run the method on the main queue either. Not to mention dispatch_get_current_queue() is deprecated for iOS 6.0+. I must be missing something big
You can try using a transaction completion handler:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[CATransaction setCompletionBlock: ^{
[self methodThatTakesLotsOfTime];
}];
But really that's just a stopgap. It is up to the authors of the code you are using to give you a way to animate with a completion block.

Make iOS not to wait until the entire method is finished to start animation

I have a method refreshDataAction:
- (void)refreshDataAction
{
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Loading Data.Please Wait";
[self deletePreviousValues];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSLog(#"Getting customer values");
[self getAllCustomerValues];
NSLog(#"Got customer values");
NSError *nwerror = nil;
if (![self.secondMOC save:&nwerror])
{
NSLog(#"209 Failed to save second MOC");
}
else
{
//NSLog(#"saved success");
}
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
NSLog(#"Saved");
});
NSLog(#"Dispatched");
}
So as soon as the refreshDataAction starts i should see the HUD. But no its like waiting for 13 seconds before showing the HUD. I have no idea why it is happening like that. I tried different one like ActivityIndicator, but to no avail. Why is my app waiting 13 seconds to start the hud. On simulator its instant. I click button and i see HUD. But on the device 13 second delay. Start the hud immediately while background thread is doing some work. Please help. Spent the entire 5 hours figuring out ways to do this task.
There is my question here :UIAlertview hanging while the thread in background is loading data
I changed it to MBProgressHUD. May be some developers who have worked with MBProgressHUD could see this problem.
Is my main UI thread in some sort sleep mode for 13 seconds? I really dont know at this point. FYI, it's in iOS 6.0. This is an in-house iPad app. If you need more info, do ask.Thanks
I'd add the following to your viewDidLoad.
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"";
HUD.mode = MBProgressHUDModeIndeterminate;
Then you can simply call the following methods without the dispatch_get_main_queue().
[HUD show:YES];
and...
[HUD hide:YES];

iOS - Background processes and UI update

the question is simple : my app control if there is an update every time it starts. If there is an update a popup will be shown with a Yes or No choose. When user tap Yes 4 methods start. These methods download xml file and upload CoreData. This is the code of the alert :
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex==1) {
[self showActivityViewer];
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[self hideActivityViewer];
NSLog(#"AGGIORNA");
} else {
NSLog(#"NON AGGIORNARE");
return;
}
}
But there is a problem : when user tap Yes the alert doesn't disappear and remain on screen until all methods are finished. So i try this other code :
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex==1) {
[self showActivityViewer];
[NSThread detachNewThreadSelector:#selector(startDownloads) toTarget:self withObject:nil];
[self hideActivityViewer];
NSLog(#"AGGIORNA");
} else {
NSLog(#"NON AGGIORNARE");
return;
}
}
-(void)startDownloads {
NSInvocationOperation *opPoi=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdatePoi) object:nil];
NSInvocationOperation *opItinerari=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdateItinerari) object:nil];
NSInvocationOperation *opArtisti=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdateArtisti) object:nil];
NSInvocationOperation *opEventi=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdateEventi) object:nil];
NSArray *operations=[[NSArray alloc] initWithObjects:opPoi,opItinerari,opArtisti,opEventi, nil];
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
[queue addOperations:operations waitUntilFinished:YES];
[queue waitUntilAllOperationsAreFinished];
}
There is a problem even here : i tap start, but the activity viewer doesn't appear. The alert disappear and the thread start and run the 4 methods one after another.
I need the processes run in background, just like happened with my 2nd code, but i need even my showActityViewer method will be run and show the spinner.
Thanks :)
First things first. You don't need to start 4 operations, since you are already in a secondary thread and do not need that the 4 operations be executed in parallel. You could simply do:
-(void)startDownloads {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[pool release];
}
Above all, you need define an autorelease pool in startDownloads if you use autorelease in the downloadControl* methods, otherwise I suspect you will have leaks.
As to why the activity indicator does not show up, it depends on the fact that you are calling:
[self hideActivityViewer];
immediately after detach. So, you are showing it and removing it, before event the UI has got the time to update itself. Remove that line from there and rewrite startDownloads like this:
-(void)startDownloads {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[self performSelectorOnMainThread:#selector(hideActivityViewer) withObject:nil waitUntilDone:NO];
[pool release];
}
Here notice that I am calling into the main thread to hideActivityViewer because only the main thread can safely use UIKit.
EDIT:
I did not know that you were using Core Data in the download methods...
Have a look at Concurrency with Core Data. You will need to tweak a bit your code by at least using a separate managed object context for your secondary thread (I don't know if it is feasible for you to create the moc there).
Also have a look at this tutorial from Cocoa is my Girlfriend.
As an alternative to all that, you could consider doing:
if (buttonIndex==1) {
[self showActivityViewer];
[self performSelector:#selector(startDownloads) withObject:nil afterDelay:0];
NSLog(#"AGGIORNA");
} else {
NSLog(#"NON AGGIORNARE");
return;
}
with:
-(void)startDownloads {
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[self hideActivityViewer];
}
This does not use threads at all, but I am not sure that the activity viewer will show without any glitch. One more level of hack, if needed, and you could specify a delay in
[self performSelector:#selector(startDownloads) withObject:nil afterDelay:0.1];

Resources