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()
}
}
경우 : 따라서, 예를 들어, 여기에서 애니메이션이없는 대시 패턴을 하나 개의 경로를 쓰다듬어 대시 패턴과 다른 쓰다듬어 단지 제의 완료시 상기 제 트리거링된다 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
}
}
}
이 첫 번째 코드와 동일한 효과를 얻을 : 시간에 어떤 주어진 순간에 적합합니다.
여러 경로에서 속성을 공유하지 않으려면 여러 CAshapelayers를 사용하십시오. –
그게 내가 현재 사용하고있는 것이지만, 내 경로를 나타내는 여러 CAShapelayers가 있기 때문에 각 경로의 길이를 조정하는 데 어려움이 있습니다. 전체 경로의 애니메이션은 매우 매끄럽지 않습니다. @JoshHomann –