2017-12-09 4 views
2

UIBezierPath과 같이 UIViewdrawRect() 방법 내에서 사용하는 경우에만 점선 얻을 :대시이 뇌졸중을 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

내가이 라인 스트로크를 애니메이션을 적용 할 경우 , 정말

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를 사용하는 것이 필요하게 될 것이다 CAShapeLayer에 애니메이션이 추가되었습니다.

enter image description here

내가이며, 내가 애니메이션을 적용 할 수 있도록 CAShapeLayer에 점선 UIBezierpath를 추가 할 필요가있는 무엇.

참고 : 여러 경로를 추가 할 때 CAShapeLayer의 lineDashPattern 메서드를 사용하고 싶지 않습니다. 일부는 대시를 사용해야하고 일부는 필요하지 않습니다.

+0

여러 경로에서 속성을 공유하지 않으려면 여러 CAshapelayers를 사용하십시오. –

+0

그게 내가 현재 사용하고있는 것이지만, 내 경로를 나타내는 여러 CAShapelayers가 있기 때문에 각 경로의 길이를 조정하는 데 어려움이 있습니다. 전체 경로의 애니메이션은 매우 매끄럽지 않습니다. @JoshHomann –

답변

1

draw(_:)에서 애니메이션을 호출하면 안됩니다. draw(_:)은 단일 프레임을 렌더링하기위한 것입니다.

당신은 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(_:)은 애니메이션의 단일 프레임을 렌더링하므로 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 
     } 
    } 
} 

이 첫 번째 코드와 동일한 효과를 얻을 : 시간에 어떤 주어진 순간에 적합합니다.

관련 문제