0

所以我有一个带有计时器的视图控制器。它有一个按钮。当视图第一次加载时,按钮应该说“开始”。

点击它时,“开始”->“暂停”。

再次点击,“暂停”->“恢复”。

再次点击,“恢复”->“暂停”。

由于我希望计时器准确,因此我将其从主线程中分离出来(我认为我选择了正确的方法,我希望得到一些澄清)。但似乎将它与线程分离实际上调用了该方法......这使得按钮以“暂停”而不是开始。我该如何解决?

顺便说一下,testTask.showButtonValue 的默认值(加载)是 1。

- (void)viewDidLoad {
    [super viewDidLoad];

    [NSThread detachNewThreadSelector:@selector(startTimer:) toTarget:self withObject:nil];

    if (testTask.showButtonValue == 1) {
        [startButton setTitle:@"Start" forState:UIControlStateNormal];
    } else if (testTask.showButtonValue == 2) {
        [startButton setTitle:@"Pause" forState:UIControlStateNormal];
    } else if (testTask.showButtonValue == 3){
        [startButton setTitle:@"Resume" forState:UIControlStateNormal];
    }
}
-(IBAction)startTimer:(id)sender{
    if (testTask.showButtonValue == 1) {
        [startButton setTitle:@"Pause" forState:UIControlStateNormal];
        timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
        testTask.showButtonValue = 2;
    } else if (testTask.showButtonValue == 2) {
        [startButton setTitle:@"Resume" forState:UIControlStateNormal];
        [timer invalidate];
        timer = nil;
        testTask.showButtonValue = 3;
    } else if (testTask.showButtonValue == 3){
        [startButton setTitle:@"Pause" forState:UIControlStateNormal];
        timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
        testTask.showButtonValue = 2;
    }
}
-(void)timerAction:(NSTimer *)t
{
    if(testTask.timeInterval == 0)
    {
        if (self.timer)
        {
            [self timerExpired];
            [self.timer invalidate];
            self.timer = nil;
        }
    }
    else
    {
        testTask.timeInterval--;
    }
    NSUInteger seconds = (NSUInteger)round(testTask.timeInterval);
    NSString *string = [NSString stringWithFormat:@"%02u:%02u:%02u",
                        seconds / 3600, (seconds / 60) % 60, seconds % 60];
    timerLabel.text = string;
    NSLog(@"%f", testTask.timeInterval);
}
4

2 回答 2

1

我建议以不同的方式解决这个问题:

@interface SNDViewController ()
@property (weak, nonatomic) IBOutlet UIButton *startButton;
@property (weak, nonatomic) IBOutlet UILabel *timerLabel;
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, assign) NSTimeInterval accumulativeTime;
@property (nonatomic, assign) NSTimeInterval currentReferenceTime;
@end

@implementation SNDViewController

编辑:当视图加载时,初始化self.accumulativeTime和更新计时器标签。accumulativeTime 变量的初始化实际上应该在适当的 init* 方法中完成,例如 initWithNibName:bundle:。正是在这一点上,您将从核心数据中读取计时器值。

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.accumulativeTime = 300;
    [self updateTimerLabelWithTotalTime:self.accumulativeTime];
}

- (IBAction)changeTimerState:(UIButton *)sender
{
    if (self.timer == nil) {
        self.currentReferenceTime = [NSDate timeIntervalSinceReferenceDate];
        [self.timer invalidate];
        self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(updateTimer:) userInfo:nil repeats:YES];
    } else {
        //Pause the timer and track accumulative time.
        NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
        [self.timer invalidate];
        self.timer = nil;
        NSTimeInterval timeSinceCurrentReference = now - self.currentReferenceTime;

编辑:从 accumalitiveTime 中减去 timeSinceCurrentReference,因为这是一个倒数计时器。如有必要,还添加了保存到核心数据的注释。

        self.accumulativeTime -= timeSinceCurrentReference;
        [self updateTimerLabelWithTotalTime:self.accumulativeTime];

        //Optionally save self.accumulativeTime to core data for future use.
    }

    NSString *buttonTitle = (self.timer != nil) ? @"Pause" : @"Resume";
    [self.startButton setTitle:buttonTitle forState:UIControlStateNormal];
}

