2017-12-09 25 views
2

UIBezierPathは破線ます:ダッシュCAShapeLayerを使用せずにUIBezierPathのストロークと、このストロークをアニメーション

override func draw(_ rect: CGRect) 
    { 
     let path = UIBezierPath() 
     let p0 = CGPoint(x: self.bounds.minX, y: self.bounds.midY) 
     path.move(to: p0) 
     let p1 = CGPoint(x: self.bounds.maxX, y: self.bounds.midY) 
     path.addLine(to: p1) 

     let dashes: [ CGFloat ] = [ 0.0, 16.0 ] 
     path.setLineDash(dashes, count: dashes.count, phase: 0.0) 
     path.lineWidth = 8.0 
     path.lineCapStyle = .round 
     UIColor.red.set() 
     path.stroke() 
    } 

enter image description here

私はこのラインストロークをアニメーション化する場合CAShapeLayerのように使用する必要があります

override func draw(_ rect: CGRect) 
    { 
     let path = UIBezierPath() 
     let p0 = CGPoint(x: self.bounds.minX, y: self.bounds.midY) 
     path.move(to: p0) 
     let p1 = CGPoint(x: self.bounds.maxX, y: self.bounds.midY) 
     path.addLine(to: p1) 

     let dashes: [ CGFloat ] = [ 0.0, 16.0 ] 
     path.setLineDash(dashes, count: dashes.count, phase: 0.0) 
     path.lineWidth = 8.0 
     path.lineCapStyle = .round 
     UIColor.red.set() 
     path.stroke() 

     let layer = CAShapeLayer() 
     layer.path = path.cgPath 
     layer.strokeColor = UIColor.black.cgColor 
     layer.lineWidth = 3 
     layer.fillColor = UIColor.clear.cgColor 
     layer.lineJoin = kCALineCapButt 
     self.layer.addSublayer(layer) 
     animateStroke(layer: layer) 
    } 

    func animateStroke(layer:CAShapeLayer) 
    { 
     let pathAnimation = CABasicAnimation(keyPath: "strokeEnd") 
     pathAnimation.duration = 10 
     pathAnimation.fromValue = 0 
     pathAnimation.toValue = 1 
     pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) 
     layer.add(pathAnimation, forKey: "strokeEnd") 
    } 

ブラックラインCAShapeLayerがアニメーション化されました。

enter image description here

私は、私はそれをアニメーション化することができるよう、CAShapeLayerに破線UIBezierpathを追加する必要がありますどのような。

注:私は複数のパスを追加しているので、CAShapeLayerのlineDashPatternメソッドを使用したくない場合があります。破損する必要があり、そうでないものがあります。

+0

パス全体で属性を共有したくない場合は、複数のCAshapelayersを使用します。 –

+0

これは私が現在使っていることですが、パスを表す複数のCAShapelayersがあるため、各パスの長さを調整するのが難しく、完全なパスのアニメーションはあまりスムーズではありません@JoshHomann –

答えて

1

アニメーションをdraw(_:)から呼び出さないでください。 draw(_:)は、1つのフレームをレンダリングするためのものです。

あなたはlineDashPatternを使用したくないと言っていますが、個人的には、パターンごとに異なるシェイプレイヤーを使用します。したがって、例えば、ここでダッシュのパターンを有する他のストローク、ノーダッシュパターンと一つのパスをストローク、アニメーションであり、ちょうど第一の完了時に第二のトリガ:

struct Stroke { 
    let start: CGPoint 
    let end: CGPoint 
    let lineDashPattern: [NSNumber]? 

    var length: CGFloat { 
     return hypot(start.x - end.x, start.y - end.y) 
    } 
} 

class CustomView: UIView { 

    private var strokes: [Stroke]? 
    private var strokeIndex = 0 
    private let strokeSpeed = 200.0 

    func startAnimation() { 
     strokes = [ 
      Stroke(start: CGPoint(x: bounds.minX, y: bounds.midY), 
        end: CGPoint(x: bounds.midX, y: bounds.midY), 
        lineDashPattern: nil), 
      Stroke(start: CGPoint(x: bounds.midX, y: bounds.midY), 
        end: CGPoint(x: bounds.maxX, y: bounds.midY), 
        lineDashPattern: [0, 16]) 
     ] 
     strokeIndex = 0 

     animateStroke() 
    } 

    private func animateStroke() { 
     guard let strokes = strokes, strokeIndex < strokes.count else { return } 

     let stroke = strokes[strokeIndex] 

     let shapeLayer = CAShapeLayer() 
     shapeLayer.lineCap = kCALineCapRound 
     shapeLayer.lineDashPattern = strokes[strokeIndex].lineDashPattern 
     shapeLayer.lineWidth = 8 
     shapeLayer.strokeColor = UIColor.red.cgColor 
     layer.addSublayer(shapeLayer) 

     let path = UIBezierPath() 
     path.move(to: stroke.start) 
     path.addLine(to: stroke.end) 

     shapeLayer.path = path.cgPath 

     let animation = CABasicAnimation(keyPath: "strokeEnd") 
     animation.fromValue = 0 
     animation.toValue = 1 
     animation.duration = Double(stroke.length)/strokeSpeed 
     animation.delegate = self 
     shapeLayer.add(animation, forKey: nil) 
    } 

} 

