核心animation进度callback

有一个简单的方法可以在核心animation运行时达到特定点(例如完成的50%和66%?

我正在考虑设置一个NSTimer,但是这并不是我想要的那么准确。

我终于为这个问题开发了一个解决scheme。

本质上,我希望每一帧都被callback,并且做我需要做的事情。

观察animation的进度没有明显的方法,但它实际上是可能的:

  • 首先,我们需要创build一个名为“progress”的animation属性的CALayer的新子类。

  • 我们将图层添加到我们的树中,然后创build一个animation,在animation的持续时间内将进度值从0驱动到1。

  • 由于我们的progress属性可以是animation的,所以在我们的子类上为animation的每一帧调用drawInContext。 这个函数不需要重绘任何东西,但是它可以用来调用委托函数:)

这是类接口:

@protocol TAProgressLayerProtocol <NSObject> - (void)progressUpdatedTo:(CGFloat)progress; @end @interface TAProgressLayer : CALayer @property CGFloat progress; @property (weak) id<TAProgressLayerProtocol> delegate; @end 

并执行:

 @implementation TAProgressLayer // We must copy across our custom properties since Core Animation makes a copy // of the layer that it's animating. - (id)initWithLayer:(id)layer { self = [super initWithLayer:layer]; if (self) { TAProgressLayer *otherLayer = (TAProgressLayer *)layer; self.progress = otherLayer.progress; self.delegate = otherLayer.delegate; } return self; } // Override needsDisplayForKey so that we can define progress as being animatable. + (BOOL)needsDisplayForKey:(NSString*)key { if ([key isEqualToString:@"progress"]) { return YES; } else { return [super needsDisplayForKey:key]; } } // Call our callback - (void)drawInContext:(CGContextRef)ctx { if (self.delegate) { [self.delegate progressUpdatedTo:self.progress]; } } @end 

然后,我们可以将图层添加到我们的主图层:

 TAProgressLayer *progressLayer = [TAProgressLayer layer]; progressLayer.frame = CGRectMake(0, -1, 1, 1); progressLayer.delegate = self; [_sceneView.layer addSublayer:progressLayer]; 

并与其他animation一起animation:

 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"progress"]; anim.duration = 4.0; anim.beginTime = 0; anim.fromValue = @0; anim.toValue = @1; anim.fillMode = kCAFillModeForwards; anim.removedOnCompletion = NO; [progressLayer addAnimation:anim forKey:@"progress"]; 

最后,随着animation的进展,委托人将被回叫:

 - (void)progressUpdatedTo:(CGFloat)progress { // Do whatever you need to do... } 

如果你不想破解CALayer来向你报告进度,还有另一种方法。 从概念上讲,您可以使用CADisplayLink来保证每一帧的callback,然后简单地测量animation开始以来的时间除以持续时间,以确定完成百分比。

开放源代码库INTUAnimationEngine将这个function打包成一个非常干净的API,看起来几乎和UIView基于块的animation一样:

 // INTUAnimationEngine.h // ... + (NSInteger)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay animations:(void (^)(CGFloat percentage))animations completion:(void (^)(BOOL finished))completion; // ... 

您只需要在启动其他animation的同时调用此方法,并传递相同的值以delay duration delay ,然后对于animation的每个帧,将以当前百分比完成animations块的执行。 如果你想让你放心,你的时间是完全同步的,你可以从INTUAnimationEngine驱动你的animation。

我在接受的答案中提供了一个由tarmesbuild议的CALayer子类的Swift(2.0)实现:

 protocol TAProgressLayerProtocol { func progressUpdated(progress: CGFloat) } class TAProgressLayer : CALayer { // MARK: - Progress-related properties var progress: CGFloat = 0.0 var progressDelegate: TAProgressLayerProtocol? = nil // MARK: - Initialization & Encoding // We must copy across our custom properties since Core Animation makes a copy // of the layer that it's animating. override init(layer: AnyObject) { super.init(layer: layer) if let other = layer as? TAProgressLayerProtocol { self.progress = other.progress self.progressDelegate = other.progressDelegate } } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) progressDelegate = aDecoder.decodeObjectForKey("progressDelegate") as? CALayerProgressProtocol progress = CGFloat(aDecoder.decodeFloatForKey("progress")) } override func encodeWithCoder(aCoder: NSCoder) { super.encodeWithCoder(aCoder) aCoder.encodeFloat(Float(progress), forKey: "progress") aCoder.encodeObject(progressDelegate as! AnyObject?, forKey: "progressDelegate") } init(progressDelegate: TAProgressLayerProtocol?) { super.init() self.progressDelegate = progressDelegate } // MARK: - Progress Reporting // Override needsDisplayForKey so that we can define progress as being animatable. class override func needsDisplayForKey(key: String) -> Bool { if (key == "progress") { return true } else { return super.needsDisplayForKey(key) } } // Call our callback override func drawInContext(ctx: CGContext) { if let del = self.progressDelegate { del.progressUpdated(progress) } } }