Dismiss MBProgressHUD when delegate method fires - iphone

First time working with a HUD and I'm confused.
I setup the HUD like this in my viewDidLoad:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[[[WSXmppUserManager shared] xmppStreamManager] connect];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
The HUD doesn't show. I think the reason is as follows. The xmpp connect method fires off a connection request to the xmpp server and then it's done. So there is no activity to wait for as is.
However, the connection isn't established until the server responds and the following delegate method is fired:
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
I want to wait for this and only then dismiss the HUD, but I'm at a loss as to how to do that. I'm missing something very simple.

You need to move this code
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
After your long running method has finished... that is, if this code is indeed returning immediately
[[WSXmppUserManager shared] xmppStreamManager] connect];
The hud is likely never going to display... as it gets told to display and then told to hide on the same run loop or perhaps one run loop right afterwards...
Why not put it at the end of this method if this indicates that a response has been received and the operation is completed?
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender

HUD =[[MBProgressHUD alloc] initWithWindow:self.view];
[HUD setDelegate:self];
[self.view addSubview:HUD];
[HUD showWhileExecuting:#selector(connectToServer)
onTarget:self
withObject:nil
animated:YES];
In the connectToServer
-(void)connectToServer
{
[[[WSXmppUserManager shared] xmppStreamManager] connect];
}
As soon as the connectToServer method comepletes it task in the background , a delegate of MBProgressHUD called hudWasHidden: is automatically called
-(void)hudWasHidden:(MBProgressHUD *)hud
{
//Further work after the background task completed
}

Related

MBProgress view and pushing a view controller doesn't display

