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
}
Related
I want to show loading animation while I am doing some initialisation. So I want to spin my (UIImageView*)_LogoImage until my initialisation will be completed. Then, after initialisation will be finished, I want to scale my _LogoImage. So, all this things begins from viewDidAppear, where I am calling beginLoading: method.
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self beginLoading];
}
Here, I am starting my spin animation. Assume, I am doing some initialisation in the next two code lines, I changed them to make a thread sleep to make a similar behaviour of my initialisation code. Then I am calling stopSpin: method to make the next half circle and to do my last scaling animation.
-(void)beginLoading{
[self startSpin];
[self sleepAction:3.0f];
[self sleepAction:1.0f];
[self stopSpin];
}
-(void)sleepAction:(float)sleepTime{
NSLog(#"SleepTime:[%f]",sleepTime);
[NSThread sleepForTimeInterval:sleepTime];
}
Here's my spinning code, which I am calling recursively until my BOOL refreshAnimating is Equal to YES. if not - running the last scaling animation
-(void)startSpin{
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
_LogoImage.transform = CGAffineTransformMakeRotation(M_PI);
}
completion:^(BOOL finished){
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
_LogoImage.transform = CGAffineTransformMakeRotation(0);
}
completion:^(BOOL finished){
if (refreshAnimating) {
[self startSpin];
}else{
[self refreshFinished];
}
}];
}];
}
-(void)stopSpin{
refreshAnimating = NO;
}
-(void)refreshFinished{
[UIView animateWithDuration:0.4 animations:^{
[_LogoImage setTransform:CGAffineTransformMakeScale(2, 2)];
}];
}
The problem is that my animation is not running, until the last initialisation code line completes. And after completion - I can see only the last animation. I was trying to put some code performing in the background and main thread, but I didn't run the way I want.
The rotation animation should start when the view appears, and continue until my initialisation code complete - then I want to see my last scale animation, which will indicate that my initialisation is completed.
Please, help.
All animations run on main thread and if you use sleep, it blocks your animations(main thread). Instead you can use a library like: MBProgressHUD please feel free to ask further questions.
Edit
-(void)startSpin{
_LogoImage.transform = CGAffineTransformMakeRotation(M_PI);
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
_LogoImage.transform =CGAffineTransformMakeRotation(0);
}
completion:nil];
}
Can you try this. I think that is what are trying to do.
As #Ersin Sezgin said, all animations run on main thread.
So now, I am doing initialisation in background with completion block handler, which is forcing stopSpin: after initialisation completes.
Here's some code block. For better readability there's typedef of block.
typedef void(^OperationSuccessCompletionHandler)(BOOL success);
in .m file
-(void)sleepAction:(OperationSuccessCompletionHandler)success{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// We are doing initialisation here
BOOL isOperationSuccess = YES // Assume initialisation was successful
success(isOperationSuccess);
});
}
So now we can do this in another way
-(void)beginLoading{
[self startSpin];
[self sleepAction:^(BOOL success){
[self sleepAction:^(BOOL success){
[self stopSpin];
}];
}];
}
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! :)
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];
});
}
So self.pic is the UIButton that I want to hide. But it never actually hides it for some reason.
Please Help
- (void)tick:(NSTimer*)time
{
self.pic.hidden = YES;
[NSThread sleepForTimeInterval:1];
self.pic.hidden = NO;
[self.pic setEnabled:NO];
if (self.checkPic == YES)
{
self.lives--;
self.livesLabel.text = [NSString stringWithFormat:#"Lives : %d", self.lives];
}
[self.pic setImage:[self backgroundImageForGame] forState:UIControlStateNormal];
[self check];
self.pic.enabled = YES;
}
Just to elaborate on what #rmaddy said in his comment, you shouldn't ever sleep the main (or UI) thread. When you do this, you're blocking the thread that is responsible for making the visual changes to the button, like whether or not it is hidden. The solution is to do the waiting asynchronously, and GCD has a built in function that makes this painless.
dispatch_after() allows you to create a block that will be executed after a specified delay, on a queue of your choosing. Since you want to update the UI, you'll want to come back to the main queue to make the changes to the on screen button. Here's an example:
self.pic.hidden = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.pic.hidden = NO;
});
I have two methods that both create a random sprite node and add it to the scene. Let's call them spriteMethod1 and spriteMethod2.
I'd like to have a looping method that runs spriteMethod1, 5 times, and then spriteMethod2 once. There also needs to be a delay between each time the spriteMethods are called.
I thought the following might work, but it doesn't:
-(void) addObjects {
for (int i = 0; i < 5; i++) {
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:2];
}
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:3];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:5];
}
I know this might not be the best solution, but it works for me:
-(void)addObjects {
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:2];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:4];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:6];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:8];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:10];
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:13];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:18];
}
Add a timer in your interface:
#property (nonatomic, weak) NSTimer *timer;
schedule a timer somewhere in your code
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(spriteMethod1) userInfo:nil repeats:YES];
do this
int count = 0;
- (void)spriteMethod1 {
count ++;
// do your work here
if(count == 5) {
// stop the timer
[self.timer invalidate];
// call the other methods
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:3];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:5];
}
}
The problem is that you won't see the delay because the selector is enqueued on the thread loop. From the documentation:
This method sets up a timer to perform the aSelector message on the
current thread’s run loop. The timer is configured to run in the
default mode (NSDefaultRunLoopMode). When the timer fires, the thread
attempts to dequeue the message from the run loop and perform the
selector. It succeeds if the run loop is running and in the default
mode; otherwise, the timer waits until the run loop is in the default
mode.
Another approach is to make the thread sleep for a few seconds.
[self spriteMethod1];
[NSThread sleepForTimeInterval:2.0f];
In this case your User Interface will hang if you don't execute this code in a separate thread.
This out the top of my head.. not sure if it'll do the trick:
- (void)startAddingObjects
{
NSNumber *counter = #(0);
[self performSelector:#selector(addMoreUsingCounter:)
withObject:counter
afterDelay:2];
}
- (void)addMoreUsingCounter:(NSNumber *)counter
{
int primCounter = [counter intValue];
if (primCounter < 5)
{
[self spriteMethod1];
[self performSelector:#selector(addMoreUsingCounter:)
withObject:#(primCounter++)
afterDelay:2];
}
else if (primCounter < 7)
{
[self spriteMethod2];
[self performSelector:#selector(addMoreUsingCounter:)
withObject:#(primCounter++)
afterDelay:3];
}
}
You'll probably still need to relate the delay to the counter to get the exact results you need.
I think your version doesn't work, because the "afterDelay" param is relative to the moment of invocation. You would need to multiply it with "i", in the for loop, and then use 13 and 18 respectively for the last two selectors.
Look into using an NSOperationQueue. You can set its maxConcurrentOperationCount to 1, to ensure it executes its actions sequentially. E.g.
NSOperationQueue * opQueue = [[NSOperationQueue alloc] init];
opQueue.maxConcurrentOperationCount = 1; // this ensures it'll execute the actions sequentially
NSBlockOperation *spriteMethod1Invoker = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; ++i)
{
[self spriteMethod1];
sleep(2); // sleep 2 secs between invoking spriteMethod1 again
}
}];
NSInvocationOperation *spriteMethod2Invoker = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(spriteMethod2) object:nil];
[opQueue addOperation:spriteMethod1Invoker];
[opQueue addOperation:spriteMethod2Invoker];