如何以编程方式暂停NSTimer?

时间:2008-12-07 04:12:29

标签: iphone objective-c cocoa-touch

我正在使用NSTimer在基于OpenGL的iPhone应用程序中进行渲染。我有一个模式对话框弹出并请求用户输入。当用户提供输入时,我想“暂停”,例如:

[myNSTimer pause];

我正在使用这种语法,因为我一直在做这样的事情:

[myNSTimer invalidate];

当我希望它停止时。

如何以编程方式暂停NSTimer?

16 个答案:

答案 0 :(得分:35)

只想更新kapesoftware答案的小修正:

NSDate *pauseStart, *previousFireDate;

-(void) pauseTimer:(NSTimer *)timer { 

    pauseStart = [[NSDate dateWithTimeIntervalSinceNow:0] retain];

    previousFireDate = [[timer fireDate] retain];

    [timer setFireDate:[NSDate distantFuture]];
}

-(void) resumeTimer:(NSTimer *)timer {

    float pauseTime = -1*[pauseStart timeIntervalSinceNow];

    [timer setFireDate:[previousFireDate initWithTimeInterval:pauseTime sinceDate:previousFireDate]];

    [pauseStart release];

    [previousFireDate release];
}

答案 1 :(得分:22)

从这里开始:

http://discussions.apple.com/thread.jspa?threadID=1811475&tstart=75

“您可以存储自计时器启动以来经过的时间量...当计时器开始将日期存储在NSDate变量中。然后当用户切换时...使用NSDate类中的方法timeIntervalSinceNow存储已经过了多少时间...请注意,这将为timeIntervalSinceNow提供负值。当用户返回时使用该值来设置适当的计时器。

我认为没有办法暂停和重启计时器。我遇到了类似的情况。 “

答案 2 :(得分:12)

我完成此操作的方法是将NSTimer的触发日期存储在变量中,然后将触发日期设置为INFINITY。

当我暂停时:

pauseStart = [[NSDate dateWithTimeIntervalSinceNow:0] retain];
previousFiringDate = [timer firingDate];
[timer setFiringDate:INFINITY]

当我取消暂停时,我将计时器暂停的时间加到上一个开火日期:

float pauseTime = -1*[pauseStart timeIntervalSinceNow];
[timer setFireDate:[previousFireDate initWithTimeInterval:pauseTime sinceDate:previousFireDate]];

这使得比担心失效和重新初始化计时器更容易。我有很多计时器,所以我把它们全部放在一个数组中,然后对所有这些计时器执行此操作。我将NSTimer子类化,以保持与该计时器关联的previousFiringDate。

这也比使用BOOL更好,无论它是否被暂停,因此如果设置了BOOL,则不会发生动作。这是因为如果在暂停之前发生了某些事情,那么在你取消它之后它就不会发生,因为它刚刚被跳过。

答案 3 :(得分:8)

就我而言,我有一个变量'seconds',另一个'timerIsEnabled'。当我想暂停计时器时,只是将timerIsEnabled设为false。由于只有在timerIsEnabled为true时,seconds变量才会递增,我修复了我的问题。希望这会有所帮助。

答案 4 :(得分:8)

我设法做到的最简单的方法是:

-(void) timerToggle{
    if (theTimer == nil) {
        float theInterval = 1.0/30.0;
        theTimer = [NSTimer scheduledTimerWithTimeInterval:theInterval target:self selector:@selector(animateBall:) userInfo:nil repeats:YES];
    } else {
        [theTimer invalidate];
        theTimer = nil;
    }
}

答案 5 :(得分:5)

以下是NSTimer上允许暂停和恢复功能的类别。

部首:

#import <Foundation/Foundation.h>

@interface NSTimer (CVPausable)

- (void)pauseOrResume;
- (BOOL)isPaused;

@end

实现:

#import "NSTimer+CVPausable.h"
#import <objc/runtime.h>

@interface NSTimer (CVPausablePrivate)

@property (nonatomic) NSNumber *timeDeltaNumber;

@end

@implementation NSTimer (CVPausablePrivate)

static void *AssociationKey;

- (NSNumber *)timeDeltaNumber
{
    return objc_getAssociatedObject(self, AssociationKey);
}

- (void)setTimeDeltaNumber:(NSNumber *)timeDeltaNumber
{
    objc_setAssociatedObject(self, AssociationKey, timeDeltaNumber, OBJC_ASSOCIATION_RETAIN);
}

@end


@implementation NSTimer (CVPausable)

