The proper way of doing chain animations - ios

void (^first_animation)();
void (^second_animation)(BOOL finished);
// First animation
first_animation = ^()
{
g_pin_info_screen.view.alpha = 1.0;
};
// Second animation
second_animation = ^(BOOL finished)
{
g_shadow_layer.opacity = 0.0;
void (^set_opacity_to_1)();
set_opacity_to_1 = ^()
{
g_shadow_layer.opacity = 1.0;
};
[UIView animateWithDuration : 2.0
delay : 0.0
options : UIViewAnimationCurveEaseInOut
animations : set_opacity_to_1
completion : nil
];
};
// Begin the animations
{
float duration;
duration = 0.35;
[UIView animateWithDuration : duration
delay : 0.00
options : UIViewAnimationCurveEaseInOut
animations : first_animation
completion : second_animation
];
}
First animation executes as expected. But second animation completes but without any animation.
Hope somebody could comment on whether the above scheme is the proper way of doing this or not.

__block NSMutableArray* animationBlocks = [NSMutableArray new];
typedef void(^animationBlock)(BOOL);
// getNextAnimation
// removes the first block in the queue and returns it
animationBlock (^getNextAnimation)() = ^{
if ([animationBlocks count] > 0){
animationBlock block = (animationBlock)[animationBlocks objectAtIndex:0];
[animationBlocks removeObjectAtIndex:0];
return block;
} else {
return ^(BOOL finished){
animationBlocks = nil;
};
}
};
[animationBlocks addObject:^(BOOL finished){
[UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
//my first set of animations
} completion: getNextAnimation()];
}];
[animationBlocks addObject:^(BOOL finished){
[UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
//second set of animations
} completion: getNextAnimation()];
}];
[animationBlocks addObject:^(BOOL finished){
[UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
//third set
} completion: getNextAnimation()];
}];
[animationBlocks addObject:^(BOOL finished){
[UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
//last set of animations
} completion:getNextAnimation()];
}];
// execute the first block in the queue
getNextAnimation()(YES);

With the help of the third party library there is a solution that looks like as below:
First, for convenience, define a category for UIView like so:
+(RXPromise*) rx_animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
RXPromise* promise = [RXPromise new];
[UIView animateWithDuration:duration animations:animations: ^(BOOL finished){
// ignore param finished here
[promise fulfillWithValue:#"finished"]; // return just a string indicating success
}];
return promise;
}
Then, define any number of asynchronous animations which execute one after the other, as follows:
[UIView rx_animateWithDuration:duration animation:^{
... //define first animation
}]
.then(^id(id result){
// ignore result, it contains the fulfill value of the promise, which is #"finished"
return [UIView rx_animateWithDuration:duration animation:^{
... // define second animation
}];
}, nil)
.then(^id(id result){
return [UIView rx_animateWithDuration:duration animation:^{
... // define third animation
}];
}, nil)
.then(^id(id result){
return [UIView rx_animateWithDuration:duration animation:^{
... // and so force
};
}, nil);
The above statement is asynchronous!
With one line additional code you can achieve cancellation:
RXPromise* rootPromise = [UIView rx_animateWithDuration:duration animation:^{
... //define first animation
}];
rootPromise.then(^id(id result){
return [UIView rx_animateWithDuration:duration animation:^{
... // define second animation
}];
}, nil)
.then(^id(id result){
return [UIView rx_animateWithDuration:duration animation:^{
... // define third animation
}];
}, nil)
...
// later, in case you need to cancel pending animations:
[rootPromise cancel];
"RXPromise" library is available on GitHub: RXPromise. It's specifically designed for these use cases, and more. Due to full disclosure: I'm the author ;)

Just check here:
https://gist.github.com/vadimsmirnovnsk/bce345ab81a1cea25a38
You can chain it in functional style:
dispatch_block_t animationsBlock = ^{
[self.view updateConstraintsIfNeeded];
[self.view layoutIfNeeded];
};
[[[[[[[[[BARAnimation construct]
initially:animationsBlock]
animationWithDuration:0.425 animationConditions:^{
[gridView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(imageView).with.offset(32.0);
}];
} animations:animationsBlock]
animationWithDuration:0.425 animationConditions:^{
[gridView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(imageView).with.offset(0.0);
}];
} animations:animationsBlock]
animationWithDuration:0.425 animationConditions:^{
[gridView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(imageView).with.offset(-32.0);
}];
} animations:animationsBlock]
animationWithDuration:0.425 animationConditions:^{
[gridView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(imageView).with.offset(0.0);
}];
} animations:animationsBlock]
animationWithDuration:0.8 animationConditions:nil animations:^{
foreView.alpha = 1.0;
}]
finally:^{
[self.didEndSubject sendNext:[RACUnit defaultUnit]];
[self.didEndSubject sendCompleted];
}]
run];

You need to chain them together by using + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
Within the options: argument, you need to include UIViewAnimationOptionBeginFromCurrentState
Good luck!

In the completion handler of the first animation, start the second one.

Related

IOS/Objective-c: code reduction within animations

