2015-07-13 6 views
5

У меня есть приложение, использующее интервалы обновления NSTimer в секундах (0,01 секунды) для отображения рабочего секундомера в формате String как 00: 00.00 (mm: ss.SS). (В основном клонирование встроенного секундомера iOS для интеграции в математические задачи в реальном времени для спортивного времени, возможно, в будущем требуется точность миллисекунд)Формат таймера секундомера реального времени в сотый раз с использованием Swift

Я использую (неправильное использование?) NSTimer для принудительного обновления UILabel. Если пользователь нажимает кнопку Пуск, это код NSTimer используется для запуска повторения функции:

displayOnlyTimer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("display"), userInfo: nil, repeats: true) 

А вот функция, которая выполняется по выше NSTimer:

func display() { 
    let currentTime = CACurrentMediaTime() - timerStarted + elapsedTime 

    if currentTime < 60 { 
     timeDisplay.text = String(format: "%.2f", currentTime) 
    }else if currentTime < 3600 { 
     var minutes = String(format: "%00d", Int(currentTime/60)) 
     var seconds = String(format: "%05.2f", currentTime % 60) 
     timeDisplay.text = minutes + ":" + seconds 
    }else { 
     var hours = String(format: "%00d", Int(currentTime/3600)) 
     var minutes = String(format: "%02d", (Int(currentTime/60)-(Int(currentTime/3600)*60))) 
     var seconds = String(format: "%05.2f", currentTime % 60) 
     timeDisplay.text = hours + ":" + minutes + ":" + seconds 
    } 
} 

Там будет по крайней мере, Одновременно работает 2 канала отображения. Будет ли этот метод слишком неэффективным, когда все остальные элементы будут играть?

Дисплей обновляется без использования NSTimer, когда пользователь нажимает кнопку stop/pause/reset. Я не нашел ничего, что непосредственно переводилось в Свифт. Я довольно уверен, что я использую неэффективный метод для принудительного обновления текста UILabel в UIView.

Подробнее: Я работаю над менее беспорядочным кодом для формата рабочего таймера (мм: ss.SS). Я обновлю это еще раз, когда закончу это.

ОБНОВЛЕНИЕ: Спасибо Rob и jtbandes за ответы на оба моих вопроса (метод форматирования и метод отображения отображения). Это было легко заменить NSTimer (см выше) с CADisplayLink():

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

А затем заменить все экземпляры в коде

displayOnlyTimer.invalidate() 

с

displayLink.paused = true 

(это приостановить ссылку на отображение от обновления)

+0

Как раз для моего собственного спокойствия, вы не создавали новый NSDateFormatter каждый раз, не так ли? Как и в, вы не создавали экземпляр NSDateFormatter внутри метода 'display()', правильно? – justinpawela

+0

jtbandes прав, и вы должны принять этот ответ (используйте ссылку на изображение, а не таймер, определенно не назовите 'CACurrentMediaTime' несколько раз в рамках функции ... он мог бы поставить галочку на следующую минуту между одним вызовом и следующим, в результате чего результат совершенно неправильный: если вы используете форматирование, создайте его один раз и повторно его используете и т. д.). Кстати, если вы собираетесь делать это, как вы делали выше, вместо того, чтобы выяснить, больше ли вы 10 или нет, просто используйте строку формата, например '% 05.2f' (которая будет отформатировать ее как xx.xx, с начальным нулем). – Rob

+0

К счастью, нет. Я сохраняю время начала для переменной, когда пользователь нажимает номер. Когда они приостанавливают его, я просто вычитаю время начала из CACurrentMediaTime() и добавляет его к текущему итогу. – mothy

ответ

6

Для быстрого обновления пользовательского интерфейса вы должны использовать a CADisplayLink. Все быстрее, чем частота обновления дисплея, является пустой тратой вычислительной мощности, поскольку физически ее невозможно отобразить. Он также предоставляет timestamp предыдущего кадра, поэтому вы можете попытаться предсказать, когда будет следующий кадр.