- (void)pauseOrResume
{
    if ([self isPaused]) {
        self.fireDate = [[NSDate date] dateByAddingTimeInterval:[self.timeDeltaNumber doubleValue]];
        self.timeDeltaNumber = nil;
    }
    else {
        NSTimeInterval interval = [[self fireDate] timeIntervalSinceNow];
        self.timeDeltaNumber = @(interval);
        self.fireDate = [NSDate distantFuture];
    }
}

- (BOOL)isPaused
{
    return (self.timeDeltaNumber != nil);
}

@end

答案 6 :(得分:2)

老问题,但我刚写了一个轻量级的实用程序类来完成OP正在寻找的东西。

https://github.com/codeslaw/CSPausibleTimer

也支持重复计时器。希望它派上用场!

答案 7 :(得分:2)

基于@Thiru和@kapesoftware的答案,我在NSTimer上使用关联对象制作了一个Objective-C类别,用于暂停和恢复计时器。

#import <objc/runtime.h>

@interface NSTimer (PausableTimer)

@property (nonatomic, retain) NSDate *pausedDate;

@property (nonatomic, retain) NSDate *nextFireDate;

-(void)pause;

-(void)resume;

@end

...

@implementation NSTimer (PausableTimer)

static char * kPausedDate = "pausedDate";
static char * kNextFireDate = "nextFireDate";

@dynamic pausedDate;
@dynamic nextFireDate;

-(void)pause {

  self.pausedDate = [NSDate date];

  self.nextFireDate = [self fireDate];

  [self setFireDate:[NSDate distantFuture]];
}

-(void)resume
{
  float pauseTime = -1*[self.pausedDate timeIntervalSinceNow];

  [self setFireDate:[self.nextFireDate initWithTimeInterval:pauseTime sinceDate:self.nextFireDate]];
}

- (void)setPausedDate:(NSDate *)pausedDate
{
  objc_setAssociatedObject(self, kPausedDate, pausedDate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSDate *)pausedDate
{
  return objc_getAssociatedObject(self, kPausedDate);
}

- (void)setNextFireDate:(NSDate *)nextFireDate
{
  objc_setAssociatedObject(self, kNextFireDate, nextFireDate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSDate *)nextFireDate
{
  return objc_getAssociatedObject(self, kNextFireDate);
}

它保持整洁,意味着您不必创建实例变量或属性只是为了暂停和恢复您的计时器。

答案 8 :(得分:1)

答案 9 :(得分:0)

如上所述,您无法暂停NSTimer。因此,我建议,被捕获的时间不应该依赖于定时器被激活。这是我最简单的解决方案:

创建计时器时,初始化单位时间如下:

self.startDate=[NSDate date];
self.timeElapsedInterval=[[NSDate date] timeIntervalSinceDate:self.startDate];//This ``will be 0 //second at the start of the timer.``     

myTimer= [NSTimer scheduledTimerWithTimeInterval:1.0 target:self `selector:@selector(updateTimer) userInfo:nil repeats:YES];

` 现在在更新计时器方法中:

  NSTimeInterval unitTime=1;
-(void) updateTimer
 {
 if(self.timerPaused)
       {
           //Do nothing as timer is paused
       }
 else{
     self.timerElapsedInterval=timerElapsedInterval+unitInterval;
    //Then do your thing update the visual timer texfield or something.
    }

 }

答案 10 :(得分:0)

好吧,我发现这是实现暂停计时器事务的最佳方法。使用静态变量切换暂停和恢复。在暂停时,使计时器无效并将其设置为nil,并在resume部分使用原始代码重置计时器。

以下代码适用于对暂停按钮单击的响应。

-(IBAction)Pause:(id)sender
{
    static BOOL *PauseToggle;
    if(!PauseToggle)
    {
        [timer invalidate];
        timer = nil;
        PauseToggle = (BOOL *) YES;
    }
    else
    {
        timer = [NSTimer scheduledTimerWithTimeInterval:0.04 target:self selector:@selector(HeliMove) userInfo:nil repeats:YES];
        PauseToggle = (BOOL *) NO;
    }
}

答案 11 :(得分:0)

下面的代码用于滚动视图,它定期打开内容,并在完成时循环回到前面,但是相同的逻辑可以用于任何暂停/重启按钮。

计时器在加载时触发(initiateFeatureTimer),并暂停用户交互(将开始拖动方法),允许用户根据自己的喜好查看内容。当用户抬起她的手指(将结束拖动)时,featurePaused布尔值会被重置,但是,您也可以选择添加更多的延迟。

这样,滚动条在手指抬起时不会立即移动,它对用户更具反应性。

//set vars, fire first method on load.
featureDelay = 8; //int
featureInt = featureDelay-1; //int
featurePaused = false; //boolean

-(void)initiateFeatureTimer {
    featureTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(moveFeatureScroller) userInfo:nil repeats:true];
    [featureTimer fire];
}
 -(void)moveFeatureScroller {

    if (!featurePaused){ //check boolean
        featureInt++;

         if (featureInt == featureDelay){
             featureInt = 0; //reset the feature int

            /*//scroll logic
            int nextPage = (featurePage + 1) * w;
            if (featurePage == features.count-1){ nextPage = 0; }
            [featureScroller setContentOffset:CGPointMake(nextPage, 0)]; */          
         }
      }
}

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    featurePaused = true; //pause the timer
}
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    featurePaused = false; //restart the timer
    featureInt = -3; //if you want to add an additional delay
}

答案 12 :(得分:0)

这是一个简单的快速暂停/播放计时器。

https://github.com/AnthonyUccello/Swift-Pausable-Timer/blob/master/Timer.swift

答案 13 :(得分:0)

如果您的开火时间间隔是宽容的,则可以使用CADisplayLink代替NSTimer。由于CADisplayLink支持-pause

displayLink = [[CADisplayLink displayLinkWithTarget:self selector:@selector(fireMethod:)];
displayLink.frameInterval = approximateVaue;//CADisplayLink's frame rate is 60 fps.
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

//pause
[displayLink pause];

答案 14 :(得分:0)

这是一个更现代的解决方案,包装定时器并在它停止时存储。

在实际的计时器触发回调中调用 pause 时,我还遇到了一个有趣的错误,这将导致计时器在表达式 timer.fireDate.timeIntervalSinceNow 计算为零后继续重复触发,我添加了一个标志在暂停中明确避免这种情况

import Foundation

class PausableTimer {
    
    let timer: Timer
    var timeIntervalOnPause: TimeInterval?
        
    init(timeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) {
        timer = Timer(timeInterval: interval, repeats: repeats, block: block)
    }
    
    func invalidate() {
        timer.invalidate()
    }
    
    var paused: Bool {
        timeIntervalOnPause != nil
    }
    
    func toggle() {
        if paused {
            resume()
        } else {
            pause(isFiringTimer: false)
        }
    }
    
    /// Pause the timer
    /// - Parameter isFiringTimer: pass in true if the timer is currently firing, in which case the resume time will not
    /// be relative to next fire date, but instead relative to the time interval. It's improtant that this be correct otherwise there maybe a near
    /// infinite loop firing of the `Timer`
    func pause(isFiringTimer: Bool) {
        guard timer.isValid else {
            return
        }
        timeIntervalOnPause = isFiringTimer || timer.fireDate.timeIntervalSinceReferenceDate == 0 ? timer.timeInterval : timer.fireDate.timeIntervalSinceNow
        timer.fireDate = .distantFuture
    }
    
    /// Resume the timer, if it was not paused has not effect, and causes assertion failure
    func resume() {
        guard timer.isValid else {
            return
        }
        guard let timeIntervalOnPause = timeIntervalOnPause else {
            assertionFailure("Resuming a timer that was never paused \(self) - \(self.timer)")
            return
        }
        let relativeFireDate = Date(timeIntervalSinceNow: timeIntervalOnPause)
        timer.fireDate = relativeFireDate
        self.timeIntervalOnPause = nil
    }
    
}

答案 15 :(得分:-1)

我还需要可以使用的NSTimer。在阅读了这个帖子和你的答案,并意识到我唯一需要的是将计时器的fireDate设置为遥远的未来然后回到现在我使用Pause和Resume方法为NSTimer制作了类别。所以我可以通过调用[myTimer pause]暂停计时器;并通过调用[myTimer resume]恢复;方法。在这里,希望它会帮助某人。

接口:

#import <Foundation/Foundation.h>

@interface NSTimer (Pausable)

-(void)pause;
-(void)resume;

@end

实现:

#import "NSTimer+Pausable.h"

@implementation NSTimer (Pausable)

-(void)pause
{
    [self setFireDate:[NSDate dateWithTimeIntervalSinceNow:[NSDate distantFuture]]]; //set fireDate to far future
}

-(void)resume
{
    [self setFireDate:[NSDate date]];
}

@end