MBProgressHUD fades / lightens when triggered by this button - ios

What is going on here?
http://screencast.com/t/WACeaQP00iqb
Here's the code for that button (the three method calls spawn 3 (yes 3; i didn't write the server :D) asynchronous data tasks that take some time to finish):
- (IBAction)didTouchClockButton:(id)sender {
[self.dr isUserClockedIn];
hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.dimBackground = NO;
// hail mary
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantPast]];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
if (![self.dr isNetworkDead])
if ([self.dr isUserClockedIn]) { // add clock in / clock out function to this button
hud.labelText = #"Clocking Out..";
[self.dr clockOut];
} else {
hud.labelText = #"Clocking In..";
[self.dr clockIn];
}
});
}
I put the hud dismissal inside a KVO callback, which isn't working yet but first things first.

You don't want to do anything related to the UI on a background thread. Dispatch the MBProgressHUD code on the main thread and see if that solves the issue. Try:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
if (![self.dr isNetworkDead])
if ([self.dr isUserClockedIn]) { // add clock in / clock out function to this button
dispatch_async(dispatch_get_main_queue(), ^{
hud.labelText = #"Clocking Out..";
});
[self.dr clockOut];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
hud.labelText = #"Clocking Out..";
});
[self.dr clockIn];
}
});

I ended up dismissing the HUD the line before I call the method that updates the other view elements. Right now they're both happening near instantaneously so it's fine, although if the view was still updating after the HUD went down that would be lame. So the solution is to not mess with the view while your HUD is active. A messy solution, so if anyone has a better one I'm all ears.

Related

UIScrollView setContentOffset:animated must be used from main thread only

I'm performing an action in tab bar, i.e. (when the button is tapped twice) it should perform,
[root.FeedTableView setContentOffset:CGPointZero animated:YES];
but I'm unable to perform it in the,
dispatch_async(dispatch_get_main_queue(), ^{
});
as nothing happens, so I tried using,
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
});
and it worked fine expect in this case the threading runtime error pops up.
Inspect the code below and suggest a remedy, for tackling this threading issue which happens at
[root.FeedTableView setContentOffset:CGPointZero animated:YES];
(void)processBtn:(UIButton *)sender {
NSLog(#"sender:%li",(long)sender.tag);
if (self.selectedViewController==self.viewControllers[0] && sender.tag==1){
NewsFeedViewController * root = [newsFeedSceneController.viewControllers firstObject];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[root.FeedTableView setContentOffset:CGPointZero animated:YES];
dispatch_async(dispatch_get_main_queue(), ^{
if(!root.isUpdatingTableView)
[root.refreshControl endRefreshing];
});
});
}
As Jon Rose noted, scroll view line:
[root.FeedTableView setContentOffset:CGPointZero animated:YES];
and UIrefreshcontrol line:
[root.refreshControl endRefreshing]; were the main conflict.
All that needed to be done was to remove UIrefreshcontrol line and add non UI dependent line[root RefreshView];

UIActivityIndicatorView not staring

