iOS: how to properly stop an Activity Indicator? - ios

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];
});
}

Related

UIActivityIndicatorView not staring

I am trying to set a 'UIActivityIndicatorView' as an accessoryView in one of my 'UITableViewCell', in a such way as, the ActivityIndicator start animating when the user touch this cell. I add the following code at 'didSelectRowAtIndexPath' method:
UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
UIActivityIndicatorView * activity = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
NSThread * newThread1 = [[NSThread alloc]initWithTarget:self selector:#selector(carregarNoticias) object:nil];
switch (indexPath.row) {
case 0:
cell.accessoryView = activity;
[activity startAnimating];
sleep(0.01);
[newThread1 start];
while (![newThread1 isFinished]) {
//waiting for thread
}
[activity stopAnimating];
[self.navigationController pushViewController:noticias animated:YES];
break;
I was expecting 'UIActivityIndicatorView' animating while the newthread1 is running. However, the animating only start when the newThread1 finish (and therefore I don't want the animation anymore). I added 0.01 seconds as sleep time to give time to the animation start. Nevertheless, this also did't solve.
Does anyone know what my error is? I appreciate any help!
I dont really know what you are trying to do adding delay like that.. try this line below, might be the one your look for.. and another thing.. please dont block the main thread like sleep(0.01); that, users wont like it..
cell.accessoryView = activity;
[activity startAnimating];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// the system will wait until this
dispatch_async(dispatch_get_main_queue(), ^(void){
[newThread1 start];
});
// is finished, maybe you dont need the `[NSThread sleepForTimeInterval:3];`
// pick the one that suits your implementation..
[NSThread sleepForTimeInterval:3];
dispatch_async(dispatch_get_main_queue(), ^(void){
[activity stopAnimating];
[self.navigationController pushViewController:noticias animated:YES];
});
});
[NSThread sleepForTimeInterval:3] sleep the thread like the one you have sleep(0.01);
But using it inside
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ });
makes it async. and wont block the main thread..
while the dispatch_async(dispatch_get_main_queue(), ^(void){ fires your code at the main thread..
Happy coding, cheers! :)

UI is not updating on background thread

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];
});
});
}

View is not displayed immediately

What i want to achieve :
Create processing screen with some custom icons, label etc. with following behaviour.
Add view over window which will not allow user to touch anything in application until it is removed. (like processing/loading screen)
When this view is displayed all other operation like adding subview, performing segue etc should work as they work normally but below my loading view.
Want method showProcessingScreen to work on any thread (Whatever thread switching code etc should be in respective show/hide method).
It should be displayed/removed immediately in after calling respective methods.
Code :
-(void) showProcessingScreen
{
dispatch_async(dispatch_get_main_queue(),
^{
UIStoryboard *mystoryboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
processingScreen = [mystoryboard instantiateViewControllerWithIdentifier:#"loadingViewController"];
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: processingScreen.view];
[mainWindow bringSubviewToFront:processingScreen.view];
});
}
-(void) hideProcessingScreen
{
dispatch_async(dispatch_get_main_queue(),
^{
[processingScreen.view removeFromSuperview];
});
}
Issue:
I want code above to work with showing/hiding loading screen immediately.
- (IBAction)proceedBtnPressed:(id)sender
{
[[GUIUtilities sharedObj] showProcessingScreen];
//Some other code here
}
When i call showProcessingScreen like above processing screen takes around 2-3 sec to show.
But when i remove other code below it (//Some other code) it shows screen immediately.
What i have tried:
Putting code in showProcessingScreen in other method and calling that on main thread using performSelectorOnMainThread.
Calling showProcessingScreen on background and executing show code on main thread using performSelector.
This works
//code
-(IBAction)proceedBtnPressed:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
//Some other code here
});
}
But i don't want any thread switching mechanism outside of showProcessingScreen.
This is common screen almost used in every application. I used similar codes with xib, custom views in my previous apps which were not using storyboard etc.,
I know this is related to threading, what i am doing wrong here ? what is best practice to achieve this ?
Any help will be aprreciatead.
The problem is that when you're dispatching, you put the creation of your loading view at the end of the run loop. Whatever your "other code" is is blocking the main thread for a few seconds.
If you move this "other code" to a different thread, this will resolve your issue. (This solution is ideal.)
You could also only switch to the main thread conditionally, which would resolve the issue if you call the loading view from the main thread:
-(void) showProcessingScreen
{
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(showProcessingScreen) withObject:nil waitUntilDone:FALSE];
return;
}
UIStoryboard *mystoryboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
processingScreen = [mystoryboard instantiateViewControllerWithIdentifier:#"loadingViewController"];
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: processingScreen.view];
[mainWindow bringSubviewToFront:processingScreen.view];
}
Instead of adding an overlay view that intercepts touch events, you can just call
– beginIgnoringInteractionEvents
– endIgnoringInteractionEvents
You could try with this:
- (void)doSomeOtherCode {
//Some other code here
}
- (IBAction) showProcessingScreenAndDoSomeOtherCode:(id)sender
{
[[GUIUtilities sharedObj] showProcessingScreen];
[self performSelector:#selector(doSomeOtherCode) withObject:nil afterDelay:0.0];
}
- (IBAction)proceedBtnPressed:(id)sender
{
[self showProcessingScreenAndDoSomeOtherCode:self];
}
and it will work. About your remark:
But i don't want any thread switching mechanism outside of showProcessingScreen.
The above solution will not cause any thread switching outside of showProcessingScreen. performSelector will just add an entry in the event loop queue.
EDIT:
If you are using blocks, the same result can be achieved through:
dispatch_async(dispatch_get_main_queue(),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
dispatch_async(dispatch_get_main_queue(), ^{
//Some other code here
});
});
or:
dispatch_async(dispatch_get_main_queue(),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
});
dispatch_async(dispatch_get_main_queue(), ^{
//Some other code here
});
Since the main queue is a serial queue, showProcessingScreen and "Some other code here" would be executed serially.

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];
}

Delay between animations on a UIImageView in iOS

I have an animation which is made up of an array of images, on which I then run
[myUIImageView startAnimating]
I want the animation to run once, then stop for 3 seconds, then repeat.
I need to run this animation in a separate thread so I have
NSThread *animationThread = [[NSThread alloc] initWithTarget:self selector:#selector(startAnimTask) withObject:nil waitUntilDone:NO];
[animationThread start];
in my viewDidLoad, and then
-(void) startAnimTask {
//create array of images here; animationDuration (3) and animationRepeatCount (1)
[self setUpAnimation];
while (true){
[myUIImageView startAnimating];
usleep(3);
[myUIImageView stopAnimating];
usleep(3);
}
}
Using this method, I receive a memory warning. I've also tried running the start and stop on the MainThread with no luck.
Any ideas?
Do this:
[myUIImageView startAnimating];
[self performSelector:#selector(startAnimTask)
withObject:nil
afterDelay:(myUIImageView.animationDuration+3.0)];
Now selector is:
-(void)startAnimTask
{
[myUIImageView startAnimating];
//repeat again then add above selector code line here
}
UI is not thread safe, so UI calls should be executed only in main thread, maybe this is the case.
I think you can go with this:
[self performSelector:#selector(startAnimTask)
withObject:nil
afterDelay:0.1];
[self performSelector:#selector(startAnimTask)
withObject:nil
afterDelay:3.0];
In startAnimTask method write your animation logic code.
Enjoy Coding :)
Try this approach to complete your task:
[self performSelector:#selector(myTask)
withObject:nil
afterDelay:3.0];
-(void)myTask{
//Write your logic for animation
}

Resources