子类化 CAShapeLayer 的正确方法

Posted

技术标签:

【中文标题】子类化 CAShapeLayer 的正确方法【英文标题】:Correct way to subclass CAShapeLayer 【发布时间】:2015-10-06 00:39:19 【问题描述】:

受this example 的启发,我为楔形和弧形创建了自定义CALayer 子类。允许我绘制弧线和楔形,并在其中设置动画变化,使它们呈放射状扫过。

其中一个令人沮丧的地方是,显然,当您走这条拥有子类drawInContext() 的路线时,您会受到图层框架剪辑的限制。对于库存层,您拥有masksToBounds,默认情况下为false!但似乎一旦你使用绘图的子类路线,那是因为隐含且不变的true

所以我想我会尝试不同的方法,通过子类化 CAShapeLayer 来代替。而不是添加自定义drawInContext(),我只需让startsweep 的变量更新接收器的path。这很好用,但它不再像以前那样动画了:

import UIKit

class WedgeLayer:CAShapeLayer 
    var start:Angle = 0.0.rotations  didSet  self.updatePath() 
    var sweep:Angle = 1.0.rotations   didSet  self.updatePath() 
    dynamic var startRadians:CGFloat  get return self.start.radians.raw  set self.start = newValue.radians
    dynamic var sweepRadians:CGFloat  get return self.sweep.radians.raw  set self.sweep = newValue.radians

    // more dynamic unit variants omitted
    // we have these alternate interfaces because you must use a type
    // which the animation framework can understand for interpolation purposes

    override init(layer: AnyObject) 
        super.init(layer: layer)
        if let layer = layer as? WedgeLayer 
            self.color = layer.color
            self.start = layer.start
            self.sweep = layer.sweep
        
    

    override init() 
        super.init()
    

    required init(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    func updatePath() 
        let center = self.bounds.midMid
        let radius = center.x.min(center.y)
        print("updating path \(self.start) radius \(radius)")

        if self.sweep.abs < 1.rotations 
            let _path = UIBezierPath()
            _path.moveToPoint(center)
            _path.addArcWithCenter(center, radius: radius, startAngle: self.start.radians.raw, endAngle: (self.start + self.sweep).radians.raw, clockwise: self.sweep >= 0.radians ? true : false)
            _path.closePath()
            self.path = _path.CGPath
        
        else 
            self.path = UIBezierPath(ovalInRect: CGRect(around: center, width: radius * 2, height: radius * 2)).CGPath
        
    

    override class func needsDisplayForKey(key: String) -> Bool 
        return key == "startRadians" || key == "sweepRadians" || key == "startDegrees" || key == "sweepDegrees" || key == "startRotations" || key == "sweepRotations" || super.needsDisplayForKey(key)
    

是否不可能让它重新生成路径并在动画值时更新?使用print() 语句,我可以看到它在动画期间按预期通过值进行插值。我尝试在不同位置添加setNeedsDisplay(),但无济于事。

【问题讨论】:

【参考方案1】:

有几个技巧可以让它正常工作:

不要使用didSet。相反,您应该覆盖 display() 并在那里更新 self.path

使用(presentation() as! WedgeLayer? ?? self).sweep 访问该属性。 (presentation,在 Obj-C 和 Swift 2 中称为 presentationLayer,允许您在动画期间访问当前可见的属性值。)

如果您想要隐式动画,请按照this answer 中的说明实现actionForKey

【讨论】:

真棒和简洁的答案!做了前两颗子弹,它奏效了。我能否请求您添加一些解释,说明为什么这种方法有效,而我的无效?特别是第二个要点正在做什么。它显然克服了第一个原因。当我执行 just 第一个时,我注意到通过我的 print() 语句,值并没有随着它的动画而改变(这是我使用原始方法看到的回归)。

以上是关于子类化 CAShapeLayer 的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

子类化 UIView 是处理未知数量的这些自定义对象的正确方法吗?

UITableViewHeaderFooterView 的正确子类化和自动布局

Typescript - 从基类中的静态方法实例化子类,使用 args 并引用其他静态方法

为 UIView 子类加载 Nib 的正确方法

子类化并覆盖 PySide2 小部件方法;我在哪里可以找到参考资料?

公开类拥有的资源的正确方法是啥?