Alternate approach or optimizing for frequent drawing/animating GraphicsContext

32 views Asked by At

I am making a timeline as you can see below. This is just my idea in its infancy. More stuff should be on the timeline like: Time spans (events starting and stopping), Comments (text, usernames maybe images and icons), Arches from one event to another and so on and so forth.

enter image description here

I want to be able to zoom in and out but obviously not like when you zoom in and out of a picture. Arches would change shape depending on zoom level and icons would fade in and out depending on zoom level as well as the vertical lines indicating date and time.

But now with this pretty simple setup it is already taking 10% to 15% cpu time. I fear that adding a whole lot more might really upset the cpu.

Is there a optimization that I should do or another approach altogether that I might use. Or is 10-15% cpu time fine? Maybe it is fine... nope! I changed it to show a wider time span and the cpu shot up to 35%!

So here is the timeline view code:

//  Agust Rafnsson 


import UIKit

class RulerView: UIView {
    // 1minute 12seconds
    let LENGTH_IN_SECONDS:TimeInterval = 100000
    var position: TimeInterval? {
        didSet {
            self.setNeedsDisplay()
        }
    }
    let span:TimeInterval = 60
    let dateFormatter = DateComponentsFormatter()
    
    var tinyLineHeight = CGFloat(1)
    var smallLineHeight = CGFloat(4)
    var bigLineHeight = CGFloat(10)
    
    public var contentOffset = CGFloat(0) {
        didSet {
            self.setNeedsDisplay()
        }
    }
    
    override open func layoutSubviews() {
        super.layoutSubviews()
        self.backgroundColor = UIColor.clear
    }
    
    override func draw(_ rect: CGRect) {
        guard let position = position else {
            return
        }

        let width = self.bounds.size.width
        let height = self.bounds.size.height
        
        tinyLineHeight = height/4
        smallLineHeight = height/3
        bigLineHeight = height/1.5
        
        let dotsPerSecond:CGFloat = width/span
        
        let firstSecond = position - span/2
        let lastSecond = position + span/2
        
        if let context = UIGraphicsGetCurrentContext() {
            let centerXPath = UIBezierPath()
            UIColor.black.set()
            centerXPath.move(to: CGPoint(x: width/2 - height/5, y: 0))
            centerXPath.addLine(to: CGPoint(x: width/2 + height/5, y: height))
            
            centerXPath.move(to: CGPoint(x: width/2 + height/5, y: 0))
            centerXPath.addLine(to: CGPoint(x: width/2 - height/5, y: height))
            
            centerXPath.lineWidth = 0.5
            centerXPath.stroke()
            
            
            for i in Int(firstSecond)...Int(lastSecond) {
                let xPosition = (Float64(i)-firstSecond)*dotsPerSecond
                
                let path = UIBezierPath()
                path.move(to: CGPoint(x: xPosition , y:0))
                if i.isMultiple(of: 15) {
                    UIColor.red.set()
                    path.addLine(to: CGPoint(x: xPosition, y: bigLineHeight))
                } else if i.isMultiple(of: 5){
                    UIColor.black.set()
                    path.addLine(to: CGPoint(x: xPosition, y: smallLineHeight))
                } else if dotsPerSecond > 5 {
                    UIColor.gray.set()
                    path.addLine(to: CGPoint(x: xPosition, y: tinyLineHeight))
                }
                path.lineWidth = 0.5
                path.stroke()
                
                if i.isMultiple(of: 15){
                    let attrs = [NSAttributedString.Key.font: UIFont(name: "HelveticaNeue", size: smallLineHeight)!,
                                 NSAttributedString.Key.foregroundColor: UIColor.black,
                                 NSAttributedString.Key.backgroundColor: UIColor.white.withAlphaComponent(0.75)]
                    if let string = dateFormatter.string(from: TimeInterval(i)) {
                        let stringSize = string.size(withAttributes: attrs)
                        string.draw(at: CGPoint(x: xPosition - stringSize.width/2, y: height/2), withAttributes: attrs)
                    }
                }
            }
        }
    }
}

And here is the viewcontroller for demonstration purposes:

import UIKit

class ViewController: UIViewController {
    let ruler: RulerView = RulerView()
    lazy var timer = {Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { [weak self] timer in
        guard let self = self else { return }
        self.ruler.position! = self.ruler.position! + 0.05
        print(self.ruler.position!)
    }}()

    override func viewDidLoad() {
        print(timer.self)
        ruler.position = TimeInterval.random(in: 0...ruler.LENGTH_IN_SECONDS)

        super.viewDidLoad()
        ruler.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(ruler)
        ruler.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
        ruler.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        ruler.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
        ruler.heightAnchor.constraint(equalToConstant: 40).isActive = true
        // Do any additional setup after loading the view.
    }


}

0

There are 0 answers