在动画期间无法正确定位子层

Trouble positioning a sublayer correctly during animation

问题是我应该如何定义和设置我的形状图层的位置以及应该如何更新它以便图层出现在我期望它在动画期间出现的位置?也就是说,形状应该粘在棍子的末端。

我有一个名为 containerLayer 的 CALayer 实例,它有一个子层,它是一个名为 shape 的 CAShapeLayer 实例。 containerLayer 应该将 shape 放置在特定位置 unitLoc,如下所示:

class ContainerLayer: CALayer, CALayerDelegate { 

    // ...
    override func layoutSublayers() {
        super.layoutSublayers()
        if !self.didSetup {
            self.setup()
            self.didSetup = true
        }
        updateFigure()
        setNeedsDisplay()
    }

    func updateFigure() {
        figureCenter = self.bounds.center
        figureDiameter = min(self.bounds.width, self.bounds.height)
        figureRadius = figureDiameter/2

        shapeDiameter = round(figureDiameter / 5)
        shapeRadius = shapeDiameter/2

        locRadius = figureRadius - shapeRadius
        angle = -halfPi
        unitLoc = CGPoint(x: self.figureCenter.x + cos(angle) * locRadius, y: self.figureCenter.y + sin(angle) * locRadius)

        shape.bounds = CGRect(x: 0, y: 0, width: shapeDiameter, height: shapeDiameter)
        shape.position = unitLoc
        shape.updatePath()
    }
    // ...
}

我无法找到正确的方法来指定此位置之前和更改 containerLayer.bounds 的调整大小动画期间的位置。我确实知道我遇到的问题是我没有以动画将按照我期望的方式显示它的方式设置位置。

我已经尝试使用 CABasicAnimation(keyPath: "position") 为位置设置动画,它比我之前尝试的效果有所改善,但它仍然关闭。

@objc func resize(sender: Any) {

    // MARK:- animate containerLayer bounds & shape position
    // capture bounds value before changing
    let oldBounds = self.containerLayer.bounds
    // capture shape position value before changing
    let oldPos = self.containerLayer.shape.position

    // update the constraints to change the bounds
    isLarge.toggle()
    updateConstraints()
    self.view.layoutIfNeeded()
    let newBounds = self.containerLayer.bounds
    let newPos = self.containerLayer.unitLoc

    // set up the bounds animation and add it to containerLayer
    let baContainerBounds = CABasicAnimation(keyPath: "bounds")
    baContainerBounds.fromValue = oldBounds
    baContainerBounds.toValue = newBounds
    containerLayer.add(baContainerBounds, forKey: "bounds")

    // set up the position animation and add it to shape layer
    let baShapePosition = CABasicAnimation(keyPath: "position")
    baShapePosition.fromValue = oldPos
    baShapePosition.toValue = newPos
    containerLayer.shape.add(baShapePosition, forKey: "position")
    
    containerLayer.setNeedsLayout()
    self.view.layoutIfNeeded()
}

我也试过像这样用presentation layer来设置位置,貌似也能关闭,但还是关闭了。

class ViewController: UIViewController {
    //...
    override func loadView() {
        super.loadView()
        displayLink = CADisplayLink(target: self, selector: #selector(animationDidUpdate))
        displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.default)
        //...
    }

    @objc func animationDidUpdate(displayLink: CADisplayLink) {
        let newCenter = self.containerLayer.presentation()!.bounds.center
        let new = CGPoint(x: newCenter.x + cos(containerLayer.angle) * containerLayer.locRadius, y: newCenter.y + sin(containerLayer.angle) * containerLayer.locRadius)
        containerLayer.shape.position = new
    }
    //...
}

class ContainerLayer: CALayer, CALayerDelegate {
    // ...
    func updateFigure() {
        //...
        //shape.position = unitLoc
        //...
    }
    // ...
}

稍微夸张一下,我能够更清楚地说明您的代码中发生了什么。在我的示例中,圆形图层应该保持背景视图高度的 1/3:

动画开始时,背景视图已在动画的结束 处设置为其最终大小。你看不到,因为动画依赖于描绘层的表示层,它是不变的;但观点本身已经改变。因此,当您定位形状图层的形状时,并且根据视图进行定位时,您是在调整大小并将其定位在动画 需要的位置 结束。因此它到它的最终大小和位置,只有当我们到达动画结束时才有意义。

好的,但现在考虑一下:

这样不是更好吗?它是如何完成的?好吧,使用我已经掌握的原则 ,我得到了一个带有自定义动画的层 属性。结果是在动画的每一帧上,我都会得到一个事件(该层的 draw(in:) 方法)。我通过重新计算形状层的路径来对此做出回应。因此,我在 动画的每一帧 上为形状层提供了一条新路径,因此它表现得很流畅。它停留在正确的位置,它根据背景视图的大小平滑地调整大小,并且它的笔触粗细始终保持不变。