UI is not updating on background thread - ios

I am running an update on an Sqlite3 database in the background when the user presses a force update button.
I want to disable the button as to not lock the database and keep the user from pressing it over and over again. Plus I want to show an Activity Indicator. However, the button is not disabling and the activity indicator does not show.
What am I doing wrong?
I hide the activity indicator when the view is loaded.
Built with storyboards:
View did load
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//other going on
[self updateUIInterface:false];
}
The method to update the UI
- (void) updateUIInterface : (BOOL) updating {
if (updating) {
//Disable buttons and show activity indicator
self.actLocalDB.hidden = NO;
[self.actLocalDB startAnimating];
self.btnSyncLocal.enabled = NO;
[self.btnSyncLocal setTitle:#"Updating.." forState:UIControlStateDisabled];
[self.btnSyncLocal setUserInteractionEnabled:NO];
} else {
// Enable buttons
self.actLocalDB.hidden = YES;
[self.actLocalDB stopAnimating];
self.btnSyncLocal.enabled = YES;
[self.btnSyncLocal setTitle:#"Sync Databases" forState:UIControlStateDisabled];
[self.btnSyncLocal setUserInteractionEnabled:YES];
}
}
My method to update the DB
- (IBAction)syncLocalDB:(id)sender {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Begin Local DB Sync");
[self updateUIInterface:true];
//db stuff goes here
dispatch_async(dispatch_get_main_queue(), ^{
//update UI here
NSLog(#"Done updating local db");
[self updateUIInterface:false];
});
});
}

You can't make UI changes in background threads. All UI operations need to be performed on the main thread. Here is a nice blog post on the topic and a link to the docs.

Just call updateUIInterface Method before entering the GCD-Block.
- (IBAction)syncLocalDB:(id)sender {
[self updateUIInterface:true];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Begin Local DB Sync");
//db stuff goes here
dispatch_async(dispatch_get_main_queue(), ^{
//update UI here
NSLog(#"Done updating local db");
[self updateUIInterface:false];
});
});
}

Related

iOS UIView is not updated immediately

When I do UI updates from the main thread, they do not seem to take effect straight away. That is, the changes do not appear immediately on screen.
Here is a simplified version of the code I'm running:
- (void) do_ui_update {
// UI update here that does not appear immediately
}
- (void) some_time_consuming_function {
// line 1
// line 2
// ...
// line n
}
- (void) function_that_runs_in_main_thread {
[self RUN_ON_UI_THREAD:^{
[self do_ui_update];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self some_time_consuming_function];
});
}];
}
- (void) RUN_ON_UI_THREAD:(dispatch_block_t)block
{
if ([NSThread isMainThread])
block();
else
dispatch_sync(dispatch_get_main_queue(), block);
}
When I debug setting breakpoints at each line in some_time_consuming_function, sometimes the UI updates appear on screen when the debugger hits line 2, sometime line 3, and so on.
So my question is:
How can I make the UI updates appear on screen before the very first line of some_time_consuming_function is reached?
Dispatch the background dispatch to the next main loop iteration:
-(void) function_that_runs_in_main_thread {
[self do_ui_update];
dispatch_async(dispatch_get_main_queue(), ^{
// All pending UI updates are now completed
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self some_time_consuming_function];
});
});
}

iOS: how to properly stop an Activity Indicator?

