Convert/Wrap Swift struct as NSValue for CAAnimation purposes?

人盡茶涼 提交于 2019-12-10 15:12:47

问题


I have a custom CALayer subclass that draws a circular arc. It looks something like:

class ArcLayer: CALayer {
    var strokeColor:UIColor = UIColor.blackColor() { didSet { self.setNeedsDisplay() }}
    var strokeWidth:CGFloat = 1.0 { didSet { self.setNeedsDisplay() }}
    var strokeCap:CGLineCap = .Butt { didSet { self.setNeedsDisplay() }}
    var startRadians:CGFloat = 0.0 { didSet { self.setNeedsDisplay() }}
    var sweepRadians:CGFloat = Tau  { didSet { self.setNeedsDisplay() }}

    // other init's omitted for brevity 

    override init(layer: AnyObject) {
        super.init(layer: layer)
        if let layer = layer as? ArcLayer {
            self.strokeColor = layer.strokeColor
            self.strokeWidth = layer.strokeWidth
            self.strokeCap = layer.strokeCap
            self.startRadians = layer.startRadians
            self.sweepRadians = layer.sweepRadians
        }
    }

    override func drawInContext(ctx: CGContext) {
        ...
    }

    override class func needsDisplayForKey(key: String) -> Bool {
        return key == "startRadians" || key == "sweepRadians" || super.needsDisplayForKey(key)
    }
}

I'm using a custom subclass, because while the path of a CAShapeLayer is animatable, it does not animate in the way you would expect the arc to rotate around. The above works. Animations to the start/sweep produce the desired effect.

I want to change it though. I have an Angle struct (see this answer) which I would rather use than the raw radian CGFloat value. Changing the type of those two vars to Angle compiles fine. And the original display works too (after making the appropriate conversions/extractions from the Angle vars). THE PROBLEM is being able to animate those vars now. Where as before I might have written some code like:

let sweep = CABasicAnimation(keyPath: "sweepRadians")
sweep.fromValue = 0.0 // raw radians value
sweep.toValue = Tau // raw radians value for one rotation
sweep.duration = 10.seconds
sweep.repeatCount = 0
self.blueRing.addAnimation(sweep, forKey: "sweepRadians")

I can't just change those to Angle values:

sweep.fromValue = Angle(raw: 0, unit: .Rotations)
sweep.toValue = Angle(raw: 0, unit: .Rotations)

That results in a

cannot assign value of type 'Angle' to type of 'AnyObject?'

I'm hoping that the solution is that I need to somehow convert/wrap my Angle values as NSValue objects? Is that indeed correct? Can it be done? How so?

I really like Swift, but the corner cases where it intersects with Objc can be really confusing.


回答1:


Angle is a struct, which does not conform to AnyObject. In order to conform to AnyObject, you need to change it to a class, or wrap it into a class like Box:

final class Box<T> {
    let value: T
    init(_ value: T) { self.value = value }
}

That said, this is unlikely to work. CABasicAnimation has special knowledge about how to interpolate specific things:

  • integers and doubles
  • CGRect, CGPoint, CGSize, and CGAffineTransform structures
  • CATransform3D data structures
  • CGColor and CGImage references

I don't believe there's any way to add to this list. I don't believe you can use CABasicAnimation to interpolate arbitrary types. You need to animate from a number to a number, so that's going to be basically the code you have above.

Of course you can make sweepRadians be a computed property that just forwards to sweep, and you could animate from Degrees(0).rawRadians to Degrees(360).rawRadians. So you can get most of the practical benefits you want; just not without manually converting to a number at some point.

Now if this is Mac, you can subclass NSAnimation and build your own animator that can do this. See CABasicAnimation and custom types for discussion of that. But there's no NSAnimation on iOS, and I don't believe you can subclass CAAnimation in the same way. There's no "progress" delegate method on CAAnimation.




回答2:


I think you should try using init(pointer pointer: UnsafePointer<Void>)of NSValue. You may obtain the pointer to your Angle instance using func withUnsafePointer<T, Result>(inout arg: T, @noescape _ body: UnsafePointer<T> throws -> Result) rethrows -> Result (see http://swiftdoc.org/v2.0/func/withUnsafePointer/ for reference). P.S. The idea of passing NSValue-wrapped things to Core Animation is a working technique in general. It's still unclear if CA will unwrap Angle though. Good luck.



来源:https://stackoverflow.com/questions/32893429/convert-wrap-swift-struct-as-nsvalue-for-caanimation-purposes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!