OK I'm hoping I'm missing something basic here - I am not very expert at this. It should be self-explanatory without example code:
I parse a web-hosted xml file consisting of a list of titles to be displayed in a tableView and associated URLs to pass to a webView when a cell is selected. The parsing happens in the tableView into a dictionary. If I parse on the main thread it works nicely but I'm worried about hanging the UI if the signal is poor. So I wrap the parsing call in a dispatch queue as per examples on here and now it presents an empty table. But if I go back up the view hierarchy and try again (it's embedded in a navigation controller) then it works, there is my table fully populated.
I'm assuming that by using a secondary thread somehow the table is created before the content array is populated. How do I get round this?
Thanks! Andrew
Implement the - (void)parserDidEndDocument:(NSXMLParser *)parser delegate method of NSXMLParser. And call reloadData of your tableView from that method.
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
dispatch_sync(dispatch_get_main_queue(), ^{
[yourTable reloadData];
});
}
Refer NSXMLParserDelegate
If you pares in a dispatch queue you have to update the UI on the main queue.
I am doing something similar. Here is my code:
dispatch_queue_t imgDownloaderQueue = dispatch_queue_create("imageDownloader", NULL);
dispatch_async(imgDownloaderQueue, ^{
NSString *avatarUrlString = [avatarImageDictionary objectForKey:#"url"];
avatarImage = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:avatarUrlString]]];
dispatch_sync(dispatch_get_main_queue(), ^{
id asyncCell = [self.tableView cellForRowAtIndexPath:indexPath];
[[asyncCell avatarImageView] setImage:avatarImage];
});
});
Related
In my model, data is downloaded from website in a for loop and in a each turn data is sending to my viewController using protocol method. In model file;
for(NSString* data in DataArray){
[self.delegate passUpdatingCourse:data_name];
//other operations
}
In my viewController data name coming from model is saving to NSArray property in other thread;
ModelClass *modelObject = [[ModelClass alloc] init];
[modelObject setDelegate:self];
dispatch_queue_t otherQ = dispatch_queue_create("Q", NULL);
dispatch_async(otherQ, ^{
//other operations
[self performSelectorOnMainThread:#selector(passUpdatingCourse:) withObject:nil waitUntilDone:YES];
dispatch_async(dispatch_get_main_queue(), ^{
[self.myIndicator stopAnimating];
self.indicatorText.hidden = YES;
[self.changingCourseLabel setNeedsDisplay];
});
});
And also data coming via protocol method is setting viewController label;
-(void)passUpdatingCourse:(NSString *)data_in{
self.myLabel.text = data_in;
}
When each data came, myLabel in a viewController must be update. But it is not happens. In a protocol method when I use this;
NSLog(#"Data:%#",self.myLabel.text);
Yeah it shows data in a console but myLabel in a view is not changing.
I searched questions like that but couldn't find a solution.
Assuming your loop is on a background thread, dispatch your label updates to the main queue (async).
If you're inside a for loop on the main thread nothing is going to get updated in the UI until your method returns and dispatch won't help in that case.
I am new in iOS.
I am trying to implement upload data in uitableview one by one row.
for that i am using background task.
using following code.
-(void)MethodUploadBgTaskAssign
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *responseString = [self MethodlblUploadClicked];
dispatch_async(dispatch_get_main_queue(), ^(void)
{
if([responseString isEqualToString:#"UploadSuccess"])
{
[self ReloadTblData];
[self ContinueUploadData];
}
});
});
}
-(void) ReloadTblData
{
if([dataArr count]>0)
[dataArr removeObjectAtIndex:0];
[uitblData reloadData];
}
-(void) ContinueUploadData
{
if((dataArr count]>0)
[self MethodUploadBgTaskAssign];
}
My problem is uploading data in table after some time table reload with empty data
because all data uploaded at that time.
I want show updated ui after uploading each cell in table.
What will be necessary changes in code?
appreciate for help.
It looks to me as if you are updating the table - but in the wrong thread (hence the table never actually appears to update). You need to use performSelectorOnMainThread to update the UI.
[self performSelectorOnMainThread:#selector(ReloadTblData:) // Update the table on the main thread
withObject:nil
waitUntilDone:NO];
I think this should work - give it a go!
I have a UITableView (TV) with several sections, each section has an NSArray that serves as the dataSource (no CoreData, no images). When the user opens the TV, my app does some intensive calculations to generate the dataSource arrays. In some cases, the calculations can take some time, and what happens then is that the section headers show first, after which the cells appear, which doesn't like good, I think.
I'm already using GCD to do the calculations:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear: animated];
[MBProgressHUD showHUDForView: self.view animated: YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self.model generateData];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView: self.view animated: YES];
[self.tableView reloadData];
});
});
}
Besides trying to optimize the calculations, is there anything else I could do to make this look smoother? For instance, is there a way for the section headers not to appear until the calculations are done?
UPDATE:
So in the end, my solution turned out to be different. To generate my data I am now using a dispatch_group, and calculate theNSArray for each section in andispatch_group_async block, so they run concurrently. This already was an improvement in speed. Furthermore, I start the calculation already in the UIViewController from which the user opens the TV. Therefore, the data is available almost instantly when the TV opens, and all sections load smoothly.
Here is a code snippet for completeness:
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) ^{
[self.model generateArray1];
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) ^{
[self.model generateArray2];
});
//... etc for each section
// make sure that everything is done before moving on
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
If you return nil from tableView:titleForHeaderInSection: then the header won't be shown, so add a small amount of conditional logic which checks if the data is loaded yet and either returns nil (if not loaded) or the section title (if it is loaded).
In my app, I have a UITableViewController.
Its tableView is divided in 3 sections.
I download datas for each of those sections from my server. To do this, I have 3 functions (for example f1 f2 and f3). Each updates a corresponding NSArray, used as data source for my table.
Now what I want is to reload datas using this functions and refresh my tableView once this 3 functions are done, but without disturbing the user.
I'm not used with asynchronous request, blocks, threads etc... and I'm looking for tips.
Actually, here is what I do :
-(void)viewDidLoad
{
//some settings
[NSTimer scheduledTimerWithTimeInterval:15.0 target:self selector:#selector(reloadDatas) userInfo:nil repeats:YES];
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
[self reloadDatas];
});
}
-(void)reloadDatas
{
dispatch_queue_t concurrentQueue = dispatch_get_main_queue();
dispatch_async(concurrentQueue, ^{
[self f1];
[self f2];
[self f3];
[myDisplayedTable reloadData];
});
}
-(void)f1
{
//load datas with a url request and update array1
}
-(void)f2
{
//load datas with a url request and update array2
}
-(void)f3
{
//load datas with a url request and update array3
}
But here, my tableView is "frozen" until it is refreshed.
I don't care about the order of execution of f1 f2 and f3, but I need to wait for this 3 functions to be done before refresh my tableView.
Thanks for your help.
EDIT
Thanks for all your answers.
Here is the working solution :
As mros suggets, I removed the dispatch queue from the viewDidLoad, and replace in reloadDatas:
dispatch_queue_t concurrentQueue = dispatch_get_main_queue();
with
dispatch_queue_t mainThreadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
And finally, I reload my table in a main thread
dispatch_async(dispatch_get_main_queue(), ^{ [myDisplayedTable reloadData]; });
So your "background thread" is actually your main thread. You have to use dispatch_get_global_queue and specify a priority to actually get a different thread. Also, the dispatch async in viewDidLoad is useless as all view controller lifecycle methods are called in the main thread. I would recommend doing something as follows in your f1, f2 and f3 methods:
Start by launching an asynchronous url request, then in the completion block, update arrayX and reload a particular section of your tableview. This way all three requests can happen simultaneously and the table just updates the necessary data when each one finishes. Alternatively, if you only want to reload once, just replace the concurrentQueue variable you have with a background thread and then perform [tableView reloadData] on the main thread.
The previous answers are absolutely right. However your implementation of reloadDatas & viewDidLoad is a bit problematic.
Just to clarify:
You want to complete the time consuming data loading stuff in a background thread, then update the UI/Cells when your data is ready on the main thread.
Like so:
-(void)viewDidLoad
{
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.my.backgroundQueue", NULL);
dispatch_async(concurrentQueue, ^{
[self reloadDatas];
});
}
-(void)reloadDatas
{
// Expensive operations i.e pull data from server and add it to NSArray or NSDictionary
[self f1];
[self f2];
[self f3];
// Operation done - now let's update our table cells on the main thread
dispatch_queue_t mainThreadQueue = dispatch_get_main_queue();
dispatch_async(mainThreadQueue, ^{
[myDisplayedTable reloadData]; // Update table UI
});
}
One other thing. Pulling data from a server and updating table cells is pretty common.
No need for queues or timers here.
Here's an alternative structure.
Say you're pulling mp3's from your server :
Your model class is : Music.h/m
Your Model manager is : MusicManager.h/m (Singleton) - it will contain an array of music objects - that singleton is basically your datasource;
and finally your UItableViewController : MusicTableVC.h/m
In MusicManager.h/m : You have an NSMutableArray which will be loaded with Music.h objects that you've pull from the server. You can do that as soon as you app loads without even waiting for the TableViewController.
Inside MusicManager you have a few helper methods to add or remove items from the mutableArray and provide the count and of course your networking methods.
Finally : Post a notification in your network code. Your UITableViewController should listen/observe that notification and "reload" accordingly.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NewMusicAdded" object:nil];
You query data from your server, parse the data into Music objects add them to your NSMutable array and post a notification to let the table update itself.
Pretty standard recipe.
In reloadDatas method you should change this line:
dispatch_queue_t concurrentQueue = dispatch_get_main_queue();
To:
dispatch_queue_t concurrentQueue = dispatch_queue_create("some queue", NULL);
But when you call [myDisplayedTable reloadData], you need to call this operation in the main queue.
dispatch_async(dispatch_get_main_queue(), ^{ [myDisplayedTable reloadData]; });
Folks,
I'd like to get your opinions on the following scenario. Most screens on my app are table views where the number of rows and contents of the table view is determined by first reading data from the local core data tables and then performing some complex calculations on it. I'd like to do this in a way where the app does not freeze while the user is transitioning from one screen to another. Here is how I have done it. In the view did appear function I start animating an activity indicator and then spawn a thread to read data from the core data tables and perform all the relevant calculations on it. Inside this thread, upon completion of the calculations, I stop animating the activity indicator, mark a flag that initialization is complete and then reload the table view. Load of table view cells before the initialization is complete will return empty cells. (I noticed that the table view data source functions are called immediately after viewWillAppear and before ViewdidAppear()). Pasted below is my code:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(#"%s",__FUNCTION__);
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"%s",__FUNCTION__);
[activityOutlet startAnimating];
dispatch_async(myQueue, ^{ [self getFromCoreData];
});
}
- (void) getFromCoreData {
// Get from coredata and start calculations here
[activityOutlet stopAnimating];
activityOutlet.hidden = YES;
[tableOutlet reloadData];
}
I'd like to know if there is a better way of doing the above.
Thanks in advance for your responses!
UI updates must be done on the main thread:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getFromCoreData];
dispatch_async(dispatch_get_main_queue(), ^{
activityOutlet stopAnimating];
activityOutlet.hidden = YES;
[tableOutlet reloadData];
});
});
}
- (void) getFromCoreData {
// Get from coredata and start calculations here
}