MPProgressView won't display when I try to push a viewcontroller until seconds before the pushed VC is displayed. Should the viewController be placed in the same function as the MBProgressView is displayed? I've made sure that my MBProgressView is on the main thread, I've tried many solutions on SO and can't see anyone with the same issue. I am simply trying to display the MBProgressHUD while the viewController is loading and being pushed. Thanks!
I am using MBProgressView as follows:
- (IBAction)pushButton:(id)sender
{
self.HUD =[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[self.view addSubview:self.HUD];
self.HUD.labelText = #"Doing stuff...";
self.HUD.detailsLabelText = #"Just relax";
self.HUD.delegate=self;
[self.view addSubview:self.HUD];
[self.HUD showWhileExecuting:#selector(loadCreate) onTarget:self withObject:nil animated:YES];
}
- (void)loadCreate {
[self performSelectorOnMainThread:#selector(dataLoadMethodMail) withObject:nil waitUntilDone:YES];
}
-(void)dataLoadMethodMail
{NSLog(#"data load method is displaying");
SelectViewController *mvc = [[SelectViewController alloc] init];
[self.navigationController pushViewController:mvc animated:YES];
}
You don't need to add self.HUD to self.view, showHUDAddedTo: does it for you.
[self.HUD showWhileExecuting:#selector(loadCreate) onTarget:self withObject:nil animated:YES];
Shows the hud until loadCreate returns.
[self performSelectorOnMainThread:#selector(dataLoadMethodMail) withObject:nil waitUntilDone:YES];
dispatches something on main thread and returns right after (before the actual end of dataLoadMethodMail). The HUD is shown but disappears right away.
To solve the issue try hiding manually the HUD when dataLoadMethodMail finishes it's work.
Just replace
[self.HUD showWhileExecuting:#selector(loadCreate) onTarget:self withObject:nil animated:YES];
with
[self loadCreate];
and add
dispatch_async(dispatch_get_main_queue(), ^{
[self.HUD hide:YES];
});
at the end of dataLoadMethodMail
PS : Loading data should not be done on main thread.

MBProgressHud not hiding after calling hide from another class

I have a ViewController called GetInfoViewController. Basically it takes the users input and then sends the input to a NSObject class, ServerConnection. ServerConnection makes a nsurlconnection request and when it is done I want the MBProgressHUD to hide.
GetInfoViewController
- (IBAction)go:(id)sender{
ServerConnection *serverConnection = [[ServerConnection alloc]init];
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
HUD.labelText = #"Searching...";
// here the progressHUD shows normally, and the yelpConnectionMethod is successful in retrieving the result.
[serverConnection yelpConnectionMethod];
}
-(void)endProgressHUD{
NSLog(#"end called");
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
ServerConnection.h
I am not going to put all the nsurlconnection code because I don't think it really applies.. but if you want it I can post it
The only line that matters is:
(this is called after all the connections are done.)
GetInfoViewController *getinfoVC = [[GetInfoViewController alloc]init];
[getinfoVC endProgressHUD];
I call the endProgressHUD method of the getInfoViewController successfully, as it logs "end called". however, the progress hud stays spinning and does not hide.
Any input would be helpful.
Try this method when showing the HUD.
- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated;
It is a method of MBProgressHUD - selector should be the yelpConnectionMethod and target the serverConnection, object = nil and animated depends on you :)

Showing MBProgressHUD during method duration

I have a method to get contacts from the address book and doing some stuff with them ("getContactInformation").
This process is a bit long (a few seconds) and after this process I show a new ViewController. To make it friendly to the user I would like to use MBProgressHUD to show an activity indicator at the beginning of the process and hide it at the end.
Which is the best way to do it? I've test this:
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Retrieving information...";
[self getContactInformation];
[MBProgressHUD hideHUDForView:self.view animated:YES];
[self presentViewController:newController animated:NO completion:nil];
But it doesn't work (It doesn't show the indicator). Anyone can help me?
Thank you in advance
Keep a separate method for [self presentViewController:newController animated:NO completion:nil];. And try calling that method after some particular delay. That should be solving the problem.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[self getContactInformation];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.mapView animated:YES];
});
});
This will spawn a thread to run getContactInformation and won't block the main thread which is required to do UI changes (such as displaying the HUD).
I'm guessing the main thread is getting locked up when you get contact info and doesn't have time to update to show the HUD. Once the method is complete, it gets the main thread again to update the UI (removing the HUD).

performSelector:withObject:afterDelay: not making call

in a method, i want to call a method after n seconds:
self.toolBarState = [NSNumber numberWithInt:1];
[self changeButtonNames];
[self drawMap];
[self performSelector:#selector(showActionSheet) withObject:nil afterDelay:2];
i want to show the action sheet 2 seconds after drawMap is complete. when i use this performSelector, it never makes the call.
if i just put [self showActionSheet];, it works just fine. is there reason why the performSelector is not making the call?
EDIT: in another part of my code, i make the same call and it works:
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = (id) self;
[HUD showWhileExecuting:#selector(drawMap) onTarget:self withObject:nil animated:YES];
[self performSelector:#selector(showActionSheet) withObject:nil afterDelay:6];
here, the showActionSheet gets called 6 seconds after drawMap has completed. i'm guessing there is something going on with the threads that i don't understand yet...
EDIT2:
-(void)showActionSheet
{
InspectAppDelegate *dataCenter = (InspectAppDelegate *) [[UIApplication sharedApplication] delegate];
if (dataCenter.fieldIDToPass == nil)
{
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:#"Selected Boundary Options" delegate:(id) self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:#"Analyze a Field",#"Retrieve Saved Analysi", #"Geotag Photos", #"Refresh the map",nil];
actionSheet.tag = 0;
[actionSheet showInView:self.view];
}
else
{
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:#"Selected Boundary Options" delegate:(id) self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:#"Analyze a Field",#"Retrieve Saved Analysi", #"Geotag Photos", #"Attribute the Field", #"Refresh the map",nil];
actionSheet.tag = 0;
[actionSheet showInView:self.view];
}
}
EDIT3:
ok, so the progress of method calls is:
-(void) foundDoubleTap:(UITapGestureRecognizer *) recognizer
{
[HUD showWhileExecuting:#selector(select) onTarget:self withObject:nil animated:YES];
}
-(void) select
{
[self changeButtonNames];
[self drawMap];
[self performSelector:#selector(showActionSheet) withObject:nil afterDelay:2];
}
showActionSheet never gets called. like i said, i'm pretty sure its a threading issue. if call it with [self showActionSheet], it will run. =/
I ran into this same issue, and by necessity I solve it slightly different from the accepted answer. Notice I wanted my delay and selectors to be variables? Using a block allows me to stay within my current method.
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:loopSelector withObject:nil afterDelay:cycleTime];
});
By the way, this is definitely a threading issue. The documentation for performSelector:withObject:afterDelay: states that this will be performed on the current thread after the delay, but sometimes that thread's run loop is no longer active.
A more detailed discussion on the subject can be found here
Try using:
-(void) select {
[self changeButtonNames];
[self drawMap];
[self performSelectorOnMainThread:#selector(showActionSheet) withObject:nil waitUntilDone:YES];
}
-performSelector:withObject:afterDelay: schedules a timer on the same thread to call the selector after the passed delay.
Maybe this will work for you:
-(void) select {
[self changeButtonNames];
[self drawMap];
[self performSelectorOnMainThread:#selector(someA) withObject:nil waitUntilDone:YES];
}
-(void)someA {
[self performSelector:#selector(showActionSheet) withObject:nil afterDelay:2];
}
Does your class still exist in memory?
If your class goes away before the performSelector fires, do you wind up sending the message to nil (which would cause nothing to occur).
You could test this by dropping an NSLog into your dealloc()
You mentioned threads. If your performSelector isn't called from the MainThread it could cause issues (UI things should be done on the main thread).
I got the same problem when I call performSelector:withObject:afterDelay: in a background thread created by ReactiveCocoa.
If I execute the block in the ReactiveCocoa's way, the block will be called correctly:
[[[RACSignal empty] delay:2.0] subscribeCompleted:^(){
// do something
}];
I guess there is some magic in the ReactiveCocoa's threading model.

iPhone activity indicator being delayed?

So when I click on a callout accessory in my mapView, nothing happens for several seconds because it is making a url request and parsing it, so I wanted to show the activity indicator so the user doesn't think it's frozen. Here's the code:
- (void)mapView:(MKMapView *)mv annotationView:(MKAnnotationView *)pin calloutAccessoryControlTapped:(UIControl *)control {
// start activity indicator
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSLog(#"tapped");
ArtPiece *artPiece = (ArtPiece *)pin.annotation;
//when annotation is tapped switches page to the art description page
artDescription *artD = [[artDescription alloc] initWithNibName:#"artDescription" bundle:nil];
artD.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
artD.startingLocation = mapView.userLocation.location.coordinate;
artD.selectedArtPiece = artPiece;
NSLog(#"0");
[self presentModalViewController:artD animated:YES];
NSLog(#"1");
[artD loadArt:artPiece];
NSLog(#"2");
// stop activity indicator
//[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[artD release];
}
Strangely (to me anyway, maybe I'm missing something obvious as I'm pretty inexperienced), the activity indicator does not show until after the method is done, and the modal view starts animating into view. I put the NSLogs in to see what was taking time. I had about a 2 second pause between "0" and "1" and another couple seconds between "1" and "2". Then the indicator finally showed, so I am sure it is waiting until the end of the method for some reason. Any ideas why?
The change to the UI, displaying the activity indicator, does not take effect until control has returned to the application's main run loop. This does not occur until after your method has ended and the stack has unwound. You need to show the activity indicator, then dump the activity you are waiting for onto a background thread:
[self performSelectorInBackground:#selector(doThingINeedToWaitFor:)
withObject:anObject];
(Note that Apple recommends that you move away from using threads explicitly; performSelectorInBackground:withObject: is the simplest method to get some code run off the main thread. More complex options are available for other situations. See the Concurrency Programming Guide.)
The important gotcha is that UI updates still need to be handled on the main thread, so in that method, when the work is done, you need to call back to stop the activity indicator:
- (void) doThingINeedToWaitFor: (id)anObject {
// Creating an autorelease pool should be the first thing
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Do your work...
// ...
// Update the UI back on the main thread
[self performSelectorOnMainThread:#selector(allDoneWaiting:)
withObject:nil
waitUntilDone:YES];
// Clear out the pool as the final action on the thread
[pool drain];
}
In your callback method, you hide the activity indicator again and do any other post-processing that's necessary.
You cannot start and stop the activity indicator in the same function.
See the answer I provided for this question: How show activity-indicator when press button for upload next view or webview?
Edit for clarity:
- (void) someFunction
{
[activityIndicator startAnimation];
// do computations ....
[activityIndicator stopAnimation];
}
The above code will not work because you do not give the UI time to update when you include the activityIndicator in your currently running function. So what I and many others do is break it up into a separate thread like so:
- (void) yourMainFunction {
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[NSThread detachNewThreadSelector:#selector(threadStartAnimating) toTarget:self withObject:nil];
//Your computations
[activityIndicator stopAnimating];
}
- (void) threadStartAnimating {
[activityIndicator startAnimating];
}
Something is slowing down your spinner. I would recommend doing your heavy lifting in background, using a thread. Try this:
-(void)myMethod{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[NSThread detachNewThreadSelector:#selector(startWorkingThread) toTarget:self withObject:nil];
}
-(void)startWorkingThread{
//Heavy lifting
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
I assume that you have commented the:
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
For testing purposes...