Pausing an animation in iOS (objective C) - ios

I am having some trouble handling animations in some objective C code.
First, here is the relevant code:
BOOL pauseFlag; // Instance variable.
CGFloat animationDuration,pauseDuration; // Instance variables.
......
pauseFlag = NO;
animationDuration = 1.0;
pauseDuration = 1.0;
- (void)animationFunction
{
[UIView animateWithDuration:animationDuration
delay:pauseFlag?pauseDuration:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
......
}
completion:^(BOOL finished){
......
pauseFlag = Some_New_Value;
[self animationFunction];
}];
}
Then here is the problem:
The delay supposed to take place when pauseFlag is YES is not happening.
Of course, before writing this post I have tried various solutions which came up to my mind, like changing the options, and I also checked that when entering animationFunction pauseFlag had the value YES. But in all cases the delay was ignored.
What did I do wrong? I need to insert a pause in my animation and thought this was the simplest way to do it.
Anyone has an idea?
Just for information, beside this issue. This animation code is working fine.

Try to animate your view with UIViewPropertyAnimator:
UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:animationDuration
delay:pauseFlag?pauseDuration:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
......
}
completion:^(UIViewAnimatingPosition finalPosition) {
......
pauseFlag = Some_New_Value;
[self animationFunction];
}];
If you want to pause the animation call pauseAnimation:
[animator pauseAnimation];
To resume it call startAnimation:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[animator startAnimation];
});
All code in this post was tested in Xcode 10.2.1.

Related

Why are completion handlers of [UIView animate] called in a wrong order?

In my project there's a ViewController which contains a few subviews(e.g. buttons).
It shows/hides those buttons, always with animation.
It has an interface like this:
#interface MyViewController: UIViewController
- (void)setMyButtonsVisible:(BOOL)visible;
#end
And an implementation looks like this:
- (void)setMyButtonsVisible:(BOOL)visible
{
if( visible )
{
// "show"
_btn1.hidden = NO;
_btn2.hidden = NO;
[UIView animateWithDuration:0.2 animations:^{
_btn1.alpha = 1.0;
_btn2.alpha = 1.0;
}];
}
else
{
// "hide"
[UIView animateWithDuration:0.2 animations:^{
_btn1.alpha = 0.0;
_btn2.alpha = 0.0;
} completion:^(BOOL finished) {
_btn1.hidden = YES;
_btn2.hidden = YES;
}];
}
}
When [_myVC setMyButtonsVisible:NO] is called, and then after some time ( > 0.2s) [_myVC setMyButtonsVisible:YES] is called, everything is OK.
However, it setMyButtonsVisible:YES is called immediately after ( < 0.2s) setMyButtonsVisible:NO, the animations overlap and setMyButtonsVisible:NO callback is called the last.
I've tried to change "hide" duration to 0.1, it doesn't help - after "hide(0.1)"+"show(0.2)" calls, "hide" callback is called after the "show" callback and my buttons are not visible.
I've added a quick-fix by caching the visible param and checking in "hide" completion handler if the state should be !visible.
My questions are:
Why the first animation completion handler is called the last if animations overlap?
What are better approahes to discard a previous "overlapping" animation?
Check the finished flag on completion:
if (finished) {
_btn1.hidden = YES;
_btn2.hidden = YES;
}

iOS show recursive loading animation until completion of initialisation code block, then show the last animation

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

completion block stop working after setAnimationDelegate to self

