How to reload tableView after async fetching JSON ? Objective C - ios

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.

Related

MBProcessHUD show/hide when using semaphore

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.

MBProgressHUD showAnimated: whileExecutingBlock: not working

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];
});
});

Would I use dispatch in block for MBProgressHud?

In the MBProgressHud documentation it states to use this inside of a dispatch like so:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
Which is completely understandable considering you don't want it to boggle up the main thread. But could I just do this instead when using a block:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error)
{
}
else
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
}];
Or would I still have to use dispatch?
Change your code to be like this:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
NSLog(#"Am I running on the main thread: %#", [NSThread isMainThread] ? #"YES": #"NO");
if (error)
{
}
else
{
}
}];
if it logs "YES" then you don't need to run [MBProgressHUD hideHUDForView:self.view animated:YES]; on the main thread, otherwise you need to use
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
Update:
blocks are run on whatever thread they've been called from, notice following example:
- (void)viewDidLoad {
[super viewDidLoad];
[self longRunningProcessWithCompletionBlock:^{
NSLog(#"Is running on the main thread? %#", [NSThread isMainThread] ? #"YES" : #"NO");
}];
}
- (void)longRunningProcessWithCompletionBlock:(void (^)(void))completionBlock {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this is running on the concurrent thread, so does completionBlock() as it has been called on a concurrent thread.
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:3];
completionBlock();
});
}
So Basically the result of above will be "Is running on the main thread? NO"
Again I have exact same call on viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self longRunningProcessWithCompletionBlock:^{
NSLog(#"Is running on the main thread? %#", [NSThread isMainThread] ? #"YES" : #"NO");
}];
}
But this time, I'm calling completionBlock of longRunningProcessWithCompletionBlock on the main thread as follow:
- (void)longRunningProcessWithCompletionBlock:(void (^)(void))completionBlock {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:3];
//notice the difference, this time we are calling the completionBlock on the main thread, so that block will be running on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
});
}
This time because we have called the completion block on the main thread, the result will be Is running on the main thread? YES
So in a nutshell, block does not guarantee that they are getting executed on the main thread! but they can guarantee that they will be executed on whatever thread they've been called from.
In your case Parse.com developers are calling the completion handler block of deleteInBackgroundWithBlock on the main thread and that's why you saw that log was "yes".So you just need to call [MBProgressHUD hideHUDForView:self.view animated:YES]; without dispatch_async(dispatch_get_main_queue(), ^{ }); (as it is already on the main thread and this is an extra unnecessary step)

Activity indicator not animating when creating a dispatch group in iOS

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.

dispatch_async and frame rate

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];
}

Resources