NSTimer no se dispara cuando ocurre el evento uiscrollview

I have a UIImageView placed in UIScrollView, Basicly this UIImageView holds very big map, and created animation on a predefined path with "arrows" pointed navigation direction.

But, whenever uiscrollevents occurs, I think MainLoop freezes and NSTimer being not fired, and animation stopped.

Are there any existing property, which solves this problem, on UIScrollView, CAKeyFrameAnimation or NSTimer?

//viewDidLoad
   self.myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(drawLines:) userInfo:nil repeats:YES];

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

   CALayer *arrow = [CALayer layer];
   arrow.bounds = CGRectMake(0, 0, 5, 5);
   arrow.position = CGPointMake(line.x1, line.y1);
   arrow.contents = (id)([UIImage imageNamed:@"arrow.png"].CGImage);

   [self.contentView.layer addSublayer:arrow];

   CAKeyframeAnimation* animation = [CAKeyframeAnimation animation];
   animation.path = path;
   animation.duration = 1.0;
   animation.rotationMode = kCAAnimationRotateAuto; // object auto rotates to follow the path
   animation.repeatCount = 1;
   animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
   animation.fillMode = kCAFillModeForwards;

   [arrow addAnimation:animation forKey:@"position"];
}

preguntado el 01 de febrero de 12 a las 04:02

2 Respuestas

iOS Applications run on an NSRunLoop. Each NSRunLoop has different modes of execution for different tasks. For example, the default nstimer is scheduled to run under the NSDefaultRunMode on the NSRunLoop. What this means however is that certain UIEvents, scrollviewing being one, will interrupt the timer, and place it on a queue to be run as soon as the event stops updating. In your case, in order to get the timer to not be interrupted, you need to schedule it for a different mode, namely NSRunLoopCommonModes, like so:

  self.myTimer =  [NSTimer scheduledTimerWithTimeInterval:280
                                                                 target:self
                                                               selector:@selector(doStuff)
                                                               userInfo:nil
                                                                repeats:NO];
  [[NSRunLoop currentRunLoop] addTimer:self.myTimer forMode:NSRunLoopCommonModes]; 

This mode will allow your timer to not be interrupted by scrolling. You can find more about this info here: https://developer.apple.com/documentation/foundation/nsrunloop At the bottom you will see the definitions of the modes you can choose from. Also, legend has it, you can write your own custom modes, but few have ever lived to tell the tale im afraid.

Respondido 01 Jul 17, 06:07

Hey @GregPrice. Thanks for the explanation, even 3 years after. However, the doc says scheduledTimerWithTimeInterval:... creates a timer and schedules it on the current run loop in the default mode. So why do you add it twice in the default RunLoop, not just once in NSRunLoopCommonModes ¿modo? - Martin

UIScrollView runs the run loop in UITrackingRunLoopMode, que es diferente a NSDefaultRunLoopMode. It's not enough to schedule the timer for the default mode if you also want it to run in other modes. - Rob Mayoff

Para Swift 3: RunLoop.current.add(self.myTimer, forMode: .commonModes) - Steve Cotner

One more thing (c)

  1. use timerWithTimeInterval method in order to avoid adding timer to runloop in DefaultMode
  2. use mainRunLoop

De modo que:

self.myTimer = [NSTimer timerWithTimeInterval:280 target:self selector:@selector(doStuff) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.myTimer forMode:NSRunLoopCommonModes];

Respondido 08 Jul 19, 15:07

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.