在Swift中animation自定义UIView属性

所有!

我创build了一个使用CoreGraphics的循环进度视图,看起来和更新像这样:

50% 在这里输入图像说明 75% 在这里输入图像说明

这个类是一个UIView类,它有一个名为“progress”的variables,它决定了多less圆被填充。

它运作良好,但我想能够animation变化的进度variables,使酒吧animation顺利。

我已经从无数的例子中看到,我需要有一个CALayer类和我所做的View类,但是它根本没有animation。

两个问题:

  1. 我可以保留在Core Graphics中绘制的graphics,还是需要以某种方式在CALayer中重绘它?
  2. 我目前(尝试)的解决scheme崩溃在: anim.fromValue = pres.progress底部。 这是怎么回事?

    class CircleProgressView: UIView { 

     @IBInspectable var backFillColor: UIColor = UIColor.blueColor() @IBInspectable var fillColor: UIColor = UIColor.greenColor() @IBInspectable var strokeColor: UIColor = UIColor.greenColor() dynamic var progress: CGFloat = 0.00 { didSet { self.layer.setValue(progress, forKey: "progress") } } var distToDestination: CGFloat = 10.0 @IBInspectable var arcWidth: CGFloat = 20 @IBInspectable var outlineWidth: CGFloat = 5 override class func layerClass() -> AnyClass { return CircleProgressLayer.self } override func drawRect(rect: CGRect) { var fillColor = self.fillColor if distToDestination < 3.0 { fillColor = UIColor.greenColor() } else { fillColor = self.fillColor } //Drawing the inside of the container //Drawing in the container let center = CGPoint(x:bounds.width/2, y: bounds.height/2) let radius: CGFloat = max(bounds.width, bounds.height) - 10 let startAngle: CGFloat = 3 * π / 2 let endAngle: CGFloat = 3 * π / 2 + 2 * π let path = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: startAngle, endAngle: endAngle, clockwise: true) path.lineWidth = arcWidth backFillColor.setStroke() path.stroke() let fill = UIColor.blueColor().colorWithAlphaComponent(0.15) fill.setFill() path.fill() //Drawing the fill path. Same process let fillAngleLength = (π) * progress let fillStartAngle = 3 * π / 2 - fillAngleLength let fillEndAngle = 3 * π / 2 + fillAngleLength let fillPath_fill = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: fillStartAngle, endAngle: fillEndAngle, clockwise: true) fillPath_fill.lineWidth = arcWidth fillColor.setStroke() fillPath_fill.stroke() //Drawing container outline on top let outlinePath_outer = UIBezierPath(arcCenter: center, radius: radius / 2 - outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true) let outlinePath_inner = UIBezierPath(arcCenter: center, radius: radius / 2 - arcWidth + outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true) outlinePath_outer.lineWidth = outlineWidth outlinePath_inner.lineWidth = outlineWidth strokeColor.setStroke() outlinePath_outer.stroke() outlinePath_inner.stroke() } 

    }

     class CircleProgressLayer: CALayer { @NSManaged var progress: CGFloat 

    class CircleProgressLayer: CALayer { @NSManaged var progress: CGFloat

     override class func needsDisplayForKey(key: String) -> Bool { if key == "progress" { return true } return super.needsDisplayForKey(key) } override func actionForKey(key: String) -> CAAction? { if (key == "progress") { if let pres = self.presentationLayer() { let anim: CABasicAnimation = CABasicAnimation.init(keyPath: key) anim.fromValue = pres.progress anim.duration = 0.2 return anim } return super.actionForKey(key) } else { return super.actionForKey(key) } } 

    }

谢谢您的帮助!

试试这:)

 class ViewController: UIViewController { let progressView = CircleProgressView(frame:CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200)) override func viewDidLoad() { super.viewDidLoad() let button = UIButton() button.frame = CGRectMake(0, 300, 200, 100) button.backgroundColor = UIColor.yellowColor() button.addTarget(self, action: #selector(ViewController.tap), forControlEvents: UIControlEvents.TouchUpInside) view.addSubview(button) view.addSubview(progressView) progressView.progress = 1.0 } func tap() { if progressView.progress == 0.5 { progressView.progress = 1.0 } else { progressView.progress = 0.5 } } } class CircleProgressView: UIView { dynamic var progress: CGFloat = 0.00 { didSet { let animation = CABasicAnimation() animation.keyPath = "progress" animation.fromValue = circleLayer().progress animation.toValue = progress animation.duration = Double(0.5) self.layer.addAnimation(animation, forKey: "progress") circleLayer().progress = progress } } func circleLayer() -> CircleProgressLayer { return self.layer as! CircleProgressLayer } override class func layerClass() -> AnyClass { return CircleProgressLayer.self } } class CircleProgressLayer: CALayer { @NSManaged var progress: CGFloat override class func needsDisplayForKey(key: String) -> Bool { if key == "progress" { return true } return super.needsDisplayForKey(key) } var backFillColor: UIColor = UIColor.blueColor() var fillColor: UIColor = UIColor.greenColor() var strokeColor: UIColor = UIColor.greenColor() var distToDestination: CGFloat = 10.0 var arcWidth: CGFloat = 20 var outlineWidth: CGFloat = 5 override func drawInContext(ctx: CGContext) { super.drawInContext(ctx) UIGraphicsPushContext(ctx) //Drawing in the container let center = CGPoint(x:bounds.width/2, y: bounds.height/2) let radius: CGFloat = max(bounds.width, bounds.height) - 10 let startAngle: CGFloat = 3 * CGFloat(M_PI) / 2 let endAngle: CGFloat = 3 * CGFloat(M_PI) / 2 + 2 * CGFloat(M_PI) let path = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: startAngle, endAngle: endAngle, clockwise: true) path.lineWidth = arcWidth backFillColor.setStroke() path.stroke() let fill = UIColor.blueColor().colorWithAlphaComponent(0.15) fill.setFill() path.fill() //Drawing the fill path. Same process let fillAngleLength = (CGFloat(M_PI)) * progress let fillStartAngle = 3 * CGFloat(M_PI) / 2 - fillAngleLength let fillEndAngle = 3 * CGFloat(M_PI) / 2 + fillAngleLength let fillPath_fill = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: fillStartAngle, endAngle: fillEndAngle, clockwise: true) fillPath_fill.lineWidth = arcWidth fillColor.setStroke() fillPath_fill.stroke() //Drawing container outline on top let outlinePath_outer = UIBezierPath(arcCenter: center, radius: radius / 2 - outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true) let outlinePath_inner = UIBezierPath(arcCenter: center, radius: radius / 2 - arcWidth + outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true) outlinePath_outer.lineWidth = outlineWidth outlinePath_inner.lineWidth = outlineWidth strokeColor.setStroke() outlinePath_outer.stroke() outlinePath_inner.stroke() UIGraphicsPopContext() } } 

虽然AntonTheDev提供了一个很好的答案,但是他的解决scheme不允许您在animation块中为CircularProgressView设置animation,所以你不能做一些整洁的事情:

 UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut, animations: { circularProgress.progress = 0.76 }, completion: nil) 

这里有一个类似的问题,以最新的Swift 3的答案为基础,从接受的答案和这篇文章的想法。 这是最终的解决scheme。

最终产品

Swift 3解决scheme