您不需要存储任何不必要的状态,例如showButtonValue变量。相反,您可以根据 self.timer == nil 来决定是否暂停或恢复。如果没有计时器运行,则获取当前参考时间并启动一个新计时器。计时器计划每 0.01 秒触发一次,这有望使其精确到 0.1 秒。您永远不需要将按钮的标题更改为“开始”。它是“暂停”或“恢复”。当定时器被用户暂停时,我们处理self.timer并更新定时器标签,使其具有最准确的时间。

- (void)updateTimer:(id)sender
{
    NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
    NSTimeInterval timeSinceCurrentReference = now - self.currentReferenceTime;

编辑:从 self.accumulativeTime 中减去 timeSinceCurrentReference 以获得总时间(即总时间随着时间的推移而减少)。

    NSTimeInterval totalTime = self.accumulativeTime - timeSinceCurrentReference;

    if (totalTime <= 0) {
        totalTime = 0;
        [self.timer invalidate];
        self.timer = nil;

        //What to do when we reach zero? For example, we could reset timer to 5 minutes:
        self.accumulativeTime = 300;
        totalTime = self.accumulativeTime;
        [self.startButton setTitle:@"Start" forState:UIControlStateNormal];
    }

    [self updateTimerLabelWithTotalTime:totalTime];
}

每次触发计时器时,我们通过查找 和 之间的差并将其添加到 来获得nowself.currentReferenceTime时间self.accumulativeTime

- (void)updateTimerLabelWithTotalTime:(NSTimeInterval)totalTime
{
    NSInteger hours = totalTime / 3600;
    NSInteger minutes = totalTime / 60;
    NSInteger seconds = totalTime;
    NSInteger fractions = totalTime * 10;

    self.timerLabel.text = [NSString stringWithFormat:@"%02u:%02u:%02u.%01u", hours, minutes % 60, seconds % 60, fractions % 10];
}

@end

该方法- (IBAction)changeTimerState:(UIButton *)sender由 UIButton 在“Touch Up Inside”事件 (UIControlEventTouchUpInside) 上调用。

你不需要在 viewDidLoad 中做任何事情。

此外,重要的是,这一切都在主线程上完成。如果主线程更新计时器标签有任何阻碍,那么用户可见的文本可能不准确,但当它更新时,您可以确定它会再次准确。这取决于您的应用程序还在做什么。但是由于所有的 UI 更新都必须在主线程上完成,所以确实无法避免这一点。

希望这可以帮助。让我知道是否有任何不清楚的地方。

(Xcode 项目在这里可用:https ://github.com/sdods3782/TVTTableViewTest )

于 2013-07-30T22:12:28.527 回答
1

调用detachNewThreadSelector将创建一个新线程并在调用后立即执行提到的选择器。

为了解决您的问题,请更改您的方法,例如:

- (void)viewDidLoad
{
    [super viewDidLoad];

    [NSThread detachNewThreadSelector:@selector(startTimer:) toTarget:self withObject:nil];
}
-(IBAction)startTimer:(id)sender
{
    if (testTask.showButtonValue == 1) {
        [startButton setTitle:@"Start" forState:UIControlStateNormal];
        timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
        testTask.showButtonValue = 3;
    } else if (testTask.showButtonValue == 2) {
        [startButton setTitle:@"Resume" forState:UIControlStateNormal];
        [timer invalidate];
        timer = nil;
        testTask.showButtonValue = 3;
    } else if (testTask.showButtonValue == 3){
        [startButton setTitle:@"Pause" forState:UIControlStateNormal];
        timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
        testTask.showButtonValue = 2;
    }
}
于 2013-07-30T20:38:21.403 回答