子类化 CAShapeLayer 的正确方法
Correct way to subclass CAShapeLayer
受到 this example 的启发,我为楔形和圆弧创建了自定义 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()
,但无济于事。
有几个技巧可以让它正常工作:
不要使用 didSet
。相反,您应该覆盖 display()
并在那里更新 self.path
。
使用(presentation() as! WedgeLayer? ?? self).sweep
访问属性。 (presentation
,在 Obj-C 中称为 presentationLayer
和 Swift 2,允许您在动画期间访问当前可见的 属性 值。)
如果您想要隐式动画,请按照 this answer.
中所述实施 actionForKey
受到 this example 的启发,我为楔形和圆弧创建了自定义 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()
,但无济于事。
有几个技巧可以让它正常工作:
不要使用
didSet
。相反,您应该覆盖display()
并在那里更新self.path
。使用
(presentation() as! WedgeLayer? ?? self).sweep
访问属性。 (presentation
,在 Obj-C 中称为presentationLayer
和 Swift 2,允许您在动画期间访问当前可见的 属性 值。)如果您想要隐式动画,请按照 this answer.
中所述实施
actionForKey