Files
twui/lib/UIKit/TUIView+Animation.m

250 lines
7.5 KiB
Objective-C

/*
Copyright 2011 Twitter, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this work except in compliance with the License.
You may obtain a copy of the License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "TUIView.h"
#import "TUICAAction.h"
@class TUIViewAnimation;
static NSMutableArray *TUIViewAnimationStack;
static TUIViewAnimation *TUIViewCurrentAnimation;
static BOOL TUIViewAnimationsEnabled = YES;
static BOOL TUIViewAnimateContents = NO;
static CGFloat TUIViewAnimationSlowMotionMultiplier (void) {
if (([NSEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) == NSShiftKeyMask) {
return 5.0;
} else {
return 1.0;
}
}
@interface TUIViewAnimation : NSObject <CAAction>
@property (nonatomic, assign) void *context;
@property (nonatomic, copy) NSString *animationID;
@property (nonatomic, unsafe_unretained) id delegate;
@property (nonatomic, assign) SEL animationWillStartSelector;
@property (nonatomic, assign) SEL animationDidStopSelector;
@property (nonatomic, copy) void (^animationCompletionBlock)(BOOL finished);
@property (nonatomic, strong, readonly) CABasicAnimation *basicAnimation;
@end
@implementation TUIViewAnimation
+ (void)initialize {
if (self != [TUIViewAnimation class]) return;
TUIViewAnimationStack = [NSMutableArray array];
}
- (id)init {
self = [super init];
if (self == nil) return nil;
_basicAnimation = [CABasicAnimation animation];
return self;
}
- (void)dealloc {
if (self.animationCompletionBlock == nil) return;
self.animationCompletionBlock(NO);
if (self.animationCompletionBlock != nil) NSLog(@"Error: animationCompletionBlock didn't complete!");
}
- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict {
CAAnimation *animation = [self.basicAnimation copy];
animation.delegate = self;
[animation runActionForKey:event object:anObject arguments:dict];
}
- (void)animationDidStart:(CAAnimation *)anim {
if (self.delegate == nil || self.animationWillStartSelector == NULL) return;
void (*animationWillStartIMP)(id, SEL, NSString *, void *) = (__typeof__(animationWillStartIMP))[(id)self.delegate methodForSelector:self.animationWillStartSelector];
animationWillStartIMP(self.delegate, self.animationWillStartSelector, self.animationID, self.context);
// only fire this once
self.animationWillStartSelector = NULL;
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if (self.delegate != nil && self.animationDidStopSelector != NULL) {
void (*animationDidStopIMP)(id, SEL, NSString *, NSNumber *, void *) = (__typeof__(animationDidStopIMP))[(id)self.delegate methodForSelector:self.animationDidStopSelector];
animationDidStopIMP(self.delegate, self.animationDidStopSelector, self.animationID, @(flag), self.context);
// only fire this once
self.animationDidStopSelector = NULL;
} else if (self.animationCompletionBlock) {
self.animationCompletionBlock(flag);
// only fire this once
self.animationCompletionBlock = nil;
}
}
@end
@implementation TUIView (TUIViewAnimation)
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations {
[self animateWithDuration:duration animations:animations completion:NULL];
}
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion {
[self beginAnimations:nil context:NULL];
self.animationDuration = duration;
TUIViewCurrentAnimation.animationCompletionBlock = completion;
animations();
[self commitAnimations];
}
+ (void)beginAnimations:(NSString *)animationID context:(void *)context {
[NSAnimationContext beginGrouping];
TUIViewCurrentAnimation = [[TUIViewAnimation alloc] init];
TUIViewCurrentAnimation.context = context;
TUIViewCurrentAnimation.animationID = animationID;
[TUIViewAnimationStack addObject:TUIViewCurrentAnimation];
// setup defaults
self.animationDuration = 0.25;
self.animationCurve = TUIViewAnimationCurveEaseInOut;
}
+ (void)commitAnimations {
[TUIViewAnimationStack removeLastObject];
TUIViewCurrentAnimation = TUIViewAnimationStack.lastObject;
[NSAnimationContext endGrouping];
}
+ (BOOL)isInAnimationContext {
return TUIViewCurrentAnimation != nil;
}
+ (void)setAnimationDelegate:(id)delegate {
TUIViewCurrentAnimation.delegate = delegate;
}
+ (void)setAnimationWillStartSelector:(SEL)selector {
TUIViewCurrentAnimation.animationWillStartSelector = selector;
}
+ (void)setAnimationDidStopSelector:(SEL)selector {
TUIViewCurrentAnimation.animationDidStopSelector = selector;
}
+ (void)setAnimationDuration:(NSTimeInterval)duration {
duration *= TUIViewAnimationSlowMotionMultiplier();
TUIViewCurrentAnimation.basicAnimation.duration = duration;
[NSAnimationContext currentContext].duration = duration;
}
+ (void)setAnimationDelay:(NSTimeInterval)delay {
TUIViewCurrentAnimation.basicAnimation.beginTime = CACurrentMediaTime() + delay * TUIViewAnimationSlowMotionMultiplier();
TUIViewCurrentAnimation.basicAnimation.fillMode = kCAFillModeBoth;
}
+ (void)setAnimationCurve:(TUIViewAnimationCurve)curve {
NSString *functionName = kCAMediaTimingFunctionEaseInEaseOut;
switch (curve) {
case TUIViewAnimationCurveLinear:
functionName = kCAMediaTimingFunctionLinear;
break;
case TUIViewAnimationCurveEaseIn:
functionName = kCAMediaTimingFunctionEaseIn;
break;
case TUIViewAnimationCurveEaseOut:
functionName = kCAMediaTimingFunctionEaseOut;
break;
case TUIViewAnimationCurveEaseInOut:
functionName = kCAMediaTimingFunctionEaseInEaseOut;
break;
default:
NSAssert(NO, @"Unrecognized animation curve: %i", (int)curve);
}
TUIViewCurrentAnimation.basicAnimation.timingFunction = [CAMediaTimingFunction functionWithName:functionName];
}
+ (void)setAnimationRepeatCount:(float)repeatCount {
TUIViewCurrentAnimation.basicAnimation.repeatCount = repeatCount;
}
+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses {
TUIViewCurrentAnimation.basicAnimation.autoreverses = repeatAutoreverses;
}
+ (void)setAnimationIsAdditive:(BOOL)additive {
TUIViewCurrentAnimation.basicAnimation.additive = additive;
}
+ (void)setAnimationsEnabled:(BOOL)enabled block:(void(^)(void))block {
BOOL save = TUIViewAnimationsEnabled;
TUIViewAnimationsEnabled = enabled;
block();
TUIViewAnimationsEnabled = save;
}
+ (void)setAnimationsEnabled:(BOOL)enabled {
TUIViewAnimationsEnabled = enabled;
}
+ (BOOL)areAnimationsEnabled {
return TUIViewAnimationsEnabled;
}
+ (void)setAnimateContents:(BOOL)enabled {
TUIViewAnimateContents = enabled;
}
+ (BOOL)willAnimateContents {
return TUIViewAnimateContents;
}
- (void)removeAllAnimations {
[self.layer removeAllAnimations];
[self.subviews makeObjectsPerformSelector:@selector(removeAllAnimations)];
}
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
id defaultAction = [NSNull null];
if (!TUIViewAnimationsEnabled) return defaultAction;
if (!TUIViewAnimateContents && [event isEqualToString:@"contents"]) return defaultAction;
TUIViewAnimation *animation = TUIViewCurrentAnimation;
if (animation == nil) return defaultAction;
if ([TUICAAction interceptsActionForKey:event]) {
return [TUICAAction actionWithAction:animation];
} else {
return animation;
}
}
@end