I have this simple animation that fades in several labels, one at a time. But I wonder if it is possible to reduce the code with this logic?
[UIView animateWithDuration:0.50f animations:^{
[_latLabel setAlpha:0.9f];
[_firstLat setAlpha:0.9f];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.50f animations:^{
[_lonLabel setAlpha:0.9f];
[_firstLon setAlpha:0.9f];
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.50f animations:^{
[_speedLabel setAlpha:0.9f];
[_firstSpeed setAlpha:0.9f];
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.50f animations:^{
[_realNorthLabel setAlpha:0.9f];
[_firstReal setAlpha:0.9f];
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.50f animations:^{
[_magneticNorthLabel setAlpha:0.9f];
[_firstMagnetic setAlpha:0.9f];
}];
}];
}];
}];
}];
Considering the need to perform operation on 2 label objects while executing any single section of the animation block, create a dictionary structure wherein section indexes define the chronological order of the sections animations to be performed & each section index contains an array of label objects.
A simple iterating loop over this structure christened here as dictionaryOfSectionedLabels can help achieve the desired alternate animation implementation.
NSDictionary<NSNumber *, NSArray *> *dictionaryOfSectionedLabels = #{ #0: #[_latLabel, _firstLat],
#1: #[_lonLabel, _firstLon],
#2: #[_speedLabel, _firstSpeed],
#3: #[_realNorthLabel, _firstReal],
#4: #[_magneticNorthLabel, _firstMagnetic]
};
for (NSUInteger i = 0; i < dictionaryOfSectionedLabels.allKeys.count; i++) {
[UIView animateWithDuration:0.5 delay:0.5*i options:UIViewAnimationOptionLayoutSubviews animations:^{
UILabel *firstLabel = [dictionaryOfSectionedLabels[#(i)] firstObject];
UILabel *secondLabel = [dictionaryOfSectionedLabels[#(i)] lastObject];
firstLabel.alpha = 0.9f;
secondLabel.alpha = 0.9f;
} completion:nil];
}
Creating an NSDictionary of your labels can be tedious and confusing code to read in the future. I'd recommend you just create a method.
-(void)customFadeForLabel:(UILabel *)theLabel withDelay:(float)delayAmount {
[UIView animateWithDuration:0.50f
delay:delayAmount
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[theLabel setAlpha:0.9f];
}completion:nil];
}
Then just call it as needed with delays
[self customFadeForLabel:_latLabel withDelay:0.00f];
[self customFadeForLabel:_firstLat withDelay:0.00f];
[self customFadeForLabel:_lonLabel withDelay:0.50f];
[self customFadeForLabel:_firstLon withDelay:0.50f];
[self customFadeForLabel:_speedLabel withDelay:1.00f];
[self customFadeForLabel:_firstSpeed withDelay:1.00f];
[self customFadeForLabel:_realNorthLabel withDelay:1.50f];
[self customFadeForLabel:_firstReal withDelay:1.50f];
[self customFadeForLabel:_magneticNorthLabel withDelay:2.00f];
[self customFadeForLabel:_firstMagnetic withDelay:2.00f];

How to create Label text slider

I have created slider in my app. How to add loop in my code. Because my label text slide only one time but i want to label text repeat (loop) on label. How it possible?
My Label slider code
Make globalCounter as global variable
globalCounter=0;
if(nameArray.count>0){
[self changeLable];
}
Then
-(void)changeLable{
if(!(globalCounter<nameArray.count)){
return;
}
NSLog(#"globalCounter %d",globalCounter);
[UIView animateWithDuration:1
delay:0.5
options: UIViewAnimationOptionTransitionCrossDissolve
animations:^{
}
completion:^(BOOL finished) {
[lblTitle setText:[nameArray objectAtIndex:globalCounter]];
globalCounter++;
[self performSelector:#selector(changeLable) withObject:nil afterDelay:1];
}];
}
Edit
-(void)changeLable{
if(!(globalCounter<nameArray.count)){
globalCounter=0;
}
NSLog(#"globalCounter %d",globalCounter);
[UIView animateWithDuration:1
delay:0.5
options: UIViewAnimationOptionTransitionCrossDissolve
animations:^{
}
completion:^(BOOL finished) {
[lblTitle setText:[nameArray objectAtIndex:globalCounter]];
globalCounter++;
[self performSelector:#selector(changeLable) withObject:nil afterDelay:1];
}];
}
do like
globalCounter=0;
if(des.count>0){
[_label setText:[des objectAtIndex:globalCounter]];
[self changeLable];
}
and call method like
-(void)changeLable{
if(globalCounter < des.count){
[UIView animateWithDuration:0.
delay:0.5
options: UIViewAnimationOptionTransitionCrossDissolve
animations:^{
}
completion:^(BOOL finished) {
if(globalCounter>=des.count){
globalCounter=0;//set counter to zero after it exceeds array count to repeat text change round repeated
}else
{
[_label setText:[des objectAtIndex:globalCounter]];
globalCounter++;
[self performSelector:#selector(changeLable) withObject:nil afterDelay:1];
}
}];
}
}
Try below code, it may help:
-(void)changeLable{
NSLog(#"globalCounter %d",globalCounter);
[UIView animateWithDuration:1
delay:0.5
options: UIViewAnimationOptionTransitionCrossDissolve
animations:^{
[lblTitle setText:[nameArray objectAtIndex:globalCounter]];
}
completion:^(BOOL finished) {
if(globalCounter>nameArray.count)){
globalCounter=0;//set counter to zero after it exceeds array count to repeat text change round repeated
}else{
globalCounter++;
}
[self performSelector:#selector(changeLable) withObject:nil afterDelay:1];
}];
}
This is my answer brother.Sorry I could not answer immediately as I had some work.I downloaded and ran your project.First it crashes as you handled the wrong array count.So it bounds the array.After that I set the condition inside the black.
-(void)changeLable
{
NSLog(#"globalCounter %d",globalCounter);
[UIView animateWithDuration:1
delay:0.5
options: UIViewAnimationOptionTransitionCrossDissolve
animations:^{
}
completion:^(BOOL finished)
{
if(globalCounter<des.count)
{
_label.text = #"";
[_label setText:[des objectAtIndex:globalCounter]];
globalCounter++;
}
else
globalCounter=0;
[self performSelector:#selector(changeLable) withObject:nil afterDelay:1];
}];
}

Create UIView Animation that will loop after the completion method is run

I'm having a bit of trouble. I've set my UIView Animation to repeat itself infinitely, however it does this before the animation starts it's completion function. I want it to wait until after the completion function fully runs because I want to run multiple animations. Any idea how to do this? If not is there a different way I can accomplish the same thing?
I'll post my code below:
-(void) createBlueControlAnimation:(int)messageCount{
if(messageCount == 0) {
return;
}
[blueControl setContentOffset:CGPointMake(0, 0)];
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionRepeat
animations:^{
[blueControl setContentOffset:CGPointMake(screenWidth, 0)];
} completion:^(BOOL finished){
if(messageCount >= 2){
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
[blueControl setContentOffset:CGPointMake(screenWidth * 2, 0)];
} completion:^(BOOL finished){
if(messageCount == 3) {
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
[blueControl setContentOffset:CGPointMake(screenWidth * 3, 0)];
}completion:^(BOOL finished){
[self scrollAnimationToStart];
}];
}else{
[self scrollAnimationToStart];
}
}];
}else{
[self scrollAnimationToStart];
}
}];
}
-(void) scrollAnimationToStart {
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
[blueControl setContentOffset:CGPointMake(0, 0)];
} completion:^(BOOL finished){}];
}
So based on the number of messages I want the animation to continue running, and then when it gets to the last message, loop back to the first message and then restart the animation. However right now it just loops infinitely between the first and the second message. Any ideas on how to fix this would be greatly appreciated, thanks.
Firstly: You should create separate consts for anything that is in code.
extern NSTimeInterval const animationDuration;
NSTimeInterval const animationDuration = 0.5;
extern NSTimeInterval const animationDelay;
NSTimeInterval const animationDelay = 2;
Secondly: intent your code in reasonable way, it's quite hard to read what you've pasted. Use one method and push every repeatable part of code into another method.
- (void)createBlueControlAnimation:(NSUInteger)messageCount {
if (messageCount == 0) {
return;
}
__weak typeof(self) weakSelf = self;
[self offsetBlueControl:0];
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[weakSelf offsetBlueControl:screenWidth];
} completion:^(BOOL finished) {
if (messageCount < 2) {
[self scrollAnimationToStart];
} else {
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[weakSelf offsetBlueControl:screenWidth * 2];
} completion:^(BOOL finished) {
if (messageCount != 3) {
[self scrollAnimationToStart];
} else {
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[weakSelf offsetBlueControl:screenWidth * 3];
} completion:^(BOOL finished) {
[self scrollAnimationToStart];
}];
}
}];
}
}];
}
- (void)scrollAnimationToStart {
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[blueControl setContentOffset:CGPointMake(0, 0)];
} completion:^(BOOL finished) {}];
}
- (void)offsetBlueControl:(CGFloat)xOffset {
[blueControl setContentOffset:CGPointMake(xOffset, 0)];
}
I should mention here that this animation block is in fact similar everywhere. I'd set it as a separate method also.
Thirdly, keep same way of spacing everywhere (eg check out https://github.com/NYTimes/objective-c-style-guide).
That's all about code, the quality, readibility etc. I'd set it as comment, but there's too much code in my response, so it has to be an answer.
I don't fully understand what do you want to do. As far as I understood:
You have a UI element, called BlueControl.
You want to move this element.
Basing on some counter you want to move your view by some width (btw I advise to switch to NSIntegers from ints and use unsigned option, so NSUInteger etc). The move steps:
3.1. Move button right during 0.5s
3.2. Wait 2 seconds if counter is large enough, otherwise end step 3
3.3. Increase counter, repeat step 3.
You want to repeat the process infinitely.
Is that right? I need to understand the problem to edit this answer and paste a full response here.

What is happening in this block queue?

I was searching for a way to queue animation blocks, and happened across this blog post:
http://xibxor.com/2013/03/27/uiview-animation-without-nested-hell/
I can't get it to work, though...the scope of how to arrange those elements isn't clear. Also, what are those semicolons on lines 18, 25, and 32 doing? Can anyone explain how to use this?
EDIT: here is the code copied from the source:
NSMutableArray* animationBlocks = [NSMutableArray new];
typedef void(^animationBlock)(BOOL);
// getNextAnimation
// removes the first block in the queue and returns it
animationBlock (^getNextAnimation)() = ^{
animationBlock block = animationBlocks.count ? (animationBlock)[animationBlocks objectAtIndex:0] : nil;
if (block){
[animationBlocks removeObjectAtIndex:0];
return block;
}else{
return ^(BOOL finished){};
}
};
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:1.0 animations:^{
//...animation code...
} completion: getNextAnimation()];
}];
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:1.0 animations:^{
//...animation code...
} completion: getNextAnimation()];
}];
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:1.0 animations:^{
//...animation code...
} completion: getNextAnimation()];
}];
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
NSLog(#"Multi-step Animation Complete!");
}];
// execute the first block in the queue
getNextAnimation()(YES);
First thanks to share of this post, it's a great idea. That works perfectly for me:
in your .h outside #interface:
typedef void(^animationBlock)(BOOL);
Then in your .m (here it's inside - (void)viewDidLoad):
NSMutableArray* animationBlocks = [NSMutableArray new];
UILabel* labelTest = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
labelTest.text = #"Label Test";
[self.view addSubview:labelTest];
// getNextAnimation
// removes the first block in the queue and returns it
animationBlock (^getNextAnimation)() = ^{
animationBlock block = animationBlocks.count ? (animationBlock)[animationBlocks objectAtIndex:0] : nil;
if (block){
[animationBlocks removeObjectAtIndex:0];
return block;
}else{
return ^(BOOL finished){};
}
};
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:2.0 animations:^{
NSLog(#"1-step Animation Complete!");
labelTest.alpha = 0;
} completion: getNextAnimation()];
}];
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:2.0 animations:^{
labelTest.text = #"New text";
} completion: getNextAnimation()];
}];
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:2.0 animations:^{
labelTest.alpha = 1;
} completion: getNextAnimation()];
}];
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
NSLog(#"Multi-step Animation Complete!");
}];
// execute the first block in the queue
getNextAnimation()(YES);
It's a really simple animation but you need it to test it ^^
Hope that will help.

