Reusing animation array blocks across views? - ios

I've create a set of cool animation using labels on my first view.
I don't want to rewrite this code as I may improve this animation in the future.
However, I want to use this exact animation with different labels on another view / view controller. They will have the same name.
How would I do this, could someone suggest / provide an example ?
This is the animation array block code I based my code on...
http://xibxor.com/2013/03/27/uiview-animation-without-nested-hell/
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);
Here's an example of my code
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:1.0 animations:^{
//...animation code...
lblLeft.transform = CGAffineTransformMakeScale(1.3,1.3);
lblMiddle.transform = CGAffineTransformMakeScale(1.3,1.3);
lblRight.transform = CGAffineTransformMakeScale(1.3,1.3);
} completion: getNextAnimation()];
}];
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:1.0 animations:^{
//...animation code...
lblLeft.transform = CGAffineTransformIdentity;
lblMiddle.transform = CGAffineTransformIdentity;
lblRight.transform = CGAffineTransformIdentity;
} completion: getNextAnimation()];
}];
I have these three labels on different views within different view controllers.

Have you tried wrapping these in an object with class methods so that would keep your animation methods inside the object but pass the view to the wrapper methods? For example:
This method lives in class FooAnimationUtilities:
+ (void)animateViewWithScale:(UIView *)myView {
[animationBlocks addObject:^(BOOL finished){;
[myView animateWithDuration:1.0 animations:^{
//...animation code...
lblLeft.transform = CGAffineTransformMakeScale(1.3,1.3);
lblMiddle.transform = CGAffineTransformMakeScale(1.3,1.3);
lblRight.transform = CGAffineTransformMakeScale(1.3,1.3);
} completion: getNextAnimation()];
}];
}
Then from your other classes, that want to use this class...
In the .m above your implementation in your various view classes:
#import "FooAnimationUtilities.h"
And inside the view class methods (say animateMe) that need animation:
-(void)animateMe {
[FooAnimationUtilities animateViewWithScale:self];
}
So, in terms of passing in the labels, you could look at amending the class method to take (UILabel*)lblLeft or you could be a bit cleaner about it and pass over an array or a dict containing the label objects. You can even pass over the scale. So the class method name would be something like:
+ (void)animateView:(UIView *)myView andLabels:(NSArray *)labels withScale:(float)scale

You can use Categories. Categories are used to extend functions of existing classes. Here's the documentation on Categories: Link
If your code doesn't rely on a UIController being your "self", you can put that code in an NSObject and create an instance of it, or have a static instance of it when you need the animation. So you can just called [ObjectA animateMyAnimation];

Related

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.

Xcode5 - toggle button border color automatically

I new to IOS programming.
I want to toggle button border color automatically at start of apps to get user attention,
I have tried the below code, but only the final color is selected.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelay:0.0];
[UIView setAnimationDuration:3.0];
button.layer.borderWidth=2.5f;
button.layer.borderColor=[[UIColor blueColor]CGColor];
[UIView setAnimationDelay:3.0];
button.layer.borderColor=[[UIColor clearColor]CGColor];
[UIView setAnimationDelay:6.0];
button.layer.borderColor=[[UIColor redColor]CGColor];
[UIView commitAnimations];
}
What you're doing is an invalid way to chain animations. As a result, only the last changes are applied. Additionally, you should be using block based animations, which Apple has recommended since iOS 4. Should be something like this:
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderWidth=2.5f;
button.layer.borderColor=[[UIColor blueColor]CGColor];
} completion:^(BOOL finished) {
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderColor=[[UIColor clearColor]CGColor];
} completion:^(BOOL finished) {
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderColor=[[UIColor redColor]CGColor];
}];
}];
}];
This answer is the same as 0x7fffffff's answer, except it uses block variables so it looks a little cleaner and hopefully makes more sense:
void (^animateToRed)(BOOL finished) = ^(BOOL finished) {
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderColor=[[UIColor redColor]CGColor];
} completion: nil];
}
void (^animateToClear)(BOOL finished) = ^(BOOL finished) {
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderColor=[[UIColor clearColor]CGColor];
} completion:animateToRed];
}
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderWidth=2.5f;
button.layer.borderColor=[[UIColor blueColor]CGColor];
} completion:animateToClear];
UIView's animateWithDuration:animations:completion: method is the best way to animate changes over time.
It takes 3 arguments.
A duration as a CGFloat, which is a measure of the length of the animation in seconds.
An animation block, which tells what animations to perform.
A completion block, which allows you to execute code after the animation is complete.
This code snippet creates two completion blocks.
The animateToRed completion block handles the animating of the border to red. It's completion block is nil, at this point, we're done animating.
The animateToClear completion block handles the animating of the border to clear. It's completion block is the animateToRed which we just defined.
Finally, we call animateWithDuration, animating the border to blue, and passing the animateToClear block for the completion (which in turn calls the animateToRed block).
For an animation this simple and with no repeated animations, it may seem like slightly overkill to do it this way (though it is slightly more readable). However, with a more complicated series of animations, especially if there's any repetitiveness, creating block variables like this to use and pass quickly becomes quite helpful.

