|
|
|
//
|
|
|
|
// QCMethod.m
|
|
|
|
//
|
|
|
|
// www.quartzcodeapp.com
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "QCMethod.h"
|
|
|
|
|
|
|
|
@implementation QCMethod
|
|
|
|
|
|
|
|
+ (CAAnimation*)reverseAnimation:(CAAnimation*)anim totalDuration:(CGFloat)totalDuration{
|
|
|
|
CGFloat duration = anim.duration + (anim.autoreverses ? anim.duration : 0);
|
|
|
|
duration = anim.repeatCount > 1 ? duration * anim.repeatCount : duration;
|
|
|
|
|
|
|
|
CGFloat endTime = anim.beginTime + duration;
|
|
|
|
CGFloat reverseStartTime = totalDuration - endTime;
|
|
|
|
|
|
|
|
CAAnimation *newAnim;
|
|
|
|
//Reverse timing function
|
|
|
|
void (^reverseTimingFunction)(CAAnimation*) = ^(CAAnimation *theAnim){
|
|
|
|
CAMediaTimingFunction *timingFunction = theAnim.timingFunction;
|
|
|
|
if (timingFunction) {
|
|
|
|
float first[2];
|
|
|
|
float second[2];
|
|
|
|
[timingFunction getControlPointAtIndex:1 values:first];
|
|
|
|
[timingFunction getControlPointAtIndex:2 values:second];
|
|
|
|
theAnim.timingFunction = [CAMediaTimingFunction functionWithControlPoints:1-second[0] :1-second[1] :1-first[0] :1-first[1]];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//Reverse animation values appropriately
|
|
|
|
if ([anim isKindOfClass:[CABasicAnimation class]]) {
|
|
|
|
CABasicAnimation *basicAnim = (CABasicAnimation*)anim;
|
|
|
|
|
|
|
|
if (!anim.autoreverses) {
|
|
|
|
id fromValue = basicAnim.toValue;
|
|
|
|
basicAnim.toValue = basicAnim.fromValue;
|
|
|
|
basicAnim.fromValue = fromValue;
|
|
|
|
reverseTimingFunction(basicAnim);
|
|
|
|
}
|
|
|
|
basicAnim.beginTime = reverseStartTime;
|
|
|
|
|
|
|
|
if (reverseStartTime > 0) {
|
|
|
|
CAAnimationGroup *groupAnim = [CAAnimationGroup animation];
|
|
|
|
groupAnim.animations = @[basicAnim];
|
|
|
|
groupAnim.duration = [self maxDurationFromAnimations:groupAnim.animations];
|
|
|
|
[groupAnim.animations setValue:kCAFillModeBoth forKeyPath:@"fillMode"];
|
|
|
|
newAnim = groupAnim;
|
|
|
|
}else newAnim = basicAnim;
|
|
|
|
}
|
|
|
|
else if ([anim isKindOfClass:[CAKeyframeAnimation class]]) {
|
|
|
|
CAKeyframeAnimation *keyAnim = (CAKeyframeAnimation*)anim;
|
|
|
|
|
|
|
|
if (!anim.autoreverses) {
|
|
|
|
NSArray *values = [keyAnim.values reverseObjectEnumerator].allObjects;
|
|
|
|
keyAnim.values = values;
|
|
|
|
reverseTimingFunction(keyAnim);
|
|
|
|
}
|
|
|
|
keyAnim.beginTime = reverseStartTime;
|
|
|
|
|
|
|
|
if (reverseStartTime > 0) {
|
|
|
|
CAAnimationGroup *groupAnim = [CAAnimationGroup animation];
|
|
|
|
groupAnim.animations = @[keyAnim];
|
|
|
|
groupAnim.duration = [self maxDurationFromAnimations:groupAnim.animations];
|
|
|
|
[groupAnim.animations setValue:kCAFillModeBoth forKeyPath:@"fillMode"];
|
|
|
|
newAnim = groupAnim;
|
|
|
|
}else newAnim = keyAnim;
|
|
|
|
|
|
|
|
}
|
|
|
|
else if ([anim isKindOfClass:[CAAnimationGroup class]]) {
|
|
|
|
CAAnimationGroup *groupAnim = (CAAnimationGroup*)anim;
|
|
|
|
NSMutableArray *newSubAnims = [NSMutableArray arrayWithCapacity:groupAnim.animations.count];
|
|
|
|
for (CAAnimation *subAnim in groupAnim.animations) {
|
|
|
|
CAAnimation *newSubAnim = [self reverseAnimation:subAnim totalDuration:totalDuration];
|
|
|
|
[newSubAnims addObject:newSubAnim];
|
|
|
|
}
|
|
|
|
groupAnim.animations = newSubAnims;
|
|
|
|
[groupAnim.animations setValue:kCAFillModeBoth forKeyPath:@"fillMode"];
|
|
|
|
groupAnim.duration = [self maxDurationFromAnimations:newSubAnims];
|
|
|
|
newAnim = groupAnim;
|
|
|
|
}else newAnim = anim;
|
|
|
|
|
|
|
|
return newAnim;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (CAAnimationGroup*)groupAnimations:(NSArray*)animations fillMode:(NSString*)fillMode forEffectLayer:(BOOL)forEffectLayer sublayersCount:(NSInteger)count{
|
|
|
|
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
|
|
|
|
groupAnimation.animations = animations;
|
|
|
|
if (fillMode) {
|
|
|
|
[groupAnimation.animations setValue:fillMode forKeyPath:@"fillMode"];
|
|
|
|
groupAnimation.fillMode = fillMode;
|
|
|
|
groupAnimation.removedOnCompletion = NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (forEffectLayer) {
|
|
|
|
groupAnimation.duration = [QCMethod maxDurationOfEffectAnimation:groupAnimation sublayersCount:count];
|
|
|
|
}else{
|
|
|
|
groupAnimation.duration = [QCMethod maxDurationFromAnimations:animations];
|
|
|
|
}
|
|
|
|
return groupAnimation;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (CAAnimationGroup*)groupAnimations:(NSArray*)animations fillMode:(NSString*)fillMode{
|
|
|
|
return [self groupAnimations:animations fillMode:fillMode forEffectLayer:NO sublayersCount:0];
|
|
|
|
}
|
|
|
|
+ (CGFloat)maxDurationFromAnimations:(NSArray*)anims{
|
|
|
|
CGFloat maxDuration = 0;
|
|
|
|
for (CAAnimation *anim in anims) {
|
|
|
|
maxDuration = MAX(anim.beginTime + anim.duration * (CGFloat)(anim.repeatCount == 0 ? 1.0f : anim.repeatCount) * (anim.autoreverses ? 2.0f : 1.0f), maxDuration);
|
|
|
|
}
|
|
|
|
if (maxDuration == INFINITY) {
|
|
|
|
maxDuration = 1000.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
return maxDuration;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (CGFloat)maxDurationOfEffectAnimation:(CAAnimationGroup*)anim sublayersCount:(NSInteger)count{
|
|
|
|
CGFloat maxDuration = 0;
|
|
|
|
if ([anim isKindOfClass:[CAAnimationGroup class]]) {
|
|
|
|
for (CABasicAnimation *subAnim in anim.animations) {
|
|
|
|
CGFloat instanceDelay = [[subAnim valueForKey:@"instanceDelay"] floatValue];
|
|
|
|
CGFloat delay = instanceDelay * (CGFloat)(count - 1);
|
|
|
|
CGFloat repeatCountDuration = 0;
|
|
|
|
if (subAnim.repeatCount >1) {
|
|
|
|
repeatCountDuration = (subAnim.duration * (subAnim.repeatCount-1));
|
|
|
|
}
|
|
|
|
|
|
|
|
CGFloat duration = subAnim.beginTime + (subAnim.autoreverses ? subAnim.duration : 0) + (delay + subAnim.duration + repeatCountDuration);
|
|
|
|
maxDuration = MAX(duration, maxDuration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (maxDuration == INFINITY) {
|
|
|
|
maxDuration = 1000.0f;
|
|
|
|
}
|
|
|
|
return maxDuration;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (void)updateValueFromAnimationsForLayers:(NSArray*)layers{
|
|
|
|
[CATransaction begin];
|
|
|
|
[CATransaction setDisableActions:YES];
|
|
|
|
|
|
|
|
for (CALayer *layer in layers) {
|
|
|
|
for (NSString *animKey in layer.animationKeys) {
|
|
|
|
CAAnimation *anim = [layer animationForKey:animKey];
|
|
|
|
[self updateValueForAnimation:anim theLayer:layer];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[CATransaction commit];
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (void)updateValueForAnimation:(CAAnimation*)anim theLayer:(CALayer *)layer{
|
|
|
|
if ([anim isKindOfClass:[CABasicAnimation class]]) {
|
|
|
|
CABasicAnimation *basicAnim = (CABasicAnimation*)anim;
|
|
|
|
if (!basicAnim.autoreverses) {
|
|
|
|
[layer setValue:basicAnim.toValue forKeyPath:basicAnim.keyPath];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ([anim isKindOfClass:[CAKeyframeAnimation class]]) {
|
|
|
|
CAKeyframeAnimation *keyAnim = (CAKeyframeAnimation*)anim;
|
|
|
|
if (!anim.autoreverses) {
|
|
|
|
[layer setValue:keyAnim.values.lastObject forKeyPath:keyAnim.keyPath];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ([anim isKindOfClass:[CAAnimationGroup class]]) {
|
|
|
|
CAAnimationGroup *groupAnim = (CAAnimationGroup*)anim;
|
|
|
|
for (CAAnimation *subAnim in groupAnim.animations) {
|
|
|
|
[self updateValueForAnimation:subAnim theLayer:layer];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (void)updateValueFromPresentationLayerForAnimation:(CAAnimation*)anim theLayer:(CALayer *)layer{
|
|
|
|
if ([anim isKindOfClass:[CABasicAnimation class]] || [anim isKindOfClass:[CAKeyframeAnimation class]]) {
|
|
|
|
CABasicAnimation *basicAnim = (CABasicAnimation*)anim;
|
|
|
|
[layer setValue:[layer.presentationLayer valueForKeyPath:basicAnim.keyPath] forKeyPath:basicAnim.keyPath];
|
|
|
|
}
|
|
|
|
else if ([anim isKindOfClass:[CAAnimationGroup class]]) {
|
|
|
|
CAAnimationGroup *groupAnim = (CAAnimationGroup*)anim;
|
|
|
|
for (CAAnimation *subAnim in groupAnim.animations) {
|
|
|
|
[self updateValueFromPresentationLayerForAnimation:subAnim theLayer:layer];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (void)addSublayersAnimation:(CAAnimation*)anim forKey:(NSString*)key forLayer:(CALayer*)layer{
|
|
|
|
[self addSublayersAnimationNeedReverse:anim forKey:key forLayer:layer reverseAnimation:NO totalDuration:0];
|
|
|
|
}
|
|
|
|
|
|
|
|
//!Add animation to each sublayer in effect layer
|
|
|
|
+ (void)addSublayersAnimationNeedReverse:(CAAnimation*)anim forKey:(NSString*)key forLayer:(CALayer*)layer reverseAnimation:(BOOL)reverse totalDuration:(CGFloat)totalDuration{
|
|
|
|
NSArray *sublayers = layer.sublayers;
|
|
|
|
NSInteger sublayersCount = sublayers.count;
|
|
|
|
|
|
|
|
void (^setBeginTime)(CAAnimation*, NSInteger) = ^(CAAnimation *subAnim, NSInteger sublayerIdx){
|
|
|
|
CGFloat instanceDelay = [[subAnim valueForKey:@"instanceDelay"] floatValue];
|
|
|
|
NSInteger orderType = [[subAnim valueForKey:@"instanceOrder"] integerValue];
|
|
|
|
switch (orderType) {
|
|
|
|
case 0: subAnim.beginTime += sublayerIdx * instanceDelay; break;
|
|
|
|
case 1: subAnim.beginTime += (sublayersCount - sublayerIdx - 1) * instanceDelay; break;
|
|
|
|
case 2: {
|
|
|
|
CGFloat middleIdx = sublayersCount/2.0f;
|
|
|
|
CGFloat begin = fabs((middleIdx - sublayerIdx)) * instanceDelay ;
|
|
|
|
subAnim.beginTime += begin; break;
|
|
|
|
}
|
|
|
|
case 3: {
|
|
|
|
CGFloat middleIdx = sublayersCount/2.0f;
|
|
|
|
CGFloat begin = (middleIdx - fabs((middleIdx - sublayerIdx))) * instanceDelay ;
|
|
|
|
subAnim.beginTime += begin; break;
|
|
|
|
}
|
|
|
|
case 4: {
|
|
|
|
//Add yours here
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
[sublayers enumerateObjectsWithOptions:0 usingBlock:^(CALayer *sublayer, NSUInteger idx, BOOL *stop) {
|
|
|
|
if ([anim isKindOfClass:[CAAnimationGroup class]]) {
|
|
|
|
CAAnimationGroup *groupAnim = (CAAnimationGroup*)anim.copy;
|
|
|
|
NSMutableArray *newSubAnimations = [NSMutableArray arrayWithCapacity:groupAnim.animations.count];
|
|
|
|
for (CABasicAnimation *subAnim in groupAnim.animations) {
|
|
|
|
[newSubAnimations addObject:subAnim.copy];
|
|
|
|
}
|
|
|
|
|
|
|
|
groupAnim.animations = newSubAnimations;
|
|
|
|
NSArray *animations = ((CAAnimationGroup*)groupAnim).animations;
|
|
|
|
for (CABasicAnimation *sub in animations) {
|
|
|
|
setBeginTime(sub, idx);
|
|
|
|
|
|
|
|
//Reverse animation if needed
|
|
|
|
if (reverse) [self reverseAnimation:sub totalDuration:totalDuration];
|
|
|
|
}
|
|
|
|
[sublayer addAnimation:groupAnim forKey:key];
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
CABasicAnimation *copiedAnim = anim.copy;
|
|
|
|
setBeginTime(copiedAnim, idx);
|
|
|
|
[sublayer addAnimation:copiedAnim forKey:key];
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if (TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE)
|
|
|
|
+ (UIBezierPath*)alignToBottomPath:(UIBezierPath*)path layer:(CALayer*)layer{
|
|
|
|
CGFloat diff = CGRectGetMaxY(layer.bounds) - CGRectGetMaxY(path.bounds);
|
|
|
|
CGAffineTransform affineTransform = CGAffineTransformTranslate(CGAffineTransformIdentity, 0, diff);
|
|
|
|
[path applyTransform:affineTransform];
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (UIBezierPath*)offsetPath:(UIBezierPath*)path by:(CGPoint)offset{
|
|
|
|
CGAffineTransform affineTransform = CGAffineTransformTranslate(CGAffineTransformIdentity, offset.x, offset.y);
|
|
|
|
[path applyTransform:affineTransform];
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
+ (NSBezierPath*)offsetPath:(NSBezierPath*)path by:(CGPoint)offset{
|
|
|
|
NSAffineTransform* xfm = [NSAffineTransform transform];
|
|
|
|
[xfm translateXBy:offset.x yBy:offset.y];
|
|
|
|
[path transformUsingAffineTransform:xfm];
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
#if (TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE)
|
|
|
|
#else
|
|
|
|
@implementation NSBezierPath (Path)
|
|
|
|
|
|
|
|
- (CGPathRef)quartzPath
|
|
|
|
{
|
|
|
|
NSInteger i, numElements;
|
|
|
|
CGPathRef immutablePath = NULL;
|
|
|
|
numElements = self.elementCount;
|
|
|
|
|
|
|
|
if (numElements > 0)
|
|
|
|
{
|
|
|
|
CGMutablePathRef path = CGPathCreateMutable();
|
|
|
|
NSPoint points[3];
|
|
|
|
BOOL didClosePath = YES;
|
|
|
|
|
|
|
|
for (i = 0; i < numElements; i++)
|
|
|
|
{
|
|
|
|
switch ([self elementAtIndex:i associatedPoints:points])
|
|
|
|
{
|
|
|
|
case NSMoveToBezierPathElement:
|
|
|
|
CGPathMoveToPoint(path, NULL, points[0].x, points[0].y);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NSLineToBezierPathElement:
|
|
|
|
CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y);
|
|
|
|
didClosePath = NO;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NSCurveToBezierPathElement:
|
|
|
|
CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y,
|
|
|
|
points[1].x, points[1].y,
|
|
|
|
points[2].x, points[2].y);
|
|
|
|
didClosePath = NO;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NSClosePathBezierPathElement:
|
|
|
|
CGPathCloseSubpath(path);
|
|
|
|
didClosePath = YES;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!didClosePath){
|
|
|
|
//CGPathCloseSubpath(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
immutablePath = CGPathCreateCopy(path);
|
|
|
|
CGPathRelease(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
return immutablePath;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation NSImage (cgImage)
|
|
|
|
|
|
|
|
-(CGImageRef)cgImage{
|
|
|
|
NSData* data = self.TIFFRepresentation;
|
|
|
|
CGImageRef imageRef = NULL;
|
|
|
|
CGImageSourceRef sourceRef;
|
|
|
|
|
|
|
|
sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
|
|
|
|
if(sourceRef) {
|
|
|
|
imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, NULL);
|
|
|
|
CFRelease(sourceRef);
|
|
|
|
}
|
|
|
|
return imageRef;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
#endif
|