ios removeAllAnimations not working

I have an forever animation, but I want to stop it at some point I tried removeAllAnimations but it did not work.
Here is my code.
[self.backgroundImageView.layer removeAllAnimations];
-(void)animateToLeft{
if(isInCenter){
[UIView animateWithDuration:10.0f animations:^{
backgroundImageView.frame = CGRectMake(kLeftX, 0, kBackgroundWidth, kBackgroundHeight);
}completion:^(BOOL finished) {
[self animateToRight];
}];
isInCenter = NO;
}
else{
[UIView animateWithDuration:20.0f animations:^{
backgroundImageView.frame = CGRectMake(kLeftX, 0, kBackgroundWidth, kBackgroundHeight);
}completion:^(BOOL finished) {
[self animateToRight];
}];
}
}
-(void)animateToRight{
[UIView animateWithDuration:20.0f animations:^{
backgroundImageView.frame = CGRectMake(kRightX, 0, kBackgroundWidth, kBackgroundHeight);
}completion:^(BOOL finished) {
[self animateToLeft];
}];
}
This will not work in your situation.
Because [self.backgroundImageView.layer removeAllAnimations]; this will remove all layer animation which already have added by [self.backgroundImageView.layer addAnimation:/*CABasicAnimation should added here*/];
You can stop this cycle by set boolean variable in completion, then check with boolean variable.
Use a boolean value and if that is set, don't do the next animation -- also cancel pending ones..
e.g.
#interface MyClass () {
BOOL cancelAll;
}
#implementation MyClass
-(void)cancelAnimation {
self.imageView.layer removeAllAnimations]; //!
cancelAll = YES;
}
-(void)animateToLeft{
if(cancelAll)
return;
...
}
-(void)animateToRight{
if(cancelAll)
return;
...
}

Resources