I am trying to set a 'UIActivityIndicatorView' as an accessoryView in one of my 'UITableViewCell', in a such way as, the ActivityIndicator start animating when the user touch this cell. I add the following code at 'didSelectRowAtIndexPath' method:
UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
UIActivityIndicatorView * activity = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
NSThread * newThread1 = [[NSThread alloc]initWithTarget:self selector:#selector(carregarNoticias) object:nil];
switch (indexPath.row) {
case 0:
cell.accessoryView = activity;
[activity startAnimating];
sleep(0.01);
[newThread1 start];
while (![newThread1 isFinished]) {
//waiting for thread
}
[activity stopAnimating];
[self.navigationController pushViewController:noticias animated:YES];
break;
I was expecting 'UIActivityIndicatorView' animating while the newthread1 is running. However, the animating only start when the newThread1 finish (and therefore I don't want the animation anymore). I added 0.01 seconds as sleep time to give time to the animation start. Nevertheless, this also did't solve.
Does anyone know what my error is? I appreciate any help!
I dont really know what you are trying to do adding delay like that.. try this line below, might be the one your look for.. and another thing.. please dont block the main thread like sleep(0.01); that, users wont like it..
cell.accessoryView = activity;
[activity startAnimating];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// the system will wait until this
dispatch_async(dispatch_get_main_queue(), ^(void){
[newThread1 start];
});
// is finished, maybe you dont need the `[NSThread sleepForTimeInterval:3];`
// pick the one that suits your implementation..
[NSThread sleepForTimeInterval:3];
dispatch_async(dispatch_get_main_queue(), ^(void){
[activity stopAnimating];
[self.navigationController pushViewController:noticias animated:YES];
});
});
[NSThread sleepForTimeInterval:3] sleep the thread like the one you have sleep(0.01);
But using it inside
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ });
makes it async. and wont block the main thread..
while the dispatch_async(dispatch_get_main_queue(), ^(void){ fires your code at the main thread..
Happy coding, cheers! :)

MBProgressHUD Hiding indicator after GCD

The user is able to click a button to download a set of maps, upon completion of that task, I would like to hide the progress indicator. I have tried several variants of the below code but have not achieved what I am searching for. Any guidance would be appreciated.
- (IBAction)SavePhotoOnClick:(id)sender{
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSURL *url = [NSURL URLWithString:urlstr1];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
// Set determinate mode
HUD.mode = MBProgressHUDModeDeterminate;
HUD.dimBackground = YES;
HUD.color = [UIColor colorWithRed:0.23 green:0.50 blue:0.82 alpha:0.90];
HUD.delegate = self;
HUD.labelText = #"Downloading Maps";
// myProgressTask uses the HUD instance to update progress
[HUD showWhileExecuting:#selector(myProgressTask) onTarget:self withObject:nil animated:YES];
}
- (void)myProgressTask {
// This just increases the progress indicator in a loop
float progress = 0.0f;
while (progress < 1.0f) {
progress += 0.01f;
HUD.progress = progress;
usleep(40000);
}
}
Removing the progresstask method and the showWhileExecuting code removes the progress indicator. It seems that usleep overrides everything and does not allow for the progress indicator to be hidden after the download completes and instead removes it after the 4 sec.
Your problem is that myProgressTask doesn't perform any actual work: your download actually happens in your dispatch_async call. Use a call like [hud showHUDAddedTo: self.navigationController.view animated:YES]; before you start your network request, so it will become hidden when your download completes. This will only show the spinning indicator, not a progress circle.
If you want the progress circle, you will need to use NSURLConnection and its associated delegate methods to track the progress of the download. Specifically, look at the connection:didReceiveResponse: method—using the expectedContentLength to calculate the percentage, using values you get from the connection:didReceiveData: method to incrementally update this progress.
Check out this question for an example of how to do this.

View is not displayed immediately

What i want to achieve :
Create processing screen with some custom icons, label etc. with following behaviour.
Add view over window which will not allow user to touch anything in application until it is removed. (like processing/loading screen)
When this view is displayed all other operation like adding subview, performing segue etc should work as they work normally but below my loading view.
Want method showProcessingScreen to work on any thread (Whatever thread switching code etc should be in respective show/hide method).
It should be displayed/removed immediately in after calling respective methods.
Code :
-(void) showProcessingScreen
{
dispatch_async(dispatch_get_main_queue(),
^{
UIStoryboard *mystoryboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
processingScreen = [mystoryboard instantiateViewControllerWithIdentifier:#"loadingViewController"];
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: processingScreen.view];
[mainWindow bringSubviewToFront:processingScreen.view];
});
}
-(void) hideProcessingScreen
{
dispatch_async(dispatch_get_main_queue(),
^{
[processingScreen.view removeFromSuperview];
});
}
Issue:
I want code above to work with showing/hiding loading screen immediately.
- (IBAction)proceedBtnPressed:(id)sender
{
[[GUIUtilities sharedObj] showProcessingScreen];
//Some other code here
}
When i call showProcessingScreen like above processing screen takes around 2-3 sec to show.
But when i remove other code below it (//Some other code) it shows screen immediately.
What i have tried:
Putting code in showProcessingScreen in other method and calling that on main thread using performSelectorOnMainThread.
Calling showProcessingScreen on background and executing show code on main thread using performSelector.
This works
//code
-(IBAction)proceedBtnPressed:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
//Some other code here
});
}
But i don't want any thread switching mechanism outside of showProcessingScreen.
This is common screen almost used in every application. I used similar codes with xib, custom views in my previous apps which were not using storyboard etc.,
I know this is related to threading, what i am doing wrong here ? what is best practice to achieve this ?
Any help will be aprreciatead.
The problem is that when you're dispatching, you put the creation of your loading view at the end of the run loop. Whatever your "other code" is is blocking the main thread for a few seconds.
If you move this "other code" to a different thread, this will resolve your issue. (This solution is ideal.)
You could also only switch to the main thread conditionally, which would resolve the issue if you call the loading view from the main thread:
-(void) showProcessingScreen
{
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(showProcessingScreen) withObject:nil waitUntilDone:FALSE];
return;
}
UIStoryboard *mystoryboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
processingScreen = [mystoryboard instantiateViewControllerWithIdentifier:#"loadingViewController"];
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: processingScreen.view];
[mainWindow bringSubviewToFront:processingScreen.view];
}
Instead of adding an overlay view that intercepts touch events, you can just call
– beginIgnoringInteractionEvents
– endIgnoringInteractionEvents
You could try with this:
- (void)doSomeOtherCode {
//Some other code here
}
- (IBAction) showProcessingScreenAndDoSomeOtherCode:(id)sender
{
[[GUIUtilities sharedObj] showProcessingScreen];
[self performSelector:#selector(doSomeOtherCode) withObject:nil afterDelay:0.0];
}
- (IBAction)proceedBtnPressed:(id)sender
{
[self showProcessingScreenAndDoSomeOtherCode:self];
}
and it will work. About your remark:
But i don't want any thread switching mechanism outside of showProcessingScreen.
The above solution will not cause any thread switching outside of showProcessingScreen. performSelector will just add an entry in the event loop queue.
EDIT:
If you are using blocks, the same result can be achieved through:
dispatch_async(dispatch_get_main_queue(),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
dispatch_async(dispatch_get_main_queue(), ^{
//Some other code here
});
});
or:
dispatch_async(dispatch_get_main_queue(),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
});
dispatch_async(dispatch_get_main_queue(), ^{
//Some other code here
});
Since the main queue is a serial queue, showProcessingScreen and "Some other code here" would be executed serially.

User left current view before dispatch_get_main_queue() executed

I want to make all reading/writing database operations to background queue and update the current UI view when completed.
There is no problem if user stays in the view while I'm dealing with my database. However, if user left that view before database operations completed, it would crash. The psuedo code is as below:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
/* save data to database, needs some time */
dispatch_async(dispatch_get_main_queue(), ^{
// back to main queue, update UI if possible
// here may cause crash
[self.indicator stopAnimating];
[self.imageView ...];
});
});
Try checking if the view is still in the view hierarchy, and also stop the activity indicator from spinning in the viewDidDisappear method as well. You also might need a flag (isNeedingUpdate in the example below) to indicate whether the UI was updated or not, so you can do the appropriate actions if the user goes away before the update is complete and then comes back again.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
if (self.view.window) { // will be nil if the view is not in the window hierarchy
dispatch_async(dispatch_get_main_queue(), ^{
[self.indicator stopAnimating];
[self.imageView ...];
self.isNeedingUpdate = NO;
});
}else{
self.isNeedingUpdate = YES;
});
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (isNeedingUpdate) {
// do whatever you need here to update the view if the use had gone away before the update was complete.
}
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.indicator stopAnimating];
}

Resources