Секундомер, использующий NSTimer, неправильно отображает время паузы на дисплее

#objective-c #ios #cocoa-touch #time #nstimer

#objective-c #iOS #cocoa-touch #время #nstimer

Вопрос:

Это мой код для секундомера iPhone. Он работает как ожидалось, останавливается и возобновляется при нажатии кнопок.

Однако, когда я нажимаю «Стоп», таймер не прекращает работать в фоновом режиме, а когда я нажимаю «Пуск», чтобы возобновить его, он обновит время и перейдет к текущему состоянию вместо возобновления с остановленного времени.

Как я могу остановить NSTimer ? Что является причиной этого?

 @implementation FirstViewController;
@synthesize stopWatchLabel;

NSDate *startDate;
NSTimer *stopWatchTimer;
int touchCount;


-(void)showActivity {

    NSDate *currentDate = [NSDate date];
    NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
    NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"mm:ss.SS"];
    [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
    NSString *timeString=[dateFormatter stringFromDate:timerDate];
    stopWatchLabel.text = timeString;
    [dateFormatter release];
}

- (IBAction)onStartPressed:(id)sender {

    stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1/10 target:self selector:@selector(showActivity) userInfo:nil repeats:YES];

    touchCount  = 1;
    if (touchCount > 1)
    {
        [stopWatchTimer fire];
    }
    else 
    {
        startDate = [[NSDate date]retain];
        [stopWatchTimer fire];

    }
}

- (IBAction)onStopPressed:(id)sender {
    [stopWatchTimer invalidate];
    stopWatchTimer = nil;
    [self showActivity];
}

- (IBAction)reset:(id)sender; {
    touchCount = 0;
    stopWatchLabel.text = @"00:00.00";
}
  

Комментарии:

1. Какое действие вызывает ваша кнопка паузы?

2. извините, я имел в виду действие onStopPressed, pause и stop — это одно и то же

Ответ №1:

При расчете текущего отображения всегда используется исходное время запуска таймера, поэтому отображение после приостановки включает интервал, в течение которого таймер был приостановлен.

Проще всего было бы сохранить другое время, NSTimeInterval скажем secondsAlreadyRun , когда таймер приостановлен, и добавить это к интервалу времени, который вы вычисляете при возобновлении. Вы захотите обновлять значение таймера startDate каждый раз, когда таймер начинает отсчет. В reset: случае вы также очистили бы этот secondsAlreadyRun интервал.

 -(void)showActivity:(NSTimer *)tim {

    NSDate *currentDate = [NSDate date];
    NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
    // Add the saved interval
    timeInterval  = secondsAlreadyRun;
    NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"mm:ss.SS"];
    [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
    NSString *timeString=[dateFormatter stringFromDate:timerDate];
    stopWatchLabel.text = timeString;
    [dateFormatter release];
}

- (IBAction)onStartPressed:(id)sender {

    stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1/10 
                                                      target:self 
                                                    selector:@selector(showActivity:) 
                                                    userInfo:nil 
                                                     repeats:YES];
    // Save the new start date every time
    startDate = [[NSDate alloc] init]; // equivalent to [[NSDate date] retain];
    [stopWatchTimer fire];
}

- (IBAction)onStopPressed:(id)sender {
    // _Increment_ secondsAlreadyRun to allow for multiple pauses and restarts
    secondsAlreadyRun  = [[NSDate date] timeIntervalSinceDate:startDate];
    [stopWatchTimer invalidate];
    stopWatchTimer = nil;
    [startDate release];
    [self showActivity];
}

- (IBAction)reset:(id)sender; {
    secondsAlreadyRun = 0;
    stopWatchLabel.text = @"00:00.00";
}
  

Не забудьте указать это startDate где-нибудь в подходящем месте! Также имейте в виду, что задокументированный NSTimer интерфейс предназначен для того, чтобы метод, который вы ему предоставляете, принимал один аргумент, которым будет сам таймер. Кажется, что это работает без этого, но зачем искушать судьбу?

Наконец, поскольку вы так часто используете это NSDateFormatter , вы можете подумать о том, чтобы сделать это ivar или поместить его в static хранилище в showActivity: , вот так:

 static NSDateFormatter * dateFormatter = nil;
if( !dateFormatter ){
    dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"mm:ss.SS"];
    [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
}
  

Комментарии:

1. Большое вам спасибо, это гораздо более эффективный способ сделать это. у меня есть одна быстрая ошибка, с которой, надеюсь, вы сможете мне помочь, я, кажется, не могу secondsAlreadyRun = [NSDate timeIntervalSinceDate:startDate]; приступить к работе. какие переменные мне нужны или что еще нужно сделать? Спасибо!

2. secondsAlreadyRun должен быть NSTimeInterval ivar. Что именно означает «не удается заставить его работать»?

3. ну, да, у меня есть secondsAlreadyRun как NSTimeInterval ivar, но он возвращает сообщение об ошибке «недопустимые операнды для двоичного выражения (‘NSTimeInterval’ (он же ‘double’) и ‘id’) и выдает предупреждение «метод класса ‘ timeIntervalSinceDate’ не найден». Должно быть, я делаю что-то не так, спасибо за вашу помощь

4. Упс! Я скомпилировал это с помощью brain и перепутал эту строку. Нет метода класса с именем timeIntervalSinceDate: . Компилятор предполагает, что метод возвращает id , когда он не может найти объявление. Вот почему вы получили это предупреждение. Я исправил эту строку.