extension CustomView: CAAnimationDelegate { 
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 
     guard flag else { return } 

     strokeIndex += 1 
     animateStroke() 
    } 
} 

enter image description here

場合draw(_:)というアプローチを実際に使用する場合は、CABasicAnimationを使用せず、代わりにCADisplayLinkを使用し、setNeedsDisplay()を繰り返し呼び出して、どれくらい時間が経過したかによって表示をレンダリングするdraw(_:)メソッドを持つメソッドを使用します。しかし、draw(_:)は、アニメーションの1つのフレームをレンダリングし、CoreAnimationの呼び出しを開始すべきではありません。


あなたが本当にシェイプレイヤーを使用したくない場合は、あなたが完全に経過時間に基づいており、所望の期間をパーセントを更新するために、前述のCADisplayLinkを使用することができ、かつdraw(_:)だけ個々のパスなどのように多くのストローク時間の任意の瞬間のための適切な:どこにも近く、効率的なようであることを行っていない可能性が高いものの

struct Stroke { 
    let start: CGPoint 
    let end: CGPoint 
    let length: CGFloat    // in this case, because we're going call this a lot, let's make this stored property 
    let lineDashPattern: [CGFloat]? 

    init(start: CGPoint, end: CGPoint, lineDashPattern: [CGFloat]?) { 
     self.start = start 
     self.end = end 
     self.lineDashPattern = lineDashPattern 
     self.length = hypot(start.x - end.x, start.y - end.y) 
    } 
} 

class CustomView: UIView { 

    private var strokes: [Stroke]? 
    private let duration: CGFloat = 3.0 
    private var start: CFTimeInterval? 
    private var percentComplete: CGFloat? 
    private var totalLength: CGFloat? 

    func startAnimation() { 
     strokes = [ 
      Stroke(start: CGPoint(x: bounds.minX, y: bounds.midY), 
        end: CGPoint(x: bounds.midX, y: bounds.midY), 
        lineDashPattern: nil), 
      Stroke(start: CGPoint(x: bounds.midX, y: bounds.midY), 
        end: CGPoint(x: bounds.maxX, y: bounds.midY), 
        lineDashPattern: [0, 16]) 
     ] 
     totalLength = strokes?.reduce(0.0) { $0 + $1.length } 

     start = CACurrentMediaTime() 
     let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:))) 
     displayLink.add(to: .main, forMode: .commonModes) 
    } 

    @objc func handleDisplayLink(_ displayLink: CADisplayLink) { 
     percentComplete = min(1.0, CGFloat(CACurrentMediaTime() - start!)/duration) 
     if percentComplete! >= 1.0 { 
      displayLink.invalidate() 
      percentComplete = 1 
     } 

     setNeedsDisplay() 
    } 

    // Note, no animation is in the following routine. This just stroke your series of paths 
    // until the total percent of the stroked path equals `percentComplete`. The animation is 
    // achieved above, by updating `percentComplete` and calling `setNeedsDisplay`. This method 
    // only draws a single frame of the animation. 

    override func draw(_ rect: CGRect) { 
     guard let totalLength = totalLength, 
      let strokes = strokes, 
      strokes.count > 0, 
      let percentComplete = percentComplete else { return } 

     UIColor.red.setStroke() 

     // Don't get lost in the weeds here; the idea is to simply stroke my paths until the 
     // percent of the lengths of all of the stroked paths reaches `percentComplete`. Modify 
     // the below code to match whatever model you use for all of your stroked paths. 

     var lengthSoFar: CGFloat = 0 
     var percentSoFar: CGFloat = 0 
     var strokeIndex = 0 
     while lengthSoFar/totalLength < percentComplete && strokeIndex < strokes.count { 
      let stroke = strokes[strokeIndex] 
      let endLength = lengthSoFar + stroke.length 
      let endPercent = endLength/totalLength 
      let percentOfThisStroke = (percentComplete - percentSoFar)/(endPercent - percentSoFar) 
      var end: CGPoint 
      if percentOfThisStroke < 1 { 
       let angle = atan2(stroke.end.y - stroke.start.y, stroke.end.x - stroke.start.x) 
       let distance = stroke.length * percentOfThisStroke 
       end = CGPoint(x: stroke.start.x + distance * cos(angle), 
           y: stroke.start.y + distance * sin(angle)) 
      } else { 
       end = stroke.end 
      } 
      let path = UIBezierPath() 
      if let pattern = stroke.lineDashPattern { 
       path.setLineDash(pattern, count: pattern.count, phase: 0) 
      } 
      path.lineWidth = 8 
      path.lineCapStyle = .round 
      path.move(to: stroke.start) 
      path.addLine(to: end) 
      path.stroke() 

      strokeIndex += 1 
      lengthSoFar = endLength 
      percentSoFar = endPercent 
     } 
    } 
} 

これは、最初のコードスニペットと同じ効果を得ることができます。

関連する問題