Reputation: 254
I have a buffer that gets filled with float data points every 1/10 of a second. When the data is ready/comes (around 4000 each time), I translate that data into lines.
My problem here, as you may already know, is that the drawing becomes really slow. I'm posting some of my code below.
Here's my custom UIView. This method gets called once.
override func layoutSubviews() {
super.layoutSubviews()
// init stuff for drawing animation
path = UIBezierPath()
yOff = Float(self.bounds.height / 2)
lastPoint = CGPoint(x: 1.0, y: Double(yOff))
path.move(to: lastPoint)
path.addLine(to: lastPoint)
pathLayer = CAShapeLayer()
pathLayer.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)
pathLayer.path = path.cgPath
pathLayer.strokeColor = UIColor.red.cgColor
pathLayer.fillColor = nil
pathLayer.lineWidth = 0.2
pathLayer.lineJoin = kCALineJoinBevel
let pathAnimation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
pathAnimation.duration = 0.1
pathAnimation.fromValue = NSNumber(value: 0.0)
pathAnimation.toValue = NSNumber(value:1.0)
self.layer.addSublayer(pathLayer)
pathLayer.add(pathAnimation, forKey: "strokeEnd")
}
The following method does the actual drawing, which gets called by "setNeedsDisplay" every 1/10 of a second.
override func draw(_ rect: CGRect) {
// this is where the magic (animation) happens
CATransaction.begin()
pathLayer.path = path.cgPath
CATransaction.commit()
}
This is in the viewcontroller. I'm looping through the data points to create a CGPoint for each data. Probably inefficient; any help/idea is welcome:
for _s in samples
{
let currP = CGPoint(x: cX * xModifier, y: yOffSet + (CGFloat(_s)*yModifier))
cX += xLen
self.waveView.path.addLine(to: currP)
ctr = ctr + 1
}
DispatchQueue.main.async {
self.waveView.setNeedsDisplay()
}
Thanks in advance.
Upvotes: 1
Views: 92
Reputation: 437632
A couple of thoughts:
Don't update layer in draw
. You should eliminate that method entirely, as it's only used when you're stroking your own path. But you're using CAShapeLayer
, which eliminates the need to do any of that. When you update your path, replace the call to setNeedsDisplay
with code that just updates the pathLayer.path
directly. If you want to animate, too, then perform you CABasicAnimation
here, too.
Unrelated to your current issue, you should be careful about adding layers in layoutSubviews
. That method can get called multiple times. Maybe you’re only seeing it called once right now, but it can be called repeatedly. E.g., if you rotate device, it can get called. If you use autolayout, and do something that triggers autolayout engine, it can get called. As a general rule, it can be called multiple times (even if you're only seeing it called only once right now).
Adding 4000 line segments to a path is quite a bit and it can be slow to render. It's not entirely clear from your question with what frequency you're adding another 4000 line segments.
Often when we want to add to an existing (potentially long) bezier path, we instead take a snapshot of the existing view, render that in a UIImageView
, and then just render the incremental portion of the path in the CAShapeLayer
. This ensures a consistent performance pattern, just the time to render image and the incremental portion, rather than trying to re-render an increasingly long UIBezierPath
.
Upvotes: 1