Creating a method to perform animations and wait for completion using a semaphore in objective c

I am trying to create a method which makes use of UIView's "+animateWithDuration:animations:completion" method to perform animations, and wait for completion. I am well aware that I could just place the code that would normally come after it in a completion block, but I would like to avoid this because there is a substantial amount of code after it including more animations, which would leave me with nested blocks.
I tried to implement this method as below using a semaphore, but I don't think this is the best way to do it, especially because it doesn't actually work. Can anyone tell me what is wrong with my code, and/or what the best way of achieving the same goal is?
+(void)animateAndWaitForCompletionWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[UIView animateWithDuration:duration
animations:animations
completion:^(BOOL finished) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
I'm not sure what's wrong with my code but when I call the method as shown below, the completion block never runs and I end up in limbo.
[Foo animateAndWaitForCompletionWithDuration:0.5 animations:^{
//do stuff
}];
-------------------------------------------EDIT-------------------------------------------------
If anyone is facing a similar issue, you might be interested to see the code I used. It uses recursion to make use of each completion block, without having to nest each function call.
+(void)animateBlocks:(NSArray*)animations withDurations:(NSArray*)durations {
[Foo animateBlocks:animations withDurations:durations atIndex:0];
}
+(void)animateBlocks:(NSArray*)animations withDurations:(NSArray*)durations atIndex:(NSUInteger)i {
if (i < [animations count] && i < [durations count]) {
[UIView animateWithDuration:[(NSNumber*)durations[i] floatValue]
animations:(void(^)(void))animations[i]
completion:^(BOOL finished){
[Foo animateBlocks:animations withDurations:durations atIndex:i+1];
}];
}
}
which can be used like so
[Foo animateBlocks:#[^{/*first animation*/},
^{/*second animation*/}]
withDurations:#[[NSNumber numberWithFloat:2.0],
[NSNumber numberWithFloat:2.0]]];
In iOS 7 and later one would generally employ keyframe animation to achieve this effect.
For example, a two second animation sequence that is composed of four separate animations that take up 25% of the entire animation each would look like:
[UIView animateKeyframesWithDuration:2.0 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat animations:^{
[UIView addKeyframeWithRelativeStartTime:0.00 relativeDuration:0.25 animations:^{
viewToAnimate.frame = ...;
}];
[UIView addKeyframeWithRelativeStartTime:0.25 relativeDuration:0.25 animations:^{
viewToAnimate.frame = ...;
}];
[UIView addKeyframeWithRelativeStartTime:0.50 relativeDuration:0.25 animations:^{
viewToAnimate.frame = ...;
}];
[UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.25 animations:^{
viewToAnimate.frame = ...;
}];
} completion:nil];
In earlier iOS versions, you could queue up a series of animations a couple of ways, but I'd encourage you to avoid using a semaphore on the main thread.
One approach would be to wrap the animation in a concurrent NSOperation subclass, which doesn't complete until the animation does. You can then add your animations to your own custom serial queue:
NSOperationQueue *animationQueue = [[NSOperationQueue alloc] init];
animationQueue.maxConcurrentOperationCount = 1;
[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
viewToAnimate.center = point1;
}]];
[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
viewToAnimate.center = point2;
}]];
[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
viewToAnimate.center = point3;
}]];
[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
viewToAnimate.center = point4;
}]];
The AnimationOperation subclass might look like:
// AnimationOperation.h
#import <Foundation/Foundation.h>
#interface AnimationOperation : NSOperation
- (instancetype)initWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations;
#end
and
// AnimationOperation.m
#import "AnimationOperation.h"
#interface AnimationOperation ()
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#property (nonatomic, copy) void (^animations)(void);
#property (nonatomic) UIViewAnimationOptions options;
#property (nonatomic) NSTimeInterval duration;
#property (nonatomic) NSTimeInterval delay;
#end
#implementation AnimationOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (instancetype)initWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations {
self = [super init];
if (self) {
_animations = animations;
_options = options;
_delay = delay;
_duration = duration;
}
return self;
}
- (void)start {
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)main {
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:self.duration delay:self.delay options:self.options animations:self.animations completion:^(BOOL finished) {
[self completeOperation];
}];
});
}
#pragma mark - NSOperation methods
- (void)completeOperation {
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
- (BOOL)isAsynchronous {
return YES;
}
- (BOOL)isExecuting {
#synchronized(self) { return _executing; }
}
- (BOOL)isFinished {
#synchronized(self) { return _finished; }
}
- (void)setExecuting:(BOOL)executing {
if (_executing != executing) {
[self willChangeValueForKey:#"isExecuting"];
#synchronized(self) { _executing = executing; }
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void)setFinished:(BOOL)finished {
if (_finished != finished) {
[self willChangeValueForKey:#"isFinished"];
#synchronized(self) { _finished = finished; }
[self didChangeValueForKey:#"isFinished"];
}
}
#end
In my demonstration, above, I used a serial queue. But you could also use a concurrent queue, but use NSOperation dependencies to manage the relationship between the various animation operations. Lots of options here.
If you want to cancel the animations, you could then do something like the following:
CALayer *layer = [viewToAnimate.layer presentationLayer];
CGRect frame = layer.frame; // identify where it is now
[animationQueue cancelAllOperations]; // cancel the operations
[viewToAnimate.layer removeAllAnimations]; // cancel the animations
viewToAnimate.frame = frame; // set the final position to where it currently is
You could also incorporate this into a custom cancel method in the operation, too, if you want.
You appear to be using a semaphore to block the main thread until animations compete. Unfortunately the main thread is the thread performing those animations so this will never occur.
You must not block the main thread of your application. By doing so you prevent any view drawing or responding to user input. You will also soon trigger an OS watchdog which detects your app as unresponsive and terminates it.
Animations an their completion blocks are expressed as asynchronous operations for good reason. Try to embrace that in your design.
Semaphore can solve all the block synchronization problems.
Two points
Need to create a serial queue.
dispatch_semaphore_wait and dispatch_semaphore_signal can not be in the same queue.
Here is a example
- (dispatch_queue_t)queue {
if (!_queue) {
_queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
}
return _queue;
}
- (dispatch_semaphore_t)semaphore {
if (!_semaphore) {
_semaphore = dispatch_semaphore_create(0);
}
return _semaphore;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self funcA];
[self funcA];
[self funcA];
[self funcA];
[self funcA];
[self funcB];
}
- (void)funcA {
dispatch_async(self.queue, ^{
//Switch to main queue to perform animation
dispatch_async(dispatch_get_main_queue(), ^{
self.view.alpha = 1;
[UIView animateWithDuration:2 animations:^{
self.view.alpha = 0;
} completion:^(BOOL finished) {
NSLog(#"funcA completed");
//Unblock custom queue
dispatch_semaphore_signal(self.semaphore);
}];
});
//Block custom queue
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
});
}
- (void)funcB {
dispatch_async(self.queue, ^{
NSLog(#"funcB completed");
});
}
As Jonah said, there is no way to do that on the main thread. If you do not want to have nested blocks, there is nothing bad with it, but I understand your wish, simply put a method inside the block and the inner block then in the method. If you need closures in the inner block, you can pass them as an argument.
Doing so will enlarge your love to nested blocks. ;-)
As many people pointed out here, is true that the animation runs on the main thread and the semaphore usage stops the main thread. But this still can be done with a semaphore using this approach:
// create semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
// do animations
} completion:^(BOOL finished) {
// send signal
dispatch_semaphore_signal(semaphore);
}];
// create background execution to avoid blocking the main thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// wait for the semaphore
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// now create a main thread execution
dispatch_async(dispatch_get_main_queue(), ^{
// continue main thread logic
});
});

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.

The proper way of doing chain animations

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.

Resources