使用 NSTimer 的秒表错误地在显示中包含暂停时间

Posted

技术标签:

【中文标题】使用 NSTimer 的秒表错误地在显示中包含暂停时间【英文标题】:Stopwatch using NSTimer incorrectly includes paused time in display 【发布时间】:2011-12-27 10:11:26 【问题描述】:

这是我的 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";

【问题讨论】:

你的暂停按钮调用哪个动作? 对不起,我的意思是 onStopPressed 动作,暂停和停止是一回事 【参考方案1】:

我在 Swift 中为计时器程序创建的计时器类,其中计数器从设定的时间开始每秒更新一次。回答以说明 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 && 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() 
    
  


【讨论】:

【参考方案2】:

您可以使用 NSTimeInterval 代替计时器。我有一个暂停和停止计时器的功能代码。

@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:@"%02u:%02u:%02u", hours, mins, secs];

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

希望这会有所帮助。谢谢

【讨论】:

【参考方案3】:

你对当前显示的计算总是使用定时器原来的开始时间,所以暂停后的显示包括定时器暂停的时间间隔。

最简单的做法是在计时器暂停时存储另一个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 或将其放入 showActivity:static 存储中,如下所示:

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

【讨论】:

非常感谢,这是一种更有效的方法。我有一个快速错误,希望你能帮助我,我似乎无法让secondsAlreadyRun += [NSDate timeIntervalSinceDate:startDate]; 工作。我需要什么变量或还有什么可做的?谢谢! secondsAlreadyRun 应该是 NSTimeInterval ivar。 “无法正常工作”到底是什么意思? 好吧,我有secondsAlreadyRun 作为NSTimeInterval ivar,但它返回错误消息“二进制表达式的无效操作数('NSTimeInterval'(又名'double')和'id')和给出警告“找不到类方法'+timeIntervalSinceDate'”。我一定做错了,谢谢你的帮助 哎呀!我用大脑编译了这个并搞砸了那条线。没有名为timeIntervalSinceDate: 的类方法。编译器假定方法在找不到声明时返回id。这就是你收到警告的原因。我已经修好了那条线。 abs 返回一个整数,截断NSTimeInterval。使用fabs 或将右侧更改为[[NSDate date] timeIntervalSinceDate:startDate];【参考方案4】:

因此,当用户按下停止,然后重新开始时,您不会重置开始时间。但是,当您更新标签时,您将基于从原始开始时间到当前时间的总经过时间。

因此,如果您运行计时器 10 秒,停止,等待 10 秒,然后重新启动,计时器将显示 00:20.00 并从那里重新开始计数。

您想要做的是在每次用户启动时钟时重置开始时间,然后同时添加所有先前运行的经过时间。或类似的东西。

顺便说一句,您现在每次重置时都会泄露开始时间。小错误。

编辑:看起来@Josh Caswell 也在想同样的事情,但他打字的速度快了很多。 :)

【讨论】:

我想我是在你之前开始的...... :)【参考方案5】:

您是否使用 ARC?

如果您使用的是 ARC,那么您似乎没有使用 _strong 引用。如果您没有使用 ARC,它看起来并没有保留对计时器的引用。

我是从手机上发布的,所以可能会遗漏一些东西。

编辑:刚刚注意到您在其他地方使用发布,所以我假设没有 ARC。设置好后需要保留定时器,以便以后可以访问并失效。

【讨论】:

以上是关于使用 NSTimer 的秒表错误地在显示中包含暂停时间的主要内容,如果未能解决你的问题,请参考以下文章

可移植地在 autoconf/automake 中包含 GLib 标头

Apple Watch 显示器睡眠暂停 NSTimer,但 WKInterfaceTimer 一直倒计时

如何有条件地在 Mongoose 的查询中包含/排除字段?

中继:有条件地在突变的胖查询中包含字段

用定时器实现10s秒表的计时功能,精确到小数点后一位,即能够有0.1s显示,有开始,暂停,清零的功能(C语

华为手机秒表怎么看