I'd like to stop the animation of the indicator within a method called by default NSNotificationCenter with a postNotificationName.
So I'm doing this on Main Thread
-(void)method
{
...
[ind performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
}
It does'n work. Method is called correctly, any other called selectors do their job but not stopAnimating.
I put [ind stopAnimating] in another function and then called it via performSelectorOnMainThread but it still didn't worked.
Try this...
Create a method that stops your animation
-(void)stopAnimationForActivityIndicator
{
[ind stopAnimating];
}
Replace your method like this -
-(void)method
{
...
[self performSelectorOnMainThread:#selector(stopAnimationForActivityIndicator) withObject:nil waitUntilDone:NO];
}
Should do the magic...
You can also use the below method which starts and stops the activity indicator on main thread in a single method, also provides you to execute your code asynchronously as well-
- (void)showIndicatorAndStartWork
{
// start the activity indicator (you are now on the main queue)
[activityIndicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do your background code here
dispatch_sync(dispatch_get_main_queue(), ^{
// stop the activity indicator (you are now on the main queue again)
[activityIndicator stopAnimating];
});
});
}
Try :
-(void)method
{
dispatch_async(dispatch_get_main_queue(), ^{
[ind stopAnimating];
});
}

iOS : using activity indicator in loading for second view controller

I have two view controllers; in the second view, a bunch of data are processed which takes pretty much time, while in the first view, there is a button navigating to the second. I want to display an activity indicator for the process in the second view right after the button clicked. But initialising UIActivityIndicatorView in the second view doesn't seem to work. Nothing showed up when the button was clicked, and the app was stuck in the first view when data being processed.
Below are the code I wrote in viewDidLoad in the second view controller.
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator setCenter:CGPointMake(SCREEN_WIDTH/2, SCREEN_HEIGHT/2)];
[self.view addSubview:_activityIndicator];
...............
[_activityIndicator startAnimating];
...............
// data processing
[_activityIndicator stopAnimating];
Anyone know how to solve this?
========EDIT=========
Thank you so much for the advices. Now I've tried using NSThread,but the spinner showed up pretty late. Here are the code I wrote in the first view controller.
- (void)viewDidLoad
{
[super viewDidLoad];
// activity indicator
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator setCenter:CGPointMake(SCREEN_WIDTH/4, SCREEN_HEIGHT/4)];
[self.view addSubview:_activityIndicator];
}
- (IBAction)startButtonClicked:(id)sender
{
[NSThread detachNewThreadSelector:#selector(threadStartAnimating:) toTarget:self withObject:nil];
}
-(void)threadStartAnimating:(id)data
{
NSLog(#"start");
[_activityIndicator startAnimating];
[self performSelectorOnMainThread:#selector(threadStopAnimating:) withObject:nil waitUntilDone:YES];
}
-(void)threadStopAnimating:(id)data
{
NSLog(#"stop");
[_activityIndicator stopAnimating];
}
The spinner appeared around 2 sec after NSLog(#"start"); being executed and showed up in a very short period. I linked - (IBAction)startButtonClicked:(id)sender with the button that navigated to the second view.
Is there any better way to put [_activityIndicator startAnimating];?
Maybe you can try to solve this issue with Grand Central Dispatch (threads).
In you second VC, try this code:
- (void) viewDidLoad{
[super viewDidLoad];
// Main thread
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator setCenter:CGPointMake(SCREEN_WIDTH/4, SCREEN_HEIGHT/4)];
[self.view addSubview:_activityIndicator];
[_activityIndicator startAnimating];
// create a queue
dispatch_queue_t queue = dispatch_queue_create("data_process", 0);
// send a block to the queue - Not in Main thread
dispatch_async(queue, ^{
// data processing
.................
// Interaction with User Interface - Main thread
dispatch_async(dispatch_get_main_queue(), ^{
[_activityIndicator stopAnimating];
_activityIndicator.hidden = YES;
});
});
}
I hope this helps.

UIAcitivity Indicator with UIAlertView

I am developing an app where the user will get to confirm some action via UIAlertView, if he confirms, I call a method that handles the operation, then I prepare to pop the view I am in to go back to another view after the method has been called.
I want to show UIActivityIndicatorView if the user presses confirm for as long as it takes to execute the method and go to that other view. I used startAnimating and stopAnimating in the proper location, but i never get to see the UI UIActivityIndicatorView shown, not for a sec.
I guess its related to some UI issues due to UIAlertView, not sure if I am correct though. I just need a clue on how to use UIActivityIndicatorView properly for a method execution time.
My code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.activityIndicator.alpha = 1.0;
self.activityIndicator.hidesWhenStopped = YES;
self.activityIndicator.center = self.view.center;
[self.view addSubview:self.activityIndicator];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(buttonIndex == 1) {
[self.activityIndicator startAnimating];
ContactsTableViewController *contactTableView = [self getContactsTVC];
[contactTableView applyActionOnCells];
// doing some setup before poping off to the root view controller of my nav controller
[self.activityIndicator stopAnimating];
// then go to rootViewController
[self.navigationController popToRootViewControllerAnimated:YES];
}
}
I'm not 100% certain, but try to comment out the stopAnimating call and see if it shows up.
If that helps, applyActionOnCells probably blocks your main thread (where all UI stuff also happens) and the indicator never has a chance to show up before you hide it again.
In that case, try do the applyActionOnCells call in the background:
if(buttonIndex == 1) {
[self.activityIndicator startAnimating];
__block typeof(self) bself = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
ContactsTableViewController *contactTableView = [bself getContactsTVC];
[contactTableView applyActionOnCells];
dispatch_async(dispatch_get_main_queue(), ^{
[bself.activityIndicator stopAnimating];
// then go to rootViewController
[bself.navigationController popToRootViewControllerAnimated:YES];
});
});
}
Edit: see also an earlier question.

User left current view before dispatch_get_main_queue() executed

I want to make all reading/writing database operations to background queue and update the current UI view when completed.
There is no problem if user stays in the view while I'm dealing with my database. However, if user left that view before database operations completed, it would crash. The psuedo code is as below:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
/* save data to database, needs some time */
dispatch_async(dispatch_get_main_queue(), ^{
// back to main queue, update UI if possible
// here may cause crash
[self.indicator stopAnimating];
[self.imageView ...];
});
});
Try checking if the view is still in the view hierarchy, and also stop the activity indicator from spinning in the viewDidDisappear method as well. You also might need a flag (isNeedingUpdate in the example below) to indicate whether the UI was updated or not, so you can do the appropriate actions if the user goes away before the update is complete and then comes back again.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
if (self.view.window) { // will be nil if the view is not in the window hierarchy
dispatch_async(dispatch_get_main_queue(), ^{
[self.indicator stopAnimating];
[self.imageView ...];
self.isNeedingUpdate = NO;
});
}else{
self.isNeedingUpdate = YES;
});
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (isNeedingUpdate) {
// do whatever you need here to update the view if the use had gone away before the update was complete.
}
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.indicator stopAnimating];
}

Resources