I had a UIView block-based animation that looks like this:
[UIView animateWithDuration:0.5
//sequential delays
delay:3.0 * (float)(index/25)
options:UIViewAnimationOptionCurveEaseOut
animations:^{
// set myself as delegate
[UIView setAnimationDelegate:self];
// when animation stared, play some sound
[UIView setAnimationWillStartSelector:#selector(playDrawCardSound)];
// some animation results here
}
completion:^(BOOL finished){
// this log will never be printed out.
NSLog(#"finished");
}];
So, the problem is that all animations are fine, but the completion handler never got called.
Also, the reason that I am using delegate is because animations block will not be delayed as animations. That being said, all the sounds are played at once (not like animations which have delays between them).
Anybody know how to let code in animations block actually get delayed? Thanks!
You shouldn't use setAnimationDelegate or setAnimationWillStartSelector because document says Use of this method is discouraged in iOS 4.0 and later. If you are using the block-based animation methods, you can include your delegate’s start code directly inside your block.
Also setAnimationDelegate and setAnimationWillStartSelector must be called between calls to the beginAnimations:context: and commitAnimations methods which are discouraged in iOS 4.0 and later.
It may not precise but I think it may help.
NSTimeInterval delay = 3.0 * (float)(index/25) ;
[UIView animateWithDuration:0.5
//sequential delays
delay:delay
options:UIViewAnimationOptionCurveEaseOut
animations:^{
[self performSelector:#selector(playDrawCardSound) withObject:nil afterDelay:delay] ;
// some animation results here
}
completion:^(BOOL finished){
// this log will never be printed out.
NSLog(#"finished");
}];
I think you should not call delay methods in UIViewAnimation, you can just try this:
NSTimeInterval delayInterval = 3.0 * (float)(index/25);
[self performSelector:#selector(playDrawCardSound) withObject:nil afterDelay:delayInterval];
[UIView animateWithDuration:0.5
//sequential delays
delay:delayInterval
options:UIViewAnimationOptionCurveEaseOut
animations:^{
// some animation results,
//to do logic things in finish block or delay method above will be better
}
completion:^(BOOL finished){
// this log will never be printed out.
NSLog(#"finished");
}];
Try performSelector with param:
[self performSelector:#selector(doDelayTast:) withObject:[NSDate date] afterDelay:4];
- (void)doDelayTast:(id)obj{
NSDate *preDate = obj;
NSLog(#"Delay timeinterval:%f", [[NSDate date] timeIntervalSinceDate:preDate]);
}
The result:
Delay timeinterval:4.066990
Note that, 4s is the minimum time before the message is sent, but this can be ignored in most time.

UIView repeated animateWithDuration CPU 100%

Hi everyone,
small question.
I have an UIView class that I created, AnimatedUIView.
In the init, I create a subview that I animate like this:
- (void)animateView{
[UIView animateWithDuration:4.0
delay:1.0
options: nil
animations:^{
// Animation PART 1
}
completion:^(BOOL completed){
[UIView animateWithDuration:4.0
animations:^{
// Animation PART 2
}
completion:^(BOOL completed){
[self animateView];
}
];
}
];
}
I have to call [self animateView] myself in the second completion block rather than using option repeat because I want to delay 1 second only the first animation.
Everything works fine, I alloc and init my view and add it as a subview, and it is animated as should.
But, when I close my superview, I guess ARC does its work and deallocs the animatedView, but my CPU goes 100 % !
I have investigated and if I comment the call to [self animateView], my CPU doesn't skyrocket when I close the superview.
So I've managed to solve the problem (by putting a condition before the call and changing a boolean value before closing the superview, keepAnimating = NO) , I just wanted to understand WHY it does this?
Why does it try to keep animating, and why does it use so much CPU?
If I put an NSLog in the completion block, I first see it every 8 seconds, but when I close the superview, the NSLog just keeps appearing every ms...
BTW: it relates to this question : UIView animation using 90%CPU , which was not really answered. Thanks a lot!
CPU going to 100% almost always means infinite recursion.
We can only guess because only Apple knows what's inside the animation source code.
In this case I guess this could be caused by triggering the animation when the animation cannot run anymore (e.g. there is no layer since we are in dealloc state). That would mean the completion handler is called immediately and that leads to infinite recursion.
There are safer ways to create infinite animations (e.g. using CAAnimation on the layer).
You should not use self in side the block. It will create a retain cycle. If it is necessary to call self create a weak self pointer. Code like this
__weak typeof(self) weakself = self;
[UIView animateWithDuration:4.0
delay:1.0
options: nil
animations:^{
// Animation PART 1
}
completion:^(BOOL completed){
[UIView animateWithDuration:4.0
animations:^{
// Animation PART 2
}
completion:^(BOOL completed){
[weakself animateView];
}
];
}
];
But main issue with your code is you calling animateView recursively without any base case. So i consuming CPU cycles... do not use animateView without base case.
Try using timer
Write following line in your viewDidLoad or any where else from where you want animation to start.
[NSTimer scheduledTimerWithTimeInterval:9 target:self selector:#selector(animateView) userInfo:nil repeats:YES];
// 9 is total animation duration.....
update your animateView method as follows:
- (void)animateView{
[UIView animateWithDuration:4.0
delay:1.0
options: nil
animations:^{
// Animation PART 1
}
completion:^(BOOL completed){
[UIView animateWithDuration:4.0
animations:^{
// Animation PART 2
}
completion:^(BOOL completed){
}
];
}
];
}

How to wait until code is done. iOS

I can call some selector by this code
[self performSelector:<#(SEL)#> onThread:<#(NSThread *)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>]
and the Thread will wait until this selector is working (if I set waitUntilDone:YES). My question is - Can i wait until some code is done?
for example. I have some subview, which loads with animation. Animation duration is 2 sec. And if i make some actions on background view, while animating, i have an error. I'd like to wait until animation is over. And I can't to make a selector, and use
[self performSelector:<#(SEL)#> onThread:<#(NSThread *)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>]
Any suggestions? )
You can try this way
-(void)waitUntilDone:(void(^)(void))waitBlock {
//use your statement or call method here
if(waitBlock){
waitBlock();
}
}
And you need to use it as
[self.tableView waitUntilDone:^{
//call the required method here
}];
UIView animation gives this to you in the form of the block style animation
[UIView animateWithDuration:0.25f
animations:^{
// animations
} completion:^(BOOL finished) {
// run code when the animation is finished
}];
Wait until animation gets finished & then do rest of the things :
For eg:
[UIView animateWithDuration:duration
animations:^
{
//DO ANIMATION ADJUSTMENTS HERE
}
completion:^(BOOL finished)
{
//ANIMATION DID FINISH -- DO YOUR STUFF
}];

Resources