5. abs возвращает целое число, усекая NSTimeInterval . Используйте fabs или измените правую часть на [[NSDate date] timeIntervalSinceDate:startDate];

Ответ №2:

Таким образом, когда пользователь нажимает «Стоп», а затем «Начать снова», вы не сбрасываете время начала. Но когда вы обновляете метку, вы основываете ее на общем времени, прошедшем с исходного времени запуска до текущего времени.

Итак, если вы запустите таймер на 10 секунд, остановитесь, подождите 10 секунд, а затем запустите снова, таймер покажет 00: 20.00 и с этого момента начнется отсчет снова.

Что вы хотите сделать, это сбрасывать время начала каждый раз, когда пользователь запускает часы, но затем также добавлять истекшее время всех предыдущих запусков. Или что-то подобное.

Кстати, вы пропускаете время запуска каждый раз, когда сбрасываете его сейчас. Незначительная ошибка.

РЕДАКТИРОВАТЬ: похоже, @Josh Caswell думал о том же, но он печатает НАМНОГО быстрее. 🙂

Комментарии:

1. Я думаю, что я только начал раньше вас … 🙂

Ответ №3:

Вы используете ARC или нет?

Если вы используете ARC, похоже, что вы не используете ссылку _strong. Если вы не используете ARC, не похоже, что вы сохраняете ссылку на таймер.

Я публикую это с мобильного устройства, поэтому, возможно, чего-то не хватает.

РЕДАКТИРОВАТЬ: только что заметил, что вы использовали release в другом месте, поэтому я предполагаю, что ARC отсутствует. Вам необходимо сохранить таймер после его настройки, чтобы иметь возможность получить к нему доступ позже и аннулировать.

Ответ №4:

Вы можете использовать NSTimeInterval вместо timer. У меня есть функциональный код для приостановки и остановки таймера.

 @interface PerformBenchmarksViewController () {

    int currMinute;
    int currSecond;
    int currHour;
    int mins;
    NSDate *startDate;
    NSTimeInterval secondsAlreadyRun;
}

@end

- (void)viewDidLoad
{
    [super viewDidLoad];

    running = false;
}

- (IBAction)StartTimer:(id)sender {

    if(running == false) {

        //start timer
        running = true;
        startDate = [[NSDate alloc] init];
        startTime = [NSDate timeIntervalSinceReferenceDate];
        [sender setTitle:@"Pause" forState:UIControlStateNormal];
        [self updateTime];
    }
    else {
        //pause timer
        secondsAlreadyRun  = [[NSDate date] timeIntervalSinceDate:startDate];
        startDate = [[NSDate alloc] init];
        [sender setTitle:@"Start" forState:UIControlStateNormal];
        running = false;
    }
}

- (void)updateTime {

    if(running == false) return;

    //calculate elapsed time
    NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
    NSTimeInterval elapsed = secondsAlreadyRun   currentTime - startTime;

    // extract out the minutes, seconds, and hours of seconds from elapsed time:
    int hours = (int)(mins / 60.0);
    elapsed -= hours * 60;
    mins = (int)(elapsed / 60.0);
    elapsed -= mins * 60;
    int secs = (int) (elapsed);

    //update our lable using the format of 00:00:00
    timerLabel.text = [NSString stringWithFormat:@"u:u:u", hours, mins, secs];

    //call uptadeTime again after 1 second
    [self performSelector:@selector(updateTime) withObject:self afterDelay:1];
}
  

Надеюсь, это поможет. Спасибо

Ответ №5:

Класс таймера, который я создал в Swift для программы таймера, в которой счетчик обновляется каждую секунду с установленного времени. Ответил, чтобы проиллюстрировать быстрое решение и функцию NSTimer.

Таймер можно остановить и перезапустить; он возобновится с того места, где он остановился. Делегат может перехватывать события для запуска, остановки, сброса, завершения и второго событий. Просто проверьте код.

 import Foundation

protocol TimerDelegate {
  func didStart()
  func didStop()
  func didReset()
  func didEnd()
  func updateSecond(timeToGo: NSTimeInterval)
}

// Inherit from NSObject to workaround Selector bug
class Timer : NSObject {

  var delegate: TimerDelegate?
  var defaultDuration: NSTimeInterval?

  var isRunning: Bool {
    get {
      return self.timer != nil amp;amp; timer!.valid
    }
  }

  private var secondsToGo: NSTimeInterval = 0

  private var timer: NSTimer?

  init(defaultDuration: NSTimeInterval, delegate: TimerDelegate? = nil) {
    self.defaultDuration = defaultDuration
    self.delegate = delegate
    super.init()
  }

  func start() {
    self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateTimer", userInfo: nil, repeats: true)
    self.timer!.tolerance = 0.05

    if delegate != nil { delegate!.didStart() }
  }

  func stop () {
    self.timer?.invalidate()
    self.timer = nil

    if delegate != nil { delegate!.didStop() }
  }

  func reset() {
    self.secondsToGo = self.defaultDuration!

    if delegate != nil { delegate!.didReset() }
  }


  func updateTimer() {
    --self.secondsToGo
    if delegate != nil { delegate!.updateSecond(self.secondsToGo) }

    if self.secondsToGo == 0 {
      self.stop()
      if delegate != nil { delegate!.didEnd() }
    }
  }

}