1

I have a UIView with a UITableView and a UIImageView in it. The UITableView takes up the top half of the UIView. The UIImageView takes the bottom half of the UIView.

I am using an NSTimer to update the image that is being displayed in the UIImageView. Basically, every 3 seconds, the UIImageView loads in a new UIImage.

Problem is, if I select a row from the UITableView, the NSTimer keeps running. How can I stop the NSTimer on didSelectRowFromIndexPath, and then start it again when the view returns to the screen?

-(void)viewDidLoad {
NSThread* timerThread = [[NSThread alloc] initWithTarget:self selector:@selector(startTimerThread) object:nil]; //Create a new thread
    [timerThread start]; //start the thread

} // end viewDidLoad

//
-(void) startTimerThread {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
    [[NSTimer scheduledTimerWithTimeInterval: 3.0
                                      target: self
                                    selector: @selector(loadNextPhoto)
                                    userInfo: nil
                                     repeats: YES] retain];

    [runLoop run];
    [pool release];
}
Chris
  • 5,485
  • 15
  • 68
  • 130

4 Answers4

2

Keep a reference to the timer you create so you can stop it when needed. The next time you need it you can just create a new timer. It sounds like the best places for you to do this are in viewDidAppear: and viewWillDisappear:. I'm not sure why you are starting the timer on a new thread. You may have a good reasons for doing so but I have never needed to do this.

//This code assumes you have created a retained property for loadNextPhotoTimer

    - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        [self startTimer];
    }

    - (void)viewWillDisappear:(BOOL)animated {
        [super viewWillDisappear:animated];
        [self stopTimer];
    }

    - (void)startTimer {
        NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:3.0] interval:3.0 target:self selector:@selector(loadNextPhoto) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        self.loadNextPhotoTimer = timer;
        [timer release];
    }

    - (void)stopTimer {
        [self.loadNextPhotoTimer invalidate];
        self.loadNextPhotoTimer = nil;
    }
jaminguy
  • 25,840
  • 2
  • 23
  • 21
  • this solution isn't working for me ... My timer is created with "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:" ... do i have to specify explicitly the run loop to which the timer needs to be added ? – Mohit Athwani May 31 '14 at 09:52
  • Ok I changed my implementation of startTimer to be exactly like above and it works .... – Mohit Athwani May 31 '14 at 10:40
1

You can use the NSTimer's invalidate method to destroy the timer.

You'll then have to recreate it, as you did in your startTimerThread method, when you return to the view.

Macmade
  • 52,708
  • 13
  • 106
  • 123
1

To get access to the thread from outside the startTimerThread method, you should add a property to your class. I would suggest property over ivar because you can have atomic accessors synthesized automatically. What you do with the timer depends on whether you want to pause or stop it.

Stopping the timer means the next time you start it, it will start with the full 3 seconds remaining. This means if the timer will fire in 1 second when you stop it, it will still take 3 seconds to fire after you start it again. However, stopping is very simple. A stopTimerThread would be defined as follows:

- (void)stopTimerThread {
    NSTimer *theTimer = self.timer;
    [theTimer invalidate];
    self.timer = nil;
}

Since the runloop in your other thread now has no sources, it will automatically exit and the thread will end, so you can use startTimerThread when you want to start it again.

Pausing the timer means that, if it will fire in 1 second when you pause it, it will fire 1 second after you restart it. To do this, you will also need a property containing the time remaining before the timer fires, which will be set in stopTimerThread and used in startTimerThread. The implementation of stopTimerThread is as follows:

- (void)stopTimerThread {
    NSTimer *theTimer = self.timer;
    NSDate *fireDate = [theTimer fireDate];
    [theTimer invalidate];
    self.timerTimeRemaining = - [fireDate timeIntervalSinceNow];
    self.timer = nil;
}

Notice that the time remaining is set to be the negative of the time interval, since the fire date will be before now. You will also have to modify startTimerThread to take the time remaining into account when setting up the timer.

- (void)startTimerThread {
    // set up autorelease pool, get run loop
    NSTimeInterval timeLeft = self.timerTimeRemaining;
    if(timeLeft <= 0.0) timeLeft = 3.0;
    NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:timeLeft];
    NSTimer *theTimer = [[NSTimer alloc] initWithFireDate:fireDate interval:3.0 target:self selector:@selector(loadNextPhoto) userInfo:nil repeats:YES];
    [runLoop addTimer:theTimer forMode:NSDefaultRunLoopMode];
    self.timer = theTimer;
    [theTimer release];
    [runLoop run];
    // clean up
}
ughoavgfhw
  • 39,734
  • 6
  • 101
  • 123
0

Create a flag (BOOL) to control the updating of the image.

Clear it when you want the routine that updates the image to ignore the fact that it was called by the timer event firing. When you want the image updates to resume, just set the flag. Maybe something like this:

-(void)ImageUpdater:(NSTimer *)theTimer
{
  if(image_updates_enabled)
  {
     // Code to update image
  }
}

Since you are firing a method only every three seconds it isn't a big deal to just let the timer run.

The other thing you can to is to setup the timer so that it does not repeat. With that in place the decision to re-enable the timer could be made somewhere else in your code rather than automatically. You could even do it within the callback function and still use the "image_updates_enabled" flag if you wish.

martin's
  • 3,853
  • 6
  • 32
  • 58