0

だから私はタイマー付きのView Controllerを持っています。ボタンが1つ付いています。ビューが初めて読み込まれるとき、ボタンには「開始」と表示されます。

タップすると「開始」→「一時停止」。

別のタップ、「一時停止」→「再開」。

もう一度「再開」→「一時停止」をタップ。

タイマーを正確にしたいので、メインスレッドから切り離しました(正しい方法を選択したと思います。説明をいただければ幸いです)。しかし、スレッドから切り離すと実際にメソッドが呼び出されるようです...これにより、ボタンは開始ではなく「一時停止」で開始されます。これを修正するにはどうすればよいですか?

ちなみに、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 変数の初期化は、initWithNibName:bundle: などの適切な init* メソッドで行う必要があります。この時点で、コア データからタイマー値を読み取ります。

- (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 を減算して、totalTime を取得します (つまり、時間の経過とともに totalTime が減少します)。

    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は、「Touch Up Inside」イベント (UIControlEventTouchUpInside) で UIButton によって呼び出されます。

viewDidLoad では何もする必要はありません。

また、重要なことに、これはすべてメインスレッドで行われます。メイン スレッドがタイマー ラベルを更新する際に何らかの障害が発生すると、ユーザーに表示されるテキストが不正確になる可能性がありますが、更新されると再び正確になることが保証されます。アプリが他に何をしているかによって異なります。しかし、UI の更新はすべてメイン スレッドで行う必要があるため、これを回避することはできません。

お役に立てれば。不明な点があればお知らせください。

(ここで入手可能な Xcode プロジェクト: https://github.com/sdods3782/TVTTableViewTest )

于 2013-07-30T22:12:28.527 に答える