I am trying to show a spinner in a subview on a tableview controller on my main thread. The view is only showing on occasions and generally appears to be showing after my background thread has finished. Here is my code:
[self.view addSubview:spinnerView];
[self.view bringSubviewToFront:spinnerView];
self.spinnerView.layer.cornerRadius = 8;
self.spinnerView.center = CGPointMake(160, 150);
[spinner startAnimating];
dispatch_queue_t downloadQueue = dispatch_queue_create("downloader", NULL);
dispatch_async(downloadQueue, ^ {
dispatch_async(dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:10];
// Add recurring transactions if necessary
if (tran.recur.id != 0) {
[Utilities addRecurringTransactionsUpToCurrentMonthByTransaction:tran];
}
[spinner stopAnimating];
[self dismissViewControllerAnimated:YES completion:nil];
});
});
dispatch_release(downloadQueue);
}
}
Since all UI is running on main thread, you must call performSelectorOnMainThread:withObject:waitUntilDone: like below:
[self.view addSubview:spinnerView];
[self.view bringSubviewToFront:spinnerView];
self.spinnerView.layer.cornerRadius = 8;
self.spinnerView.center = CGPointMake(160, 150);
[spinner performSelectorOnMainThread:#selector(startAnimating) withObject:nil waitUntilDone:NO];
dispatch_queue_t downloadQueue = dispatch_queue_create("downloader", NULL);
dispatch_async(downloadQueue, ^ {
dispatch_async(dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:10];
// Add recurring transactions if necessary
if (tran.recur.id != 0) {
[Utilities addRecurringTransactionsUpToCurrentMonthByTransaction:tran];
}
[spinner performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
[self dismissViewControllerAnimated:YES completion:nil];
});
});
dispatch_release(downloadQueue);
Related
Hi I m using dispatch_group_t for async web call and during that wait period I want to show activity indicator.
Here is my code which is not working for show/hide activity indicator.
- (BaseResponse*)getResponse:(BaseRequest*)request{
request.header = [[Header alloc]init];
request.header.osType = 2;
NSString *jsonBody = [request toJSONString];
NSURL *requestURL = #"myurl";
__block BaseResponse *baseResponse;
[self showHUDProcessView]; //here activity indicator should start animating but not working as per expected.
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[COMHandler sharedCOMHandler] makeServiceCall:jsonBody requestURL_:requestURL completion:^(ComResponse *comResponse){
[self hideHUDProcessView]; //hide activity when receive response
if (comResponse != nil) {
baseResponse = comResponse;
}else{
//show alert
}
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return baseResponse;
}
- (void) showHUDProcessView {
[MBProgressHUD showHUDAddedTo:viewController.view animated:YES];
}
- (void) hideHUDProcessView {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:viewController.view animated:YES];
});
});
}
I also tried with starting group in block but also not work..
Tried
dispatch_semaphore_t activity = dispatch_semaphore_create(0);
//show indicator
^myblock {
//hide indicator
dispatch_semaphore_signal(activity);
}
dispatch_semaphore_wait(activity, DISPATCH_TIME_FOREVER);
also but no luck.
Can anybody suggest me when i m doing wrong??
Or want else I can do to achieve this indicator show/hide issue.
I'm using a method for scanning Bluetooth device, which I import from another framework. The scanning method would take a while, and it'll block the GUI, which is what we never want to happen.
I'm also having MBProgressHud, trying to show a hud while scanning, but it's not working (hud not showing up). Any help?
Here's the code I'm currently using:
[hud showAnimated:YES whileExecutingBlock:^{
self.btDevices = [Util scanBT];
}];
Edit 1: Okay, so if I use this code, it'll still block my UI for a while, then all suddenly continue to run.
hud = [[MBProgressHUD alloc] initWithView:self.view];
hud.labelText = #"Now scanning";
hud.dimBackground = YES;
hud.opacity = 0.5;
[hud show:YES];
[hud hide:YES afterDelay:5.0];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
self.btDevices = [Util scanBT];
});
Edit 2: Ok, I will post all my block of code:
hud = [[MBProgressHUD alloc] initWithView:[self getTopView:self.view]];
hud.labelText = #"Now scanning";
hud.dimBackground = YES;
hud.opacity = 0.5;
[hud showAnimated:YES whileExecutingBlock:^{
self.btDevices = [Util scanBT];
}];
dispatch_queue_t myqueue = dispatch_queue_create("queue", NULL);
dispatch_async(myqueue, ^{
//Whatever is happening in the BT scanning method will now happen in the background
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:[self getTopView:self.view] animated:YES];
});
});
/** Recursion to get the top most view in view layer. */
- (UIView *)getTopView:(UIView *)view
{
if ([view.superview class]) {
return [self getTopView:view.superview];
}
return view;
}
I'm request scanning bt in a popover, but I want to show HUD in a main View, so I write a block to retrieve the main view. Maybe that's where the problem occur?
Try this:
In your viewDidload or the method where you want to place it
MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:self.view];
[hud setDimBackground:YES];
[hud setOpacity:0.5f];
[hud show:YES];
[hud hide:YES afterDelay:5.0];
[self performSelector:#selector(startScanning) withObject:nil afterDelay:5.0];
And your method
- (void) startScanning {
self.btDevices = [Util scanBT];
}
OR I think you should try it running
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
// Insert task code here
self.btDevices = [Util scanBT];
});
Try it with completion block
[hud showAnimated:YES whileExecutingBlock:^{
self.btDevices = [Util scanBT];
} completionBlock:^{
//code for after completion
}];
OR you can also try this
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_queue_t myqueue = dispatch_queue_create("queue", NULL);
dispatch_async(myqueue, ^{
//Whatever is happening in the BT scanning method will now happen in the background
self.btDevices = [Util scanBT];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
This is my code for async fetching JSON
-(void)viewDidAppear:(BOOL)animated
{
[_indicator startAnimating];
_indicator.hidesWhenStopped = YES;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//Load the json on another thread
[Constants shared].jsonData = [[NSData alloc] initWithContentsOfURL:
[NSURL URLWithString:#"http://api.fessor.da.kristoffer.office/homework/index/?rp[token]=app&rp[workspace]=parent&child_id=22066&type=parent&start_date=2014-05-01&end_date=2014-05-01"]];
//When json is loaded stop the indicator
[_indicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
[self declareVariables];
[_tableView reloadData];
});
}
And it's not working for some reason.
It shows the spinner, I can see that the data is fetched, it stops and hides the spinner, but the tableView is not reloaded, it's blank.
You want to move all the UI stuff to the main queue. So your code should read as:
-(void)viewDidAppear:(BOOL)animated
{
[_indicator startAnimating];
_indicator.hidesWhenStopped = YES;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//Load the json on another thread
[Constants shared].jsonData = [[NSData alloc] initWithContentsOfURL:
[NSURL URLWithString:#"http://api.fessor.da.kristoffer.office/homework/index/?rp[token]=app&rp[workspace]=parent&child_id=22066&type=parent&start_date=2014-05-01&end_date=2014-05-01"]];
dispatch_async(dispatch_get_main_queue(), ^{
//When json is loaded stop the indicator
[_indicator stopAnimating];
[self declareVariables];
[_tableView reloadData];
});
});
}
This way your JSON is loaded on the background queue and the UI is updated on the main queue.
You should try to reload your table view on the main thread. Use GCD to dispatch on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[_tableView reloadData];
});
If there's no result, try to log in the cellForRowAtIndexPath method to check whether the reload is done or not. If so, you error is elsewhere.
I want to display an activity indicator while performing some network calls in a dispatch_group_asyc block. But activity indicator only shows when the block finishes. I'm creating a dispatch_group_t because I need to get the result of the network calls before performing some other tasks. This is a simplified version of my code:
- (BOOL)doNetCall
{
[activityIndicator startAnimating];
__block BOOL netResult = NO;
dispatch_queue_t queue = dispatch_queue_create(netQueue, NULL);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue,^{
netResult = [service queryService];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
dispatch_release(queue);
[activityIndicator stopAnimating];
if (netResult) {
// Perform some tasks
}
else {
[self showAlertView];
}
return netResult;
}
What am I doing wrong? Thanks!
EDIT: I need the method to wait until the block finishes in order to return the result I get
You should use activity indicator in this way:
[activityIndicator startAnimating];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
dispatch_async(dispatch_get_main_queue(), ^{
[activityIndicator stopAnimating];
});
});
If you're using group, just rewrite your code a little bit.
There doesn't appear to be any need for a dispatch group with what you are doing. Try this:
- (void)buttonClicked {
[activityIndicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL netResult = [service queryService];
dispatch_async(dispatch_get_main_queue(), ^{
[activityIndicator stopAnimating];
if (netResult) {
// perform some tasks
} else {
// show alert
}
});
});
}
If you have your queue, replace the call to dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) with your own queue.
I'm trying to learn GCD, so I don't have a full grasp on how it works yet. For some reason, I experience a permanent drop in frame rate after I call the following method. If I don't use the dispatch functions and simply write the data on the main loop, the frame rate stays at 60. I don't know why.
-(void)saveDataFile {
_hud = [MBProgressHUD showHUDAddedTo:self.parentView animated:YES];
_hud.labelText = NSLocalizedString(#"Saving data...", nil);
dispatch_queue_t myQueue = dispatch_queue_create("myQueueName", NULL);
dispatch_async(myQueue, ^(void) {
#autoreleasepool {
id data = [self.model getData];
if (data != nil) {
NSString *filePath = #"myPath";
[data writeToFile:filePath atomically:YES];
}
}
dispatch_async(dispatch_get_main_queue(), ^(void) {
[_hud hide:YES];
});
});
}
Solved. I followed the implementation of HUD from this question: MBProgressHUD not showing
Basically, I need to remove the HUD rather than simply hide it. Otherwise the HUD animation continued, invisible to me, hence causing the drop in frame rate.
-(void)saveDataFile {
// create HUD and add to view
MBProgressHUD *hud = [[MBProgressHUD alloc]initWithView:self.parentView];
hud.labelText = NSLocalizedString(#"Saving data...", nil);
hud.delegate = self;
[self.parentView addSubview:hud];
// define block for saving data
void (^saveData)() = ^() {
#autoreleasepool {
id data = [self.model getData];
if (data != nil) {
NSString *filePath = #"myPath";
[data writeToFile:filePath atomically:YES];
}
}
}
// use HUD convenience method to run block on a background thread
[hud showAnimated:YES whileExecutingBlock:saveData];
}
// remove hud when done!
//
- (void)hudWasHidden:(MBProgressHUD *)hud {
[hud removeFromSuperview];
}