I have a class that runs similar to the AFHTTPSessionManager component of this tutorial http://www.raywenderlich.com/59255/afnetworking-2-0-tutorial
However, [self.tableView reloadData] is not working for me.
I have the manager implemented as so:
-(void) refresh{
manager = [[AFHTTPSessionManager...] iniwithBaseURL:...];
[manager Get:... parameters:... success:^(NSURLSessionDataTask *task, id responseObject){
//test success values in responseObject
if(test){
//Get table data
[self.tableView reloadData];
}
}
....
}
However if I run [self.tableView reloadData] in a separate function afterwards, it works just fine.
Why is this happening, instead of how it should in the tutorial?
Always reload on the main queue:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
write the [self.tableView reloadData];in the main queue.
dispatch_sync(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
Related
I applied this code in my tableview. However, i realise that data will load slow for first time request. After that, if i access to the same page, the records will be loaded faster. Any idea to improve for first time loading? Thanks for your help.
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:#"http://api.domainname.com/api/abc" parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
_serverDataArr = responseObject;
self.dataArr=[NSMutableArray array];
for (NSDictionary *subDic in self.serverDataArr) {
Friend_Model *model=[[Friend_Model alloc]initWithDic:subDic];
[self.dataArr addObject:model];
}
_rowArr=[Friend_DataHelper getFriendListDataBy:self.dataArr];
_sectionArr=[Friend_DataHelper getFriendListSectionBy:[_rowArr mutableCopy]];
[self.tableView reloadData];
} failure:^(NSURLSessionTask *operation, NSError *error) {
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:#"Error Retrieving Ninjas"
message:[error localizedDescription]
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"Ok"
style:UIAlertActionStyleCancel
handler:nil];
[alertVC addAction:okAction];
[self presentViewController:alertVC animated:YES completion:nil];
}];
And here is my cellForRowAtIndexPath code :-
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIde=#"cellIde";
Friend_TableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIde];
if (cell==nil) {
cell=[[Friend_TableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIde];
}
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
if (tableView==_searchDisplayController.searchResultsTableView){
NSString *url_Img_FULL = [_searchResultArr[indexPath.row] valueForKey:#"image"];
[cell.headImageView setImageWithURL:[NSURL URLWithString:url_Img_FULL] placeholderImage:[UIImage imageNamed:#"Placeholder.png"]];
[cell.nameLabel setText:[_searchResultArr[indexPath.row] valueForKey:#"name"]];
}else{
Friend_Model *model=_rowArr[indexPath.section][indexPath.row];
[cell.headImageView setImageWithURL:[NSURL URLWithString:model.image] placeholderImage:[UIImage imageNamed:#"Placeholder.png"]];
[cell.nameLabel setText:model.name];
}
return cell;
}
It looks like you're doing perfectly appropriate stuff to form the request and present the results. You might be seeing a big difference between the 1st and nth request because your back-end is awake, and data has been cached at various points on the stack. Hard to suggest much here besides moving the request earlier, maybe to app startup, where the user might not notice as much delay.
Another idea would be to request data for table rows more lazily, based on where the table is scrolled. When user gestures a pull at the bottom of the table, show a busy indicator and go get more data.
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)
I am utilizing MBProgressHUD along with STTwitter...I call MBProgressHUD and then load the twitter feed off the main thread and hide the HUD thereafter. Unfortunately, the HUD hides as soon as it receives a response and not necessarily after the data has completely downloaded. Is there a solution to this? This also occurs with webviews elsewhere in the app. Thanks!
[[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//Sets the auth key (user or app) for the RMACC Twitter Feed
STTwitterAPI *twitter = [STTwitterAPI twitterAPIOSWithFirstAccount];
[twitter verifyCredentialsWithSuccessBlock:^(NSString *username) {
[twitter getUserTimelineWithScreenName:#"RMACCNewsNotes" count: 10 successBlock:^(NSArray *statuses) {
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
self.twitterFeed =[NSMutableArray arrayWithArray:statuses];
[self.tableView reloadData];
}
errorBlock:^(NSError *error){
}];
} errorBlock:^(NSError *error) {
[self twitterselfauthrequest];
}];
});
}
I would suggest using SVProgressHUD which is easy to use, implements Singleton, and with much more functionality and control
Move your HUD hiding code inside successBlock as follows
successBlock: ^(NSArray *statuses) {
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
self.twitterFeed =[NSMutableArray arrayWithArray:statuses];
[self.tableView reloadData];
}
Try changing dispatch_async to dispatch_sync.
I think this might be happening because of some issues dispatch_async has on main thread.
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.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0ul);
dispatch_sync(queue,^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:fileUrl]];
NSDate *now=[NSDate date];
self.time = [self DateFormatWithString:now];
QBChatMessageCustom *customMessage=[[QBChatMessageCustom alloc]init];
customMessage.senderID = [[[NSUserDefaults standardUserDefaults]objectForKey:#"jabber_id"] intValue];
customMessage.recipientID=opponent.ID;
customMessage.text=self.messageDBStr;
customMessage.imageDataV=[[NSData alloc]initWithData:data];
customMessage.datetime=[self StringWithDate:self.time];
[self.messages addObject:customMessage];
NSInteger currentDate = [[NSDate date] timeIntervalSince1970];
if(currentDate - [self.opponent.lastRequestAt timeIntervalSince1970] > 300)
{
// Send push
[QBMessages TSendPushWithText:self.messageDBStr toUsers:[NSString stringWithFormat:#"%d", self.opponent.ID]delegate:self];
}
//[HUD setHidden:YES];
[self SenderIDInsertDB:customMessage.senderID recipient:customMessage.recipientID TextMessage:customMessage.text DATA:customMessage.imageDataV dateFormat:customMessage.datetime];
// reload table
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[messages count]-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});
This process will be completed after that TabelView automatically scroll down. but when when process is running at that time TabelView will be hang up.
Any process that is related to the UI must be on the Main thread, so modify your code to this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0ul);
dispatch_sync(queue,^{
your service call code ..........
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[messages count]-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
});
You may go in an infinite loop when calling
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[messages count]-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
Does this scrolling of the tableView trigger the call of your method again (and thus a new scroll and so on...)?
Try NSOperationQueue and NSOperation for multi-threading in the app i.e. view will be automatically updated when you get the response from web service.
Check Class Reference for NSOperationQueue
https://developer.apple.com/library/ios/documentation/cocoa/reference/NSOperationQueue_class/Reference/Reference.html
And have a look on very useful tutorial by Ray
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
Cheers.