Skip to content

Instantly share code, notes, and snippets.

@vadimsmirnovnsk
Created March 13, 2016 13:22
Show Gist options
  • Save vadimsmirnovnsk/bce345ab81a1cea25a38 to your computer and use it in GitHub Desktop.
Save vadimsmirnovnsk/bce345ab81a1cea25a38 to your computer and use it in GitHub Desktop.
Objective-C animation chaining utility
@interface BARAnimation : NSObject
/*! Just start to construct your animation from this method. */
+ (instancetype)construct;
/*! Before all animations will execute this block. */
- (instancetype)initially:(void (^)(void))initiallyBlock;
/*! Add animations and link it with each other. */
- (instancetype)animationWithDuration:(NSTimeInterval)duration
animationConditions:(void (^)(void))conditions
animations:(void (^)(void))animations;
/*! Add animations with parameters and link it with each other. */
- (instancetype)animationWithDuration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
options:(UIViewAnimationOptions)options
animationConditions:(void (^)(void))conditions
animations:(void (^)(void))animations;
/*! After completion all of animation block this block will be called. */
- (instancetype)finally:(void (^)(void))finallyBlock;
/*! When you construct your animation, just run it. */
- (void)run;
/*! Just syntax sugar for linking animations. You don't need to use it. */
- (instancetype)then;
@end
#import "BARAnimation.h"
@interface BARAnimationBlock : NSObject
@property (nonatomic, copy, readonly) dispatch_block_t conditions;
@property (nonatomic, copy, readonly) dispatch_block_t animations;
@property (nonatomic, assign, readonly) NSTimeInterval duration;
@property (nonatomic, assign, readonly) NSTimeInterval delay;
@property (nonatomic, assign, readonly) UIViewAnimationOptions options;
- (instancetype)initWithDuration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
options:(UIViewAnimationOptions)options
animationConditions:(void (^)(void))conditions
animations:(void (^)(void))animations;
@end
@implementation BARAnimationBlock
- (instancetype)initWithDuration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
options:(UIViewAnimationOptions)options
animationConditions:(void (^)(void))conditions
animations:(void (^)(void))animations
{
self = [super init];
if (self == nil) return nil;
_duration = duration;
_delay = delay;
_options = options;
_conditions = [conditions copy];
_animations = [animations copy];
return self;
}
@end
@interface BARAnimation ()
@property (nonatomic, strong, readonly) NSMutableArray<BARAnimationBlock *> *animationBlocks;
@property (nonatomic, copy, readonly) dispatch_block_t finallyBlock;
@property (nonatomic, copy, readonly) dispatch_block_t initiallyBlock;
@end
@implementation BARAnimation
+ (instancetype)construct
{
return [[BARAnimation alloc] init];
}
- (instancetype)init
{
self = [super init];
if (self == nil) return nil;
_animationBlocks = [NSMutableArray array];
return self;
}
- (instancetype)animationWithDuration:(NSTimeInterval)duration
animationConditions:(void (^)(void))conditions
animations:(void (^)(void))animations
{
[self.animationBlocks addObject:[[BARAnimationBlock alloc] initWithDuration:duration
delay:0.0
options:0
animationConditions:conditions
animations:animations]];
return self;
}
- (instancetype)animationWithDuration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
options:(UIViewAnimationOptions)options
animationConditions:(void (^)(void))conditions
animations:(void (^)(void))animations
{
[self.animationBlocks addObject:[[BARAnimationBlock alloc] initWithDuration:duration
delay:delay
options:options
animationConditions:conditions
animations:animations]];
return self;
}
- (instancetype)then
{
return self;
}
- (instancetype)finally:(void (^)(void))finallyBlock
{
_finallyBlock = [finallyBlock copy];
return self;
}
- (instancetype)initially:(void (^)(void))initiallyBlock
{
_initiallyBlock = [initiallyBlock copy];
return self;
}
- (void)run
{
if (self.initiallyBlock)
{
self.initiallyBlock();
}
[self runAnimationWithIndex:0];
}
// Recursive call animations
- (void)runAnimationWithIndex:(NSUInteger)index
{
if (self.animationBlocks.count > index)
{
BARAnimationBlock *block = self.animationBlocks[index];
if (block.conditions)
{
block.conditions();
}
[UIView animateWithDuration:block.duration delay:block.delay options:block.options animations:^{
if (block.animations)
{
block.animations();
}
} completion:^(BOOL finished) {
if (finished)
{
[self runAnimationWithIndex:index + 1];
};
}];
}
else
{
if (self.finallyBlock)
{
self.finallyBlock();
}
}
}
@end
@vadimsmirnovnsk
Copy link
Author

Description

Simple class for chaining animations in functional style for iOS.

Why?

For example, look at the chained animations below:

dispatch_block_t animationsBlock = ^{
        [self.view updateConstraintsIfNeeded];
        [self.view layoutIfNeeded];
    };

    [gridView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(imageView).with.offset(32.0);
    }];

    [UIView animateWithDuration:0.425 animations:animationsBlock
        completion:^(BOOL finished) {
        if (finished)
        {
            [gridView mas_updateConstraints:^(MASConstraintMaker *make) {
                make.top.equalTo(imageView).with.offset(0.0);
            }];

            [UIView animateWithDuration:0.425 animations:animationsBlock
                completion:^(BOOL finished) {
                if (finished)
                {
                    [gridView mas_updateConstraints:^(MASConstraintMaker *make) {
                        make.top.equalTo(imageView).with.offset(-32.0);
                    }];

                    [UIView animateWithDuration:0.425 animations:animationsBlock
                        completion:^(BOOL finished) {
                        if (finished)
                        {
                            [gridView mas_updateConstraints:^(MASConstraintMaker *make) {
                                make.top.equalTo(imageView).with.offset(0.0);
                            }];

                            [UIView animateWithDuration:0.425 animations:animationsBlock
                                completion:^(BOOL finished) {
                                if (finished)
                                {
                                    [UIView animateWithDuration:0.8 animations:^{
                                        foreView.alpha = 1.0;
                                    } completion:^(BOOL finished) {
                                        if (finished)
                                        {
                                            [self.didEndSubject sendNext:[RACUnit defaultUnit]];
                                            [self.didEndSubject sendCompleted];
                                        }
                                    }];
                                }
                            }];
                        }
                    }];
                }
            }];
        }
    }];

Looks terrible, yeah? Just imagine how will you change this animation for a new product purpose.

And you can rewrite it with BARAnimation like that:

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

So, I can add some animations or remove any parts of them any time when I want.

Just hit the star if you like it)
Cheers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment