5

I implement a UITableView of UIImageView cells, each of which periodically refreshes itself every 5 seconds via NSTimer. Each image is loaded from a server in the background thread, and from that background thread I also update the UI, displaying the new image, by calling performSelectorOnMainThread. So far so good.

The problem I noticed is the number of threads is increasing over time and UI becomes non-responsive. Therefore, I want to invalidate NSTimer if a cell goes off screen. Which delegation methods in UITableView should I use to do this efficiently?

The reason why I associate an NSTimer with each cell because I don't want image transition to occur at the same time for all cells. Is there any other methods to do this by the way? For example, is it possible to use just a single NSTimer?

(I can't use SDWebImage because my requirement is to display a set of images in loop loaded from a server)

// In MyViewController.m

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        ...
        NSTimer* timer=[NSTimer scheduledTimerWithTimeInterval:ANIMATION_SCHEDULED_AT_TIME_INTERVAL 
                                                    target:self 
                                                  selector:@selector(updateImageInBackground:) 
                                                  userInfo:cell.imageView 
                                                   repeats:YES];
        ...
    }

- (void) updateImageInBackground:(NSTimer*)aTimer
{  
    [self performSelectorInBackground:@selector(updateImage:)
                       withObject:[aTimer userInfo]];
}  

- (void) updateImage:(AnimatedImageView*)animatedImageView 
{      
    @autoreleasepool { 
        [animatedImageView refresh];
    }
}  

// In AnimatedImageView.m

 -(void)refresh
    {
        if(self.currentIndex>=self.urls.count)
            self.currentIndex=0;

    ASIHTTPRequest *request=[[ASIHTTPRequest alloc] initWithURL:[self.urls objectAtIndex:self.currentIndex]];
    [request startSynchronous];

    UIImage *image = [UIImage imageWithData:[request responseData]];

    // How do I cancel this operation if I know that a user performs a scrolling action, therefore departing from this cell.
    [self performSelectorOnMainThread:@selector(performTransition:)
                       withObject:image
                    waitUntilDone:YES];
}

-(void)performTransition:(UIImage*)anImage
{
    [UIView transitionWithView:self duration:1.0 options:(UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction) animations:^{ 
        self.image=anImage;
        currentIndex++;
    } completion:^(BOOL finished) {
    }];
}
Prashant Nikam
  • 2,253
  • 4
  • 17
  • 29
Nimit Pattanasri
  • 1,602
  • 1
  • 26
  • 37
  • 2
    Are you creating a new cell for each call of cellForRowAtIndexPath? Or are you properly dequeueing reusable cells? In the latter (correct) case, you can use the table view cell's prepareForReuse method. –  Sep 03 '12 at 05:17
  • See this question: http://stackoverflow.com/q/3330735/937822 – lnafziger Sep 03 '12 at 05:20
  • you might be able to do what you want using core animation--create `CAAnimation` objects to animate the changes you want in your cells, and use the `beginTime` field of each animation to vary the time at which they animate – nielsbot Sep 03 '12 at 05:32
  • @H2CO3 Yes, I reused cells, but never managed those when they are dequeued. I will give it a shot. – Nimit Pattanasri Sep 03 '12 at 05:52
  • @H2CO3 Can you change your comment to an answer so I can accept it? – Nimit Pattanasri Sep 03 '12 at 07:47

2 Answers2

6

willMoveToSuperview: and/or didMoveToSuperview: do not work on ios 6.0

from ios 6.0 you have the following method of UITableViewDelegate

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath

Use this method to detect when a cell is removed from a table view, as opposed to monitoring the view itself to see when it appears or disappears.

Mert
  • 6,025
  • 3
  • 21
  • 33
  • didEndDisplayingCell **does not work** - - it's not called when, simply, your screen with the tableview is closed, and the whole table view goes away. You have to *additionally* handle that case. – Fattie Jul 16 '19 at 16:54
5

If you properly manage memory and dequeue reusable cells, you can subclass UITableViewCell and override its - prepareForReuse method in order to stop the timer.

Furthermore, as @lnfaziger points out, if you want to stop the timer immediately when the cell is removed from the table view, you can also override its willMoveToSuperview: and/or didMoveToSuperview: method and check if its superview parameter is nil - if it is, the cell is being removed, so you can stop the timer.

  • 3
    Note that this is not called when a cell goes off the screen, but instead when a cell is about to be reused. This can leave cells waiting in the queue that still have the timer running. If you need to cancel them as soon as they leave the screen, use `willMoveToSuperview` or `didMoveToSuperview` instead. – lnafziger Sep 03 '12 at 15:20
  • Thanks, I think that it will be helpful for other people! (I ran into this situation when trying to cancel KVO and had a time of figuring out the best place to do it, lol.) – lnafziger Sep 03 '12 at 15:22
  • @lnafziger wait, but aren't they called when they move **in** to the table view? –  Sep 03 '12 at 15:22
  • Yes, both. But you need to check to see if the cell's superview is nil or not in order to determine if it is going in/out of the tableview. (btw, +1 for your answer :) ) – lnafziger Sep 03 '12 at 15:22
  • I haven't actually tested this, but I suspect that when the cell is leaving the tableview, the `superview` property will be set to the tableview in `willMoveToSuperview:` and will be `nil` in `didMoveToSuperview:`. – lnafziger Sep 03 '12 at 15:59
  • @lnafziger it should be `nil` in both cases if it really works as you described. –  Sep 03 '12 at 16:42
  • Actually, the "will" methods don't change the properties yet, so that you can make a determination of where they were coming from, whereas the "did" methods are after the fact, correct? – lnafziger Sep 03 '12 at 17:07
  • 1
    @lnafziger nope. It's rather true that when inside the 'will' methods, the `self.superview` property itself isn't unchanged, but the parameter reflects the new superview - it's both conformant to Cocoa's naming conventions *and* logical; else there would be no way to determine in advance which view **will the new superview be**. –  Sep 03 '12 at 17:24
  • Oh, never-mind. I just noticed that you were referring to the paramater and not the property. You are absolutely correct on this, sorry! We were referring to different things. :) – lnafziger Sep 03 '12 at 17:35
  • Something must have changed in iOS 7+ - it looks like (at least in my project) in `willMoveToSuperview:` UITableViewCells no longer move to a nil superview, they are moved to a `UITableViewWrapperView` – taber Mar 01 '14 at 16:12