1

I'm creating a subclass of AVPlayerController, that observes the player and handles player's states. If AVPlayerItemStatus is .failed, I add a custom UIView over the player's frame. At present, the important parts of my custom view's code looks like this:

class NoiseView: UIView { ... var timer: Timer? func animate() { timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true) } func stopAnimating() { timer?.invalidate() } @objc func timerAction() { self.setNeedsDisplay() } override func draw(_ rect: CGRect) { super.draw(rect) let context = UIGraphicsGetCurrentContext()! let h = rect.height let w = rect.width var val: CGFloat! var color: UIColor! for i in 0...Int(w-1) { for j in 0...Int(h-1) { val = .random color = UIColor(red: val, green: val, blue: val, alpha: 1.0) context.setFillColor(color.cgColor) context.fill(CGRect(x: i, y: j, width: 1, height: 1)) } } context.flush() } } 

I'm calling the method animate() from other ViewController that keeps the NoiseView object.

The thing is, it's working as it's supposed to work (view is animating and creating a white noise) but the app starts to work slowly. What should be a proper approach to redraw the view without causing such a performance drop?

By the way, the drop happened with 0.1s interval (10 FPS). What I want to accomplish is having a constant white noise with at least 30 FPS to look like a legit tv noise.

2
  • What is the animation you are trying to achieve by filling entire screen with random stripes of height and width of 1px? BTW redwing the burden on CPU and utilizing core graphics APIs which uses GPU would solve reducing frame drops. But I cant post the code without actually knowing whats the animation u are trying to achieve Commented Jul 3, 2018 at 17:27
  • I'm trying to achieve a white noise. It should re-render every frame with a new set of grayish points. Commented Jul 3, 2018 at 17:33

3 Answers 3

1

There are a number of strategies you can try to optimize the drawing code, but to me the most obvious one is that you should pre-calculate the CGRects you need outside the drawing code.

The loops you're running require w^h iterations for each frame of animation to calculate all the CGRects you need. That's a lot, and totally unnecessary, because the CGRects will always be the same so long as the width and height are unchanged. Instead, you should do something like this:

var cachedRects = [CGRect]() override var frame: CGRect { didSet { calculateRects() } } func calculateRects() { cachedRects = [] let w = frame.width let h = frame.height for i in 0...Int(w-1) { for j in 0...Int(h-1) { let rect = CGRect(x: i, y: j, width: 1, height: 1) cachedRects += [rect] } } } override func draw(_ rect: CGRect) { super.draw(rect) let context = UIGraphicsGetCurrentContext()! var val: CGFloat! var color: UIColor! for rect in cachedRects { val = .random color = UIColor(red: val, green: val, blue: val, alpha: 1.0) context.setFillColor(color.cgColor) context.fill(rect) } context.flush() } 

By pre-caching the rects, you only have to do (w * h) number of iterations, which is a vast improvement.

If that isn't enough to improve performance, you can further optimize using similar pre-caching strategies, like instead of randomizing the each pixel, pre-calculate tiles of random colors outside the draw code, and then randomly assemble them in drawRect(). If the randomizer is what's the performance problem, this would cut down on the amount of work it would have to.

The key strategy is to try and minimize the amount of work your drawRect() method has to do, because it run on each frame of animation.

Good luck!

Sign up to request clarification or add additional context in comments.

2 Comments

This will still max out the CPU, need to work in CALayer with animation to work off the GPU.
Can you point to where in Apple's documentation (or related) they say this, because UIViews are backed by CALayers and whether or not the CPU or GPU is used to draw is not dependent on whether you're directly manipulating the CALayer. But I might be wrong.
1

Instead of using the timer and calling setNeedsDisplay which in result will keep calling draw method, hence slowing the app.

Use CAAnimation and create CALayer or CAreplicator layer and animate that.

If you need code you can check this link Color Picker or I can post some demo code after sometime, Its fifa time :-p.

CAReplicatorlayer CALayer

Comments

0

Custom draw(rect:) methods can cause performance hits.

I suggest looking into adding a CAAnimation or CAAnimationGroup to your white noise view.

CABasicAnimation Documentation

CAAnimationGroup StackOverflow Post

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.