Правильный способ подкласса CAShapeLayer

Вдохновившись этим примером, я создали пользовательские CALayer подклассы для клиньев и дуг. Они позволяют мне рисовать дуги и клинья и анимировать изменения в них, чтобы они проходили радиально.

Одно из разочарований в них заключается в том, что, по-видимому, когда вы идете по этому пути с подклассом drawInContext(), вы ограничены клипом кадра слоя. Со стандартными слоями у вас есть masksToBounds, который по умолчанию равен false! Но кажется, что когда-то вы создали маршрут подкласса с рисованием, потому что неявно и неизменно true.

Поэтому я решил попробовать другой подход, вместо этого создав подкласс CAShapeLayer. И вместо добавления пользовательского drawInContext() я бы просто изменил свои переменные для start и sweep на 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() в разные места, но безрезультатно.


person Travis Griggs    schedule 06.10.2015    source источник


Ответы (1)


Есть пара хитростей, чтобы заставить это работать правильно:

  • Не используйте didSet. Скорее вам следует переопределить display() и обновить там self.path.

  • Используйте (presentation() as! WedgeLayer? ?? self).sweep для доступа к свойству. (presentation, известный как presentationLayer в Obj-C и Swift 2, позволяет вам получить доступ к видимым в данный момент значениям свойств во время анимации.)

  • Если вам нужна неявная анимация, реализуйте actionForKey, как описано в этом ответе.

person jtbandes    schedule 06.10.2015
comment
Потрясающий и лаконичный ответ! Сделал первые две пули и все заработало. Могу я попросить вас добавить какое-то объяснение, почему этот подход работает, а мой нет? Особенно то, что делает второй пункт. Это явно преодоление чего-то из первых причин. Когда я сделал только первый, я заметил с помощью моего оператора print(), что значения не менялись при анимации (что было регрессом по сравнению с тем, что я видел с моим первоначальным подходом). - person Travis Griggs; 06.10.2015