2012/08/23

UIPickerView與NSTimer

朋友問我一個問題,在畫面上顯示UIPickerView,另外顯示一個UILabel進行倒數計時(譬如說從12.0秒倒數到0.0秒,每0.1秒更新)。

聽起來很簡單,不就用個NSTimer就搞定了嗎?可惜不是。

當倒數計時並更新UILabel時,若手指撥動UIPickerView,那麼倒數計時會暫停、UILabel也不會更新。



真正的原因我不清楚,大概是因為UIPickerView需要追蹤使用者的手指觸控事件,所以佔據了thread的runloop,使得NSTimer無法觸發吧。

解法之一是在另一個thread使用NSTimer:

float countdown = 120;

- (void)timerFireMethod:(NSTimer*)timer
{
    if(countdown > 0){
        countdown--;
        self.label.text = [NSString stringWithFormat:@"%2.1f", countdown/10];
        NSLog(@"%2.1f", countdown/10);
    }
    else{
        [self suspendTimer];
    }
}

- (void)suspendTimer
{
    if(_timer != nil)
    {
        [_timer invalidate];
        _timer = nil;
       
        CFRunLoopStop([_runLoop getCFRunLoop]);
       
        _timerThread = nil;
    }
}

- (void)_startTimerThread
{
    @autoreleasepool {

        _runLoop = [NSRunLoop currentRunLoop];
        _timer = [NSTimer scheduledTimerWithTimeInterval:0.1
                                               target:self
                                             selector:@selector(timerFireMethod:)
                                             userInfo:nil
                                              repeats:YES] ;
        [_runLoop run];
    }
}

- (void)resumeTimer
{
    if(_timer == nil)
    {
        _timerThread = [[NSThread alloc] initWithTarget:self
                                               selector:@selector(_startTimerThread)
                                                 object:nil];
        [_timerThread start];
    }
}

其中resumeTimer、suspendTimer是使用介面,把你想要執行的程式寫在timerFireMethod:裡。

解法之二是使用GCD(Grand Central Dispatch):

float countdown;

-(void)tick
{
    self.label.text = [NSString stringWithFormat:@"%2.1f", countdown/10];
}
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
  
    countdown = 120;
  
    queue = dispatch_queue_create("com.example.MyQueue",  NULL);
  
    ds_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
  
    dispatch_source_set_timer(ds_timer,  dispatch_time(DISPATCH_TIME_NOW,  0),  0.1*NSEC_PER_SEC, 0);
    dispatch_source_set_event_handler(ds_timer,  ^(){
        if(countdown > 0){
            NSLog(@"tick %2.1f", countdown/10);
            countdown--;
            [self performSelectorOnMainThread:@selector(tick) withObject:nil waitUntilDone:YES];
        }
        else{
            //over
        }
    });
    dispatch_resume(ds_timer);
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
  
    dispatch_suspend(ds_timer);
    dispatch_release(ds_timer);
    dispatch_release(queue);
}

利用timer dispatch source便能每隔一段時間產生出某事件,然後指定想要執行的程式碼,要注意的是,想要更新iOS的畫面時,必須在main thread裡進行,所以上面程式碼使用了 [self performSelectorOnMainThread:@selector(tick) withObject:nil waitUntilDone:YES];這個方法。

應該也能以NSOperation與NSOperationQueue解決,但我還沒試。


參考文章:


No comments:

Post a Comment