0

I've UITableView with dynamically changed data in cells by the time, i.e I have custom class for objects that store data for each cell 'AVMFilmsStore'. This class has method for updating data by himself (it gets progress from the sever every 10 seconds) like:

   -(void)getProgress{
 // some operations
 ...

 if (progress<100){
 [self getProgressWithDelay:10];
 }
}

and getProgressWithDelay method is:

-(void)getProgressWithDelay:(float)delay{
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,(int64_t)(delay * NSEC_PER_SEC));

dispatch_after(time,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    [self getProgress];
    });
}

After every call of this method I check if progress value has changed and I need to tell to my main class with UITableView that it must redraw every cell where progress has changed. I have custom UITableViewCell class for this which contains method:

-(void)styleForReady:(BOOL)isReady andProgress:(float)progress{
if(!isReady){
    self.movieDate.hidden = YES;
    self.movieName.hidden = YES;
    self.playButton.hidden = YES;
    self.settingsButton.hidden = YES;
    self.progressLabel.hidden = NO;
    self.progressLine.hidden = NO;
    self.backgroundImage.hidden = YES;
    self.progressLine.transform = CGAffineTransformMakeScale(1.0, 4.0);
    self.progressLine.progressTintColor = MINT_1_0;
    self.progressLabel.textColor = [UIColor blackColor];
    self.bgView.hidden = YES;
    self.progressLabel.text = NSLocalizedString(@"movieCreating", nil);
    [self.progressLine setProgress:progress animated:YES];

} else{
    self.movieDate.hidden = NO;
    self.movieName.hidden = NO;
    self.playButton.hidden = NO;
    self.settingsButton.hidden = NO;
    self.progressLabel.hidden = YES;
    self.progressLine.hidden = YES;
    self.backgroundImage.hidden = NO;
    self.bgView.hidden = NO;
}

And I call this method in cellForRowAtIndexPath like:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"readyCell";
AVMMovieCell  *cell = [self.readyTable dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    // Configure the cell...
if (cell == nil) {
    cell = (AVMMovieCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
}
AVMFilmsStore *oneItem = [readyArray objectAtIndex:indexPath.row];


if (oneItem.ready==1){
 [cell styleForReady:YES andProgress:100];
} else { //если идет сборка
    [cell styleForReady:NO andProgress:[oneItem getProgress]];

}

    return cell;

}

To notify my class with cellForRowAtIndexPath I register NSNotification in viewDidLoad method of this class and then I send notifications in getProgress of AVMFilmsStore.

When my main class get notifications, it calls [tableView reloadData]; method and progressView in each needed cell updates. The problem is that updates of progressView happens ONLY if I scroll my UITableView though dataSource has changed. It continues from my previous question Check progress where is necessary UITableView I am quite confused with this problem. Any suggestions will be helpful. Sorry for so silly question but I have no idea how to solve it.

EDIT This is how I register for NSNotifications in main class:

-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(fsProgressChanged:)
                                             name:@"filmStoreProgressChanged"
                                           object:nil];
}

- (void)fsProgressChanged:(NSNotification *)notification
{

if ([[notification name] isEqualToString:@"filmStoreProgressChanged"]) {
    [readyTable reloadData];
    }
}

And how I send notifications in AVMFilmsStore: I have @property readyProgress in this class and every time I get progress in this method I store progress into the readyProgress and then I do:

-(void)getProgress{
 // some operations
 ...

 if (progress<100){
     if(self.readyProgress!=progress){
      [[NSNotificationCenter defaultCenter] postNotificationName:@"filmStoreProgressChanged" object:nil];
   }
 [self getProgressWithDelay:10];
 }
}
Community
  • 1
  • 1
vendettacore
  • 1,439
  • 1
  • 13
  • 28
  • UITableView reuses the cells, take a look what this method dequeueReusableCellWithIdentifier does. You need to remodel your code on MVC pattern. – Priyatham51 Apr 06 '15 at 05:41
  • This isn't what's causing your problem, but dequeueReusableCellWithIdentifier:forIndexPath: always returns a valid cell, so the if (cell == nil ) clause is not needed. The code you have in there is incorrect, but it's never executed anyway, so just delete it. – rdelmar Apr 06 '15 at 05:53
  • You haven't posted the code that seems to be the problem; show how you register for and respond to the notification, as well as how you post it in your other class. – rdelmar Apr 06 '15 at 05:58

2 Answers2

1

You can use the key-value observing(KVO) method here. For that first you should keep a property/instance of related AVMFilmsStore class in cell.

@property(nonatomic, strong) AVMFilmsStore *filmStore;

Then in filmStore setter method of cell add observer for property,

- (void)setFilmStore:(AVMFilmsStore *)filmStore {

  _filmStore = filmStore;

  [_filmStore addObserver:self forKeyPath:@"ready" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}

Then implement method,

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{

    if([keyPath isEqualToString:@"ready"])
    {
      id oldC = [change objectForKey:NSKeyValueChangeOldKey];
      id newC = [change objectForKey:NSKeyValueChangeNewKey];

      // Handle changes or update cell here
    }
}

And remove observer in dealloc,

- (void)dealloc {

  [_filmStore removeObserver:self forKeyPath:@"ready"];
}

In your viewController,

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  // Cell creation

  cell.filmStore = [readyArray objectAtIndex:indexPath.row];

  return cell;
}
Anusha Kottiyal
  • 3,855
  • 3
  • 28
  • 45
0

I think your problem is that you are starting the whole update process using dispatch_after on a background thread. You have to make sure that when you call the code that will, in fact, update the table, you are on the main thread.

Try doing something like this:

- (void)fsProgressChanged:(NSNotification *)notification
{

    if ([[notification name] isEqualToString:@"filmStoreProgressChanged"]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [readyTable reloadData];
        });
    }
}

EDIT:

I did this test class and I could simulate your error and the fix worked:

class ViewController: UITableViewController {

    private struct Constants {
        static let Notification = "notification"
        static let CellIdentifier = "cell"
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "notificationFired:", name: Constants.Notification, object: nil)
        tableView.reloadData()
    }

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 20
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(Constants.CellIdentifier, forIndexPath: indexPath) as UITableViewCell
        cell.backgroundColor = UIColor.random
        return cell
    }

    func notificationFired(notification: NSNotification) {
        dispatch_async(dispatch_get_main_queue()) { () -> Void in
            self.tableView.reloadData()
        }
    }

    @IBAction func fireNotification(sender: UIBarButtonItem) {
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))

        dispatch_after(time, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) { () -> Void in
            NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notification, object: self)
        }
    }
}

private extension UIColor {
    class var random : UIColor {
        switch arc4random() % 5 {
        case 0: return UIColor.greenColor()
        case 1: return UIColor.blueColor()
        case 2: return UIColor.orangeColor()
        case 3: return UIColor.redColor()
        case 4: return UIColor.purpleColor()
        default: return UIColor.blackColor()
        }
    }
}
Félix Simões
  • 325
  • 2
  • 8