Animate view by CADisplayLink

Khi muốn animate views trên ứng dụng của bạn, thường thường chúng ta thường dùng hàm animateWithDuration:animations của UIView.Phương pháp này thực sự tốt khi chúng ta muốn hide, show hay di chuyển xung quanh UI element. Vấn đề nếu chúng ta muốn animate update nội dung của screen hay 1 lable theo thời gian thì việc sử dụng hàm animateWithDuration:animations: của UIView trở nên khó khăn hay có thể không làm được. Vi dụ, chúng ta muốn nội dung của 1 label thay đổi từ 0 - 1000 trong 1 khoảng thời gian là 5 phút thì hàm animate của UIView trên thực sự ko làm được. Để làm đc việc này, chúng ta cần 1 object chay theo timer, và update nội dung của theo thời gian tương ứng. Và chắc khi nói đến timer chúng ta sẽ nghĩ đến NSTimer.Tuy nhiên, NSTimer không thực sự giải quyết bài toán này 1 cách tốt nhất.

NSTimer

  var timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.updateLabel), userInfo: nil, repeats: true)

NSTimer gọi 1 hàm theo 1 khoảng thời gian xác định trước, và chúng ta có thể update nội dung của lable trong hàm được gọi đó. Vấn đề là khoảng thời gian để call hàm #selector(self.updateLabel) không thật sự luôn luôn giống nhau (0.4).Khoảng thời gian đó có thể bị delay, tuy thuộc vào NSRunLoop và vấn đề nữa đó là có thể frame để update nội dung label (Vẽ lại màn hình) ko trùng với thời gian call hàm self.updateLabel.Do đó , dùng NSTimer có thể giải quyết bài toán này, nhưng việc update sẽ ko được mượt mà.Và thực sự nếu chúng ta dùng NSTimer để vẽ biểu đồ thì sẽ dễ dàng nhận thấy, NSTimer ko phải là 1 giải pháp tối ưu. Cái chúng ta cần là update nội dung label theo frame update màn hình.CADisplayLink có thể giúp chúng ta giải quyết vấn đề này.

CADisplayLink

CADisplayLink cũng là 1 timer object, nhưng nó cho mình update hay call hàm theo refresh rate của màn hình (thường thường là 60 frame/1s) Việc sử dụng CADisplayLink cũng tương tự như NSTimer.

let displayLink = CADisplayLink(target: self, selector: Selector("updateLoop:"))
displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)

Hàm Selector("updateLoop") sẽ được gọi theo refresh rate của screen(default), nên việc update nội dung của label cũng sẽ là thời điểm screen vẽ lại.Do đó, update label sẽ râte là mượt.Thậm chí dùng để vẽ nhiều view mà ko có hiện tượng bị giật. Tuy nhiên, khi sử dụng CADisplayLink có vấn đề là time interval giữa 2 lần refresh frame cũng không giống nhau.Nên để tính toán giá trị update chính xác nhất, chúng ta phải lưu lại thời gian update frame lần trước để biết đc khoảng thời gian 2 lần update frame.

    var previousTimestamp:CFTimeInterval?
    func updateLoop(displayLink:CADisplayLink) {
        let currentTimestamp = displayLink.timestamp
        let timestamp = currentTimestamp - (self.previousTimestamp ?? 0.0)
        self.updateLabel(timestamp)
        self.previousTimestamp = currentTimestamp
    }