在UITableviewCell高度动画化的同时对CALayer阴影进行动画处理 [英] Animating CALayer shadow simultaneously as UITableviewCell height animates

查看:31
本文介绍了在UITableviewCell高度动画化的同时对CALayer阴影进行动画处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个UITableView,我正在尝试使用其 beginUpdates endUpdates 方法展开和折叠,并在发生这种情况时显示阴影.在我的自定义UITableViewCell中,我有一个图层,可在 layoutSubviews :

I have a UITableView that I am attempting to expand and collapse using its beginUpdates and endUpdates methods and have a drop shadow displayed as that's happening. In my custom UITableViewCell, I have a layer which I create a shadow for in layoutSubviews:

self.shadowLayer.shadowColor = self.shadowColor.CGColor;
self.shadowLayer.shadowOffset = CGSizeMake(self.shadowOffsetWidth, self.shadowOffsetHeight);
self.shadowLayer.shadowOpacity = self.shadowOpacity;
self.shadowLayer.masksToBounds = NO;
self.shadowLayer.frame = self.layer.bounds;
// this is extremely important for performance when drawing shadows
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.shadowLayer.frame cornerRadius:self.cornerRadius];
self.shadowLayer.shadowPath = shadowPath.CGPath;

我将此层添加到 viewDidLoad 中的UITableViewCell中:

I add this layer to the UITableViewCell in viewDidLoad:

self.shadowLayer = [CALayer layer];
self.shadowLayer.backgroundColor = [UIColor whiteColor].CGColor;
[self.layer insertSublayer:self.shadowLayer below:self.contentView.layer];

据我了解,当我调用 beginUpdates 时,如果当前运行循环不存在,则会为当前运行循环创建一个隐式的 CALayerTransaction .此外,还会调用 layoutSubviews .这里的问题是,基于UITableViewCell的新大小立即绘制了生成的阴影.我确实需要阴影以在实际图层进行动画处理时继续以预期的方式进行投射.

As I understand it, when I call beginUpdates, an implicit CALayerTransaction is made for the current run loop if none exists. Additionally, layoutSubviews also gets called. The problem here is that the resulting shadow is drawn immediately based on the new size of the UITableViewCell. I really need to shadow to continue to cast in the expected way as the actual layer is animating.

由于我创建的图层不是支持CALayer,因此它在没有显式指定CATransaction的情况下进行动画处理.但是,据我了解,我确实需要某种方法来抓住 beginUpdates / endUpdates CATransaction并在其中执行动画.如果有的话,我该怎么办?

Since my created layer is not a backing CALayer it animates without explicitly specifying a CATransaction, which is expected. But, as I understand it, I really need some way to grab hold of beginUpdates/endUpdates CATransaction and perform the animation in that. How do I do that, if at all?

推荐答案

所以我猜你有这样的东西:

So I guess you have something like this:

(我在模拟器中打开了"Debug> Slow Animations".)而且,您不喜欢阴影跳到其新大小的方式.您想要这个:

(I turned on "Debug > Slow Animations" in the simulator.) And you don't like the way the shadow jumps to its new size. You want this instead:

您可以在此github存储库中找到我的测试项目 .

You can find my test project in this github repository.

选择动画参数并在表视图的动画块中添加动画是棘手的,但并非并非不可能.最棘手的部分是,您需要在阴影视图本身或阴影视图的直接父视图的 layoutSubviews 方法中更新 shadowPath .在我的演示视频中,这意味着 shadowPath 需要通过绿色框视图或绿色框的直接父视图的 layoutSubviews 方法进行更新.

It is tricky but not impossible to pick up the animation parameters and add an animation in the table view's animation block. The trickiest part is that you need to update the shadowPath in the layoutSubviews method of the shadowed view itself, or of the shadowed view's immediate superview. In my demo video, that means that the shadowPath needs to be updated by the layoutSubviews method of the green box view or the green box's immediate superview.

我选择创建一个 ShadowingView 类,其唯一的工作就是绘制并为其子视图之一的阴影设置动画.这是界面:

I chose to create a ShadowingView class whose only job is to draw and animate the shadow of one of its subviews. Here's the interface:

@interface ShadowingView : UIView

@property (nonatomic, strong) IBOutlet UIView *shadowedView;

@end

要使用 ShadowingView ,我将其添加到情节提要中的单元格视图中.实际上,它嵌套在单元内部的堆栈视图中.然后,我将绿色框添加为 ShadowingView 的子视图,并将 shadowedView 插座连接到绿色框.

To use ShadowingView, I added it to my cell view in my storyboard. Actually it's nested inside a stack view inside the cell. Then I added the green box as a subview of the ShadowingView and connected the shadowedView outlet to the green box.

ShadowingView 实现包含三个部分.一种是其 layoutSubviews 方法,该方法在其自己的图层上设置图层阴影属性,以在其 shadowedView 子视图周围绘制阴影:

The ShadowingView implementation has three parts. One is its layoutSubviews method, which sets up the layer shadow properties on its own layer to draw a shadow around its shadowedView subview:

@implementation ShadowingView

- (void)layoutSubviews {
    [super layoutSubviews];

    CALayer *layer = self.layer;
    layer.backgroundColor = nil;

    CALayer *shadowedLayer = self.shadowedView.layer;
    if (shadowedLayer == nil) {
        layer.shadowColor = nil;
        return;
    }

    NSAssert(shadowedLayer.superlayer == layer, @"shadowedView must be my direct subview");

    layer.shadowColor = UIColor.blackColor.CGColor;
    layer.shadowOffset = CGSizeMake(0, 1);
    layer.shadowOpacity = 0.5;
    layer.shadowRadius = 3;
    layer.masksToBounds = NO;

    CGFloat radius = shadowedLayer.cornerRadius;
    layer.shadowPath = CGPathCreateWithRoundedRect(shadowedLayer.frame, radius, radius, nil);
}

当此方法在动画块中运行时(例如,表格视图为单元格大小的动画设置动画时),并且该方法设置了 shadowPath 时,Core Animation将寻找一个更新 shadowPath 后运行的操作".它的外观之一是通过将 actionForLayer:forKey:发送到图层的委托,而委托是 ShadowingView .因此,我们覆盖 actionForLayer:forKey:,以在可能和适当的情况下提供一个操作.如果不能,我们就叫 super .

When this method is run inside an animation block (as is the case when the table view animates a change in the size of a cell), and the method sets shadowPath, Core Animation looks for an "action" to run after updating shadowPath. One of the ways it looks is by sending actionForLayer:forKey: to the layer's delegate, and the delegate is the ShadowingView. So we override actionForLayer:forKey: to provide an action if possible and appropriate. If we can't, we just call super.

很重要的一点是,Core Animation要求在 shadowPath 设置器内部进行操作,之前实际更改 shadowPath 的值.

It is important to understand that Core Animation asks for the action from inside the shadowPath setter, before actually changing the value of shadowPath.

要提供操作,请确保键为 @"shadowPath" ,并确保 shadowPath 的现有值,并且已存在动画 bounds.size 的图层.为什么我们要寻找现有的 bounds.size 动画?由于该现有动画具有持续时间和计时功能,因此我们应该使用动画来创建 shadowPath .如果一切正常,我们将获取现有的 shadowPath ,制作动画副本,将其存储在动作中,然后返回动作:

To provide the action, we make sure the key is @"shadowPath", that there is an existing value for shadowPath, and that there is already an animation on the layer for bounds.size. Why do we look for an existing bounds.size animation? Because that existing animation has the duration and timing function we should use to animate shadowPath. If everything is in order, we grab the existing shadowPath, make a copy of the animation, store them in an action, and return the action:

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
    if (![event isEqualToString:@"shadowPath"]) { return [super actionForLayer:layer forKey:event]; }

    CGPathRef priorPath = layer.shadowPath;
    if (priorPath == NULL) { return [super actionForLayer:layer forKey:event]; }

    CAAnimation *sizeAnimation = [layer animationForKey:@"bounds.size"];
    if (![sizeAnimation isKindOfClass:[CABasicAnimation class]]) { return [super actionForLayer:layer forKey:event]; }

    CABasicAnimation *animation = [sizeAnimation copy];
    animation.keyPath = @"shadowPath";
    ShadowingViewAction *action = [[ShadowingViewAction alloc] init];
    action.priorPath = priorPath;
    action.pendingAnimation = animation;
    return action;
}

@end

该动作是什么样的?这是界面:

What does the action look like? Here's the interface:

@interface ShadowingViewAction : NSObject <CAAction>
@property (nonatomic, strong) CABasicAnimation *pendingAnimation;
@property (nonatomic) CGPathRef priorPath;
@end

该实现需要一个 runActionForKey:object:arguments:方法.在此方法中,我们使用保存的旧 shadowPath 和新的 shadowPath 更新在 actionForLayer:forKey:中创建的动画,并然后我们将动画添加到图层中.

The implementation requires a runActionForKey:object:arguments: method. In this method, we update the animation that we created in actionForLayer:forKey: using the saved-away old shadowPath and the new shadowPath, and then we add the animation to the layer.

我们还需要管理已保存路径的保留计数,因为ARC不管理 CGPath 对象.

We also need to manage the retain count of the saved path, because ARC doesn't manage CGPath objects.

@implementation ShadowingViewAction

- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict {
    if (![anObject isKindOfClass:[CALayer class]] || _pendingAnimation == nil) { return; }
    CALayer *layer = anObject;
    _pendingAnimation.fromValue = (__bridge id)_priorPath;
    _pendingAnimation.toValue = (__bridge id)layer.shadowPath;
    [layer addAnimation:_pendingAnimation forKey:@"shadowPath"];
}

- (void)setPriorPath:(CGPathRef)priorPath {
    CGPathRetain(priorPath);
    CGPathRelease(_priorPath);
    _priorPath = priorPath;
}

- (void)dealloc {
    CGPathRelease(_priorPath);
}

@end

这篇关于在UITableviewCell高度动画化的同时对CALayer阴影进行动画处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