我正在使用NSTimer在基于OpenGL的iPhone应用程序中进行渲染。我有一个模式对话框弹出并请求用户输入。当用户提供输入时,我想“暂停”,例如:
[myNSTimer pause];
我正在使用这种语法,因为我一直在做这样的事情:
[myNSTimer invalidate];
当我希望它停止时。
如何以编程方式暂停NSTimer?
答案 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)
答案 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)
可以在NSTimer
上重复使用我的类别:
https://github.com/keeshux/ios-components/tree/master/Components/Categories/NSTimer%2BPause
答案 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)
答案 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