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
}
Related
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 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).
I'm using the MBProgressHUD library in my app, but there are times that the progress hud doesn't even show when i query extensive amount of data, or show right after the processing of data is finished (by that time i don't need the hud to be displayed anymore).
In another post i found out that sometimes UI run cycles are so busy that they don't get to refresh completely, so i used a solution that partially solved my problem: Now every request rises the HUD but pretty much half the times the app crashes. Why? That's where I need some help.
I have a table view, in the delegate method didSelectRowAtIndexPath i have this code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[NSThread detachNewThreadSelector:#selector(showHUD) toTarget:self withObject:nil];
...
}
Then, I have this method:
- (void)showHUD {
#autoreleasepool {
[HUD show:YES];
}
}
At some other point I just call:
[HUD hide:YES];
And well, when it works it works, hud shows, stays and then disappear as expected, and sometimes it just crashes the application. The error: EXC_BAD_ACCESS . Why?
By the way, the HUD object is already allocated in the viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
...
// Allocating HUD
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
HUD.labelText = #"Checking";
HUD.detailsLabelText = #"Products";
HUD.dimBackground = YES;
}
You need to perform your processing on another thread, otherwise the processing is blocking MBProgressHud drawing until it completes, at which point MBProgressHud is hidden again.
NSThread is a bit too low-level for just offloading processing. I'd suggest either Grand Central Dispatch or NSOperationQueue.
http://jeffreysambells.com/2013/03/01/asynchronous-operations-in-ios-with-grand-central-dispatch
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
/* Prepare the UI before the processing starts (i.e. show MBProgressHud) */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/* Processing here */
dispatch_async(dispatch_get_main_queue(), ^{
/* Update the UI here (i.e. hide MBProgressHud, etc..) */
});
});
This snippet will let you do any UI work on the main thread, before dispatching the processing to another thread. It then returns to the main thread once the processing is done, to allow you to update the UI.
I have an PlayPageViewController as rootViewController. The PlayPageViewController will display 3D models and UIImages in an method call editPages(), which will takes several seconds to wait.
I just want to add an loadingView at start and when PlayPageViewController gets fully loaded it will disappear.
Here is my solution:
Add an loadingView with activityIndicator.
When the loadingView is loaded, I will begin to implement
but seems it didn't work
STLoadingViewController *loadingView =
[[STLoadingViewController alloc]initWithNibName:#"STLoadingViewController"
bundle:nil];
loadingView.view.frame=CGRectMake(0, 0, 1024, 768);
[self.view insertSubview:loadingView.view atIndex:3];
if(loadingView.isViewLoaded && loadingView.view.window)
{
[self.view insertSubview:self.playPageViewController.view atIndex:4];
[self.playPageViewController setEditingPage:pageIndex];
[loadingView.view removeFromSuperview];
}
You have to do your respective methods to call in viewDidAppear method when this method is called all the appearing task had been finished.
What is the ViewLoad() method? Do you mean viewDidLoad:? You could setup all your views in the storyboard, including a view containing the activity indicator. Don't load the model at this point but wait until viewDidLoad: is called. At this point you may use Grand Central Dispatch to start the loading of the model. It could look a bit like this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// You need to setup the activity indicator outlet in the storyboard
[_activityIndicator startAnimating];
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Do the expensive work on the background
[NSThread sleepForTimeInterval:5.0f];
// All UI related operations must be performed on the main thread!
dispatch_async(dispatch_get_main_queue(), ^{\
// Replace the view containing the activity indicator with the view of your model.
[_activityIndicator stopAnimating];
NSLog(#"STOP");
});
});
}
Edit: The link seems to be down. This is probably due to the current problems with the Dev Center. You can find the documentation in the documentation pane of the Xcode Organizer.