web-dev-qa-db-fra.com

Comment puis-je suspendre un NSTimer par programme?

J'utilise un NSTimer pour effectuer un rendu dans une application iPhone basée sur OpenGL. J'ai une boîte de dialogue modale qui apparaît et demande à l'utilisateur d'entrer. Pendant que l’utilisateur fournit des informations, je souhaite "mettre en pause", par exemple:

[myNSTimer pause];

J'utilise cette syntaxe parce que je fais des choses comme:

[myNSTimer invalidate];

quand je veux que ça s'arrête.

Comment puis-je mettre par programme le NSTimer en pause?

42
MrDatabase

D'ici:

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

"Vous pouvez stocker la durée écoulée depuis le démarrage du minuteur ... Lorsque le minuteur démarre, enregistrez la date dans une variable NSDate. Puis, lorsque l'utilisateur bascule ... utilisez la méthode timeIntervalSinceNow dans la classe NSDate pour stocker combien le temps est écoulé ... notez que ceci donnera une valeur négative pour timeIntervalSinceNow. Lorsque l'utilisateur reviendra, utilisez cette valeur pour définir un minuteur approprié.

Je ne pense pas qu'il y ait un moyen de faire une pause et de redémarrer une minuterie. J'ai fait face à une situation similaire. "

22
BobbyShaftoe

Je viens de penser à la mise à jour de correctifs mineurs de la réponse de 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];
}
35
Thiru

Pour ce faire, j'ai stocké la date de mise à feu du NSTimer dans une variable, puis défini la date de mise à feu sur INFINITY. 

Quand je fais une pause:

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

Lorsque je rétablis la pause, j'ajoute la durée de pause du minuteur à la date de déclenchement précédente:

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

Cela rendait beaucoup plus facile que de s’inquiéter d’invalider et de réinitialiser le chronomètre. J'avais beaucoup de chronomètres, alors je les ai tous placés dans un tableau et je les ai tous traités. J'ai sous-classé le NSTimer afin de garder le previousFiringDate associé à ce minuteur.

C’était aussi mieux que d’utiliser un BOOL pour indiquer s’il était en pause ou non, afin que les actions ne se produisent pas si le BOOL était défini. En effet, si quelque chose était sur le point de se produire avant votre pause, cela ne se produira pas une fois que vous l'avez mis en pause, car elle vient d'être ignorée.

12
kapesoftware

La façon la plus simple de réussir est la suivante:

-(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;
    }
}
8
Riley

Dans mon cas, j'avais une variable 'secondes' et un autre 'timerIsEnabled'. Lorsque je voulais suspendre le chronomètre, définissez simplement timerIsEnabled sur false. Comme la variable secondes n'était incrémentée que si timerIsEnabled était à true, j'ai résolu mon problème. J'espère que cela t'aides.

8
Oscar Olim

Voici une catégorie sur NSTimer qui permet les fonctionnalités de pause et de reprise.

Entête:

#import <Foundation/Foundation.h>

@interface NSTimer (CVPausable)

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

@end

La mise en oeuvre:

#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
5
Carl Veazey

Vieille question, mais je viens d'écrire une classe utilitaire légère pour accomplir exactement ce que l'OP cherche.

https://github.com/codeslaw/CSPausibleTimer

Prend également en charge les minuteries à répétition. J'espère que cela vous sera utile!

2
Chris

Basé sur les réponses de @Thiru et @ kapesoftware, j'ai créé une catégorie Objective-C sur NSTimer à l'aide d'objets associés pour suspendre et reprendre le chronomètre.

#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);
}

Cela permet de garder les choses en ordre et vous évite de créer des variables d'instance ou des propriétés pour mettre en pause et reprendre vos minuteurs. 

2
Toby
1
keeshux

Si votre intervalle de temps de tir est tolérant, vous pouvez utiliser CADisplayLink au lieu de NSTimer. Parce que CADisplayLink supporte -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];
0
ooOlly

Vous ne pouvez pas suspendre NSTimer comme mentionné ci-dessus. Donc, le temps à capturer ne devrait pas dépendre de la minuterie, je suggère. Voici ma solution la plus simple:

Lors de la création de la minuterie, initialisez le temps de l’unité de départ comme suit:

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];

` Maintenant dans la méthode du timer de mise à jour:

  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.
    }

 }
0
Masud alam

Eh bien, je trouve que c'est la meilleure méthode pour implémenter la mise en pause d'une minuterie. Utilisez une variable statique pour basculer en pause et reprendre. En pause, invalidez le chronomètre et réglez-le sur nil. Dans la section de reprise, réinitialisez le chronomètre en utilisant le code d'origine par lequel il a été démarré.

Le code suivant fonctionne sur la réponse à un clic sur un bouton de pause.

-(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;
    }
}
0
divyenduz

Le code ci-dessous est destiné à être utilisé avec une vue de défilement, qui affiche le contenu à intervalles réguliers et revient au début à la fin, mais la même logique peut être utilisée pour tout bouton pause/redémarrage. 

Le minuteur se déclenche au chargement (initFeatureTimer) et met en pause les interactions de l'utilisateur (la méthode de glissement commence), ce qui permet à l'utilisateur de regarder le contenu aussi longtemps qu'il le souhaite. Lorsque l'utilisateur lève son doigt (finit de glisser), le booléen en attente est réinitialisé, mais vous avez également la possibilité d'ajouter un peu plus de retard.

De cette façon, le scroller ne s'éloigne pas instantanément lors de la levée des doigts, il est plus réactif pour l'utilisateur.

//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
}
0
Johnny Rockex
0
Aggressor