Вы рассчитываете CACurrentMediaTime() - timerStarted + elapsedTime несколько раз. Я бы рекомендовал сделать это только один раз и сохранить его в локальной переменной.

Рассмотрите возможность использования NSDateComponentsFormatter. Попытайтесь повторно использовать один экземпляр форматирования, а не создавать новый каждый раз (что обычно является самой дорогой частью). В целом, чем меньше манипуляции с строкой вы можете сделать, тем лучше.

Вы можете проверить CACurrentMediaTime в начале и конце вашего метода отображения, чтобы узнать, сколько времени потребуется. В идеале это должно быть много менее 16,6 мс. Следите за использованием ЦП (и общим энергопотреблением) в навигаторе отладки Xcode.

+0

спасибо. Исправьте меня, если я ошибаюсь, но я не могу использовать переменную с CACurrentMediaTime(), потому что вызов переменной не будет обновлять переменную до текущего времени, это будет время, когда переменная была сначала сохранена. Но опять же, отвечая на то, что может быть бесполезным, предполагая, что CADisplayLink работает намного по-другому, чем мой текущий код, когда я буду искать его завтра. – mothy

+1

Правильно, но дело в том, что в пределах одной функции лучше сказать 'let currentTime = CACurrentMediaTime()' и повторно использовать это, чем называть ее повторно. – jtbandes

+0

Да, это не выглядит правильным для меня, это имеет смысл сейчас. Он вызывается как минимум 2x, даже если первое утверждение верно. Последний вопрос: если я назвал его константой let переменной, это все еще безопасно использовать, поскольку значение CACurrentMediaTime() является различным значением при каждом вызове функции? AKA ли переменная перестает существовать после завершения функции? – mothy

1

Я решал ту же проблему сегодня и нашел этот ответ. Советы Rob и jtbandes очень помогли, и я смог собрать чистые и рабочие решения со всего Интернета. Спасибо вам, ребята. И спасибо mothy за вопрос.

Я решил использовать CADisplayLink, потому что нет никакого смысла в стрельбе обратного вызова таймера чаще, чем обновления экрана:

class Stopwatch: NSObject { 
    private var displayLink: CADisplayLink! 
    //... 

    override init() { 
     super.init() 

     self.displayLink = CADisplayLink(target: self, selector: "tick:") 
     displayLink.paused = true 
     displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) 
     //... 
    } 
    //... 
} 

Я отслеживание времени приращения переменной ElapsedTime по displayLink.duration каждого тика :

var elapsedTime: CFTimeInterval! 

override init() { 
    //... 
    self.elapsedTime = 0.0 
    //... 
} 

func tick(sender: CADisplayLink) { 
    elapsedTime = elapsedTime + displayLink.duration 
    //... 
} 

Time-форматирование осуществляется с помощью NSDateFormatter:

private let formatter = NSDateFormatter() 

override init() { 
//... 
     formatter.dateFormat = "mm:ss,SS" 
} 

func elapsedTimeAsString() -> String { 
     return formatter.stringFromDate(NSDate(timeIntervalSinceReferenceDate: elapsedTime)) 
} 

Пользовательский интерфейс может быть обновлен в замыкании обратного вызова, который Секундомер вызовов на каждый тике:

var callback: (() -> Void)? 

func tick(sender: CADisplayLink) { 
    elapsedTime = elapsedTime + displayLink.duration 

    // Calling the callback function if available 
    callback?() 
} 

И это все, что вам нужно сделать в ViewController использовать секундомер:

let stopwatch = Stopwatch() 
stopwatch.callback = self.tick 

func tick() { 
    elapsedTimeLabel.text = stopwatch.elapsedTimeAsString() 
} 

Вот является gist с полным кодом Секундомера и руководство по использованию: https://gist.github.com/Flar49/06b8c9894458a3ff1b14

Я надеюсь, что это объяснение и суть помогут другим, которые наткнутся на эту тему в будущем с той же проблемой :)

+0

У вас есть эквивалентная версия для OS X? – Matt