Skip to content

Instantly share code, notes, and snippets.

@imownbey
Created June 30, 2015 22:34
Show Gist options
  • Save imownbey/0a7039fc5c1c648f5c84 to your computer and use it in GitHub Desktop.
Save imownbey/0a7039fc5c1c648f5c84 to your computer and use it in GitHub Desktop.
//
// UIView+TimedAnimations.m
// Bumper
//
// Created by Ian Ownbey on 6/30/15.
// Copyright © 2015 Bumpers Media Inc. All rights reserved.
//
#import "UIView+TimedAnimations.h"
#import <objc/runtime.h>
@interface BPSavedAnimationState: NSObject
@property (strong) CALayer *layer;
@property (copy) NSString *keyPath;
@property (strong) id oldValue;
+ (instancetype)savedStateWithLayer:(CALayer *)layer
keyPath:(NSString *)keyPath;
@end
@implementation BPSavedAnimationState
+ (instancetype)savedStateWithLayer:(CALayer *)layer
keyPath:(NSString *)keyPath
{
BPSavedAnimationState *savedState = [BPSavedAnimationState new];
savedState.layer = layer;
savedState.keyPath = keyPath;
savedState.oldValue = [layer valueForKeyPath:keyPath];
return savedState;
}
@end
@implementation UIView (TimedAnimations)
static NSMutableArray *BP_savedAnimationStates = NULL;
+ (void)load
{
BP_savedAnimationStates = [NSMutableArray array];
SEL originalSelector = @selector(actionForLayer:forKey:);
SEL extendedSelector = @selector(BP_actionForLayer:forKey:);
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method extendedMethod = class_getInstanceMethod(self, extendedSelector);
NSAssert(originalMethod, @"original method should exist");
NSAssert(extendedMethod, @"exchanged method should exist");
if(class_addMethod(self, originalSelector, method_getImplementation(extendedMethod), method_getTypeEncoding(extendedMethod))) {
class_replaceMethod(self, extendedSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, extendedMethod);
}
}
static void *BP_currentAnimationContext = NULL;
static void *BP_animationContext = &BP_animationContext;
- (id<CAAction>)BP_actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
if (BP_currentAnimationContext == BP_animationContext) {
[BP_savedAnimationStates addObject:[BPSavedAnimationState savedStateWithLayer:layer keyPath:event]];
return [NSNull null];
}
return [self BP_actionForLayer:layer forKey:event];
}
+ (void)BP_cubicTimedAnimateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay group:(dispatch_group_t)group animations:(void (^)(void))animations complete:(void (^)(BOOL))complete
{
[self BP_timedAnimateWithDuration:duration delay:delay timingFunction:[[CAMediaTimingFunction alloc] initWithControlPoints:0.2 : 0.7 : 0.5 : 1] group:group animations:animations complete:complete];
}
+ (void)BP_timedAnimateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay timingFunction:(CAMediaTimingFunction *)timingFunction group:(dispatch_group_t)group animations:(void (^)(void))animations complete:(void (^)(BOOL))complete
{
BP_currentAnimationContext = BP_animationContext;
animations();
[BP_savedAnimationStates enumerateObjectsUsingBlock:^(BPSavedAnimationState *savedState, NSUInteger idx, BOOL *stop) {
CALayer *layer = savedState.layer;
NSString *keyPath = savedState.keyPath;
id oldValue = savedState.oldValue;
id newValue = [layer valueForKeyPath:keyPath];
layer.masksToBounds = NO;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:keyPath];
anim.beginTime = CACurrentMediaTime() + delay;
anim.duration = duration;
anim.fromValue = oldValue;
anim.toValue = newValue;
anim.timingFunction = timingFunction;
// back to old value without an animation
[CATransaction begin];
[CATransaction setDisableActions:YES];
[layer setValue:oldValue forKeyPath:keyPath];
[CATransaction commit];
// animate the "pop"
if (group) {
dispatch_group_enter(group);
}
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[layer setValue:newValue forKey:keyPath];
[layer removeAnimationForKey:keyPath];
if (complete) {
complete(YES);
}
if (group) {
dispatch_group_leave(group);
}
}];
[layer addAnimation:anim forKey:keyPath];
[CATransaction commit];
}];
// clean up (remove all the stored state)
[BP_savedAnimationStates removeAllObjects];
BP_currentAnimationContext = nil;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment