具有动画路径内容的CALayer,使Inspectable / Designable

时间:2016-12-02 10:41:49

标签: ios core-animation calayer cgpath ibinspectable

我通过继承UIView创建了一个自定义循环进度视图,在其图层中添加了CAShapeLayer子图层并覆盖drawRect()以更新形状图层path属性。

通过制作视图@IBDesignableprogress属性@IBInspectable,我能够在Interface Builder中编辑其值,并实时查看更新的贝塞尔曲线路径。非必要,但非常酷!

接下来,我决定将路径设置为动画:每当你在代码中设置一个新值时,指示进度的弧应该"增长"从零长度到圆圈的任何百分比(想想苹果手表活动应用中的弧线)。

为实现这一目标,我将CAShapeLayer子图层替换为具有CALayer@dynamic)属性的自定义@NSManaged子类,被视为动画的关键(我实现了) needsDisplayForKey()actionForKey()drawInContext()等。)

我的查看代码(相关部分)如下所示:

// Triggers path update (animated)
private var progress: CGFloat = 0.0 {
    didSet {
        updateArcLayer()
    }
}

// Programmatic interface:
// (pass false to achieve immediate change)
func setValue(newValue: CGFloat, animated: Bool) {
    if animated {
        self.progress = newValue
    } else {
        arcLayer.animates = false
        arcLayer.removeAllAnimations()
        self.progress = newValue
        arcLayer.animates = true
    }
}

// Exposed to Interface Builder's  inspector: 
@IBInspectable var currentValue: CGFloat {
    set(newValue) {
        setValue(newValue: currentValue, animated: false)
        self.setNeedsLayout()
    }
    get {
        return progress
    }
}

private func updateArcLayer() {
    arcLayer.frame = self.layer.bounds
    arcLayer.progress = progress
}

图层代码:

var animates: Bool = true
@NSManaged var progress: CGFloat

override class func needsDisplay(forKey key: String) -> Bool {
    if key == "progress" {
        return true
    }
    return super.needsDisplay(forKey: key)
}

override func action(forKey event: String) -> CAAction? {
    if event == "progress" && animates == true {
        return makeAnimation(forKey: event)
    }
    return super.action(forKey: event)
}

override func draw(in ctx: CGContext) {
    ctx.beginPath()

    // Define the arcs...
    ctx.closePath()
    ctx.setFillColor(fillColor.cgColor)
    ctx.drawPath(using: CGPathDrawingMode.fill)
}

private func makeAnimation(forKey event: String) -> CABasicAnimation? {
    let animation = CABasicAnimation(keyPath: event)

    if let presentationLayer = self.presentation() {
        animation.fromValue = presentationLayer.value(forKey: event)
    }

    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
    animation.duration = animationDuration
    return animation
}

动画有效,但现在我无法在Interface Builder中显示我的路径

我尝试过像这样实现我的观点prepareForInterfaceBuilder()

override func prepareForInterfaceBuilder() {
    super.prepareForInterfaceBuilder()

    self.topLabel.text = "Hello, Interface Builder!"
    updateArcLayer()
}

...标签文本更改会反映在Interface Builder中,但路径不会被渲染。

我错过了什么吗?

1 个答案:

答案 0 :(得分:1)

嗯,不是很有趣......事实证明我的@Inspectable财产的声明有一个非常愚蠢的错误。

你能发现它吗?

@IBInspectable var currentValue: CGFloat {
    set(newValue) {
        setValue(newValue: currentValue, animated: false)
        self.setNeedsLayout()
    }
    get {
        return progress
    }
}

应该是:

@IBInspectable var currentValue: CGFloat {
    set(newValue) {
        setValue(newValue: newValue, animated: false)
        self.setNeedsLayout()
    }
    get {
        return progress
    }
}

也就是说,我丢弃了传递的值(newValue)并使用当前的值(currentValue)来代替设置内部变量。 "记录" Interface Builder的值(总是0.0)(通过我的标签' s text属性!)给了我一个线索。

现在它工作正常,不需要drawRect()等。