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!
Related
I've come to a problem where proper threading is needed, but I can't seem to optimise it correctly.
Here's my method:
-(void) method1
{
// -1 to an NSInteger
nsint1--;
[self showActiviyIndicator:YES]; //act as loading screen
[alloc database etc stuffs and retrieving of data here]
//for loop here to check with database, and grey out button depending on database values
for (int i = 1; i<12; i ++)
{
//get values from database and store into variables, then grey out the button if variables are 0.
}
int Val1 = [get from database]
if Val1 = 0
[button setTitleColor:[UIColor Grey]];
someLabel.text = [NSString stringWithFormat:#"%ld", (long)nsint1];
//here's where the problem lies
[self refreshTableSessionList:xx];
[self showActiviyIndicator:NO]
}
inside [self refreshTableSessionList:xx], there's a
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
to get data from server database, then a
dispatch_async(dispatch_get_main_queue(),
to populate and reload tableViewCell.
But there'll be a conflict for when I put a
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
before [alloc database etc stuffs and retrieving of data here]
and put dispatch_async(dispatch_get_main_queue(),
when greying out the button, but that's inside a loop, which i don't think it is the right way.
What's the solution to overcome this?
As I understood you don't wait for the finish of the background database stuff.
Have you read about multithreading? For example, Ray's article.
In a simple way, you can call dispatch_async inside the dipatch_async block inside the dispatch_async and etc.
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do some database stuff
dispatch_async(dispatch_get_main_queue(), ^{
// do some UI stuff
});
});
So you should switch between the main thread and a global queue. Also, you can use delegates, notifications or even reactivity for such purposes.
I have table view that load new data (depend on page) from SQL data base. Problem is, when i load it in main thread, it block UI for a while. When i try to do "hard work" in background, and reload data in main thread, odd things start to happen, for example, table view section header move in wrong place, and i load enormous amount of data.
First case, all work but block UI for while:
[self.tableView addInfiniteScrollingWithActionHandler:^{
#strongify(self)
if (!self.viewModel.isUpdating){
self.viewModel.isUpdating = YES;
[self.tableView.infiniteScrollingView startAnimating];
[self.viewModel nextPage];
[self.tableView reloadData];
self.viewModel.isUpdating = NO;
}
}];
In second case, i tried to do background work, following not work as expected:
if (!self.viewModel.isUpdating){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Add some method process in global queue - normal for data processing
self.viewModel.isUpdating = YES;
[self.tableView.infiniteScrollingView startAnimating];
[self.viewModel nextPage];
dispatch_async(dispatch_get_main_queue(), ^(){
//Add method, task you want perform on mainQueue
//Control UIView, IBOutlet all here
[self.tableView reloadData];
self.viewModel.isUpdating = NO;
});
//Add some method process in global queue - normal for data processing
});
}
}];
How should i modify my code to not load main thread, and without "weird" things?
have you tried something like this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
.....
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelectorOnMainThread:#selector(updateView) withObject:nil waitUntilDone:YES];
});
});
......
-(void)updateView{
[self.tableView reloadData];
self.viewModel.isUpdating = NO;
}
user PerformSelectorOnMainThread it may help you.
I am developing iPhone app in which i am downloading image from server in background,
Here is view of my application,
when i click on Button 1 i am fetching 5 data from server also images, after fetching data when user scrolls up i am fetching new 5 data from server again when user scrolls up i am fetching new 5 data from server and so on.
while fetching data for Button 1 if i click on Button 2 am cancelling my previous thread of Button 1 and i am fetching new 5 data for Button 2 and on scrolling it again fetching new 5 data Same as Button 1
but after some time while reloading a tableview my app gets crashes and shows:
Terminating app due to uncaught exception 'NSRangeException', reason: ' -[__NSArrayM objectAtIndex:]: index 28 beyond bounds [0 .. 4]
Here is my code snippet:
- (void)viewDidLoad
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self fetchDataForButton1];
dispatch_async(dispatch_get_main_queue(), ^(void) {
[tableView reloadData];
});
});
}
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
if(it is last row then fetch new 5 data)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Code runs on background thread
[self LoadMoreData];
dispatch_async(dispatch_get_main_queue(), ^(void) {
//Code here is run on the main thread
[_tblList reloadData];
});
});
}else{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self beginBackgroundFetchTask];
[self downloadImage_3:indexPath];
[self endBackgroundFetchTask];
});
}
}
-(void)downloadImage_3:(NSIndexPath *)path{
UIImage *img = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:ImagePath]]];
if (img) {
[dicImages_msg setObject:img forKey:[[msg_array objectAtIndex:path.row] valueForKey:#"Merchant_SmallImage"]];
}
[_tblList performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
- (void) beginBackgroundFetchTask
{
self.backgroundFetchTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundFetchTask];
}];
}
- (void) endBackgroundFetchTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundFetchTask];
self.backgroundFetchTask = UIBackgroundTaskInvalid;
NSLog(#"ended BackgroundFetchTask");
}
-(void)LoadMoreData
{
//Fetches new 5 data from server...
}
I think the problem maybe at [self downloadImage_3:indexPath]; you call dispatch_async when you at Button1, and the dispatch_async block have not invoked, then you click on Button2, then I guess msg_array is cleared and filled with new 5 object, after that, dispatch_async block is invoked, the path.row for block is 28, whereas, the msg_array has new array content with 5 new objects, then crash.
You should cancel dispatch_async before click on other button, which is impossible for dispatch_async , so you can have a judgement in block:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self beginBackgroundFetchTask];
[self downloadImage_3:indexPath buttonIndex:index];
[self endBackgroundFetchTask];
});
-(void)downloadImage_3:(NSIndexPath *)path buttonIndex:(int)buttonIndex{
if(self.currentSelectIndex != buttonIndex) return; //skip reloadData if not same index
UIImage *img = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:ImagePath]]];
if (img) {
[dicImages_msg setObject:img forKey:[[msg_array objectAtIndex:path.row] valueForKey:#"Merchant_SmallImage"]];
}
[_tblList performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
Try this out
1) Use SDWebImage for fetching Images from Server
2) Use Pull Refresher feature for fetching next 5 data.
This will helps to fetch data in background.
Use SDWebImage for fetching Images from Server.
Disable user interaction then go to top on table:
[TableView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
then call:
[TableView reloadData];
then enable userintercation.
hope will solve the problem
This is because images take time to load from server and cell gets no data when it is allocated.
You need to implement lazy loading for it.
Here is a good tutorial.
I would not fetch data like that from the cellForRowAtIndexPath method. That is used to display cells.
Instead do it like this:
List your original data.
When the user scrolls past the last cell, show a footer that displays a loading symbol.
at the same time While the footer is displaying a loading symbol, then and ONLY then call the fetchLoadData method, and WAIT
Until you dont hear from the server, continue displaying the loading symbol, dont start adding new cells to prepare for anything, because chances are you might either not get anymore data back, which at this point you have to get rid of the footer cell and let the user know that there is no more data to retrieve, OR if there is more data to display, THEN set up your next 5 cells by doing the following in this order:
adding your new 5 data objects to your data array
updating the number of rows in section using the numberOfRowsInSection delegate method
You will find that your cells if coded correctly will be displayed correctly once you refresh the table using [myTable reloadTable];
AS per your log array is having only 5 count and you are fetching the data as per indexpath.row and in this crash case is 28. So it might be that you are adjusting your cell indexpath row according to your new array which is fetching again and again data on scrolling but in the repetition of 5.
Just put a breakpoint and check whether you are resetting the indexpath row in count of [0 4];
UPDATED:
[[NSThread currentThread] populateDataInBackground];
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];
});
});
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
}