2016-05-26 75 views
0

一切!動畫自定義的UIView屬性,斯威夫特

我已創建使用CoreGraphics中,看起來和更新等這樣的圓形進度視圖:

50% enter image description here 75% enter image description here

的類是一個UIView類,它具有一個所謂的可變決定多少圈的「進步」填充。

它運作良好,但我希望能夠以動畫更改進度變量使得杆動畫順利。

我已經遠離了我需要有一個CALayer的類View類,這是我取得了一起,但是,它並沒有在動畫無數的所有實例讀取。

兩個問題:

  1. 我能保留我核芯顯卡畫圖形,或者我需要以某種方式重新繪製它的CALayer?
  2. 我目前(嘗試)的解決方案在底部崩潰: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

    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) 
        } 
    } 
    

    }

感謝您的幫助!

+0

你有沒有嘗試過做類似:http://www.informit.com/articles/article.aspx?p=1431312&seqNum=5? –

+0

我相信在actionForKey() - > CAAction中發生了什麼? –

回答

6

嘗試了這一點:)

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() 
    } 
} 
+0

謝謝!我知道可以提供示例代碼,但是這種方式就像一種魅力。它看起來像我在錯誤的地方有正確的元素。感謝您向我展示如何在CA Layer中繪製核心圖形元素,並將動畫嵌入到didSet :) –

0

雖然AntonTheDev提供了一個很好的答案,他的解決方案不允許你進行動畫處理的CircularProgressView在動畫塊,所以你不能做整潔的事物,如:

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

還有一個類似的問題在這裏與最新的斯威夫特3答案根據接受的答案和this後的想法。 這是最終解決方案的樣子。

the final product

Swift 3 Solution