ganime
ganime

Reputation: 129

UIProgressView indicator not updating with the correct "progress"

I am downloading images asynchronously and displaying them in a UITableView. While theimage is downloading, a UIProgressView should be displayed in the corresponding table row. After the download is complete, progress view should be replaced by the actual image.

In my table view, I am using a custom cell called ProgressTableViewCell subclassed from UITableViewCell. It has a UIProgressView IBOutlet.

I have created an NSOperation from NSURLConnection and added them to an NSOperationQueue. As the delegate's

didReceiveData

method is called, a notification is posted to my table view controller to update the corresponding table row with

reloadRowsAtIndexPaths

method of table view. My cellForRowAtIndexPath does the following for the reloaded row:

   ProgressTableViewCell *cell = (ProgressTableViewCell*)[tableView dequeueReusableCellWithIdentifier:@"ProgressCell"];

    float received = [[downloadInfo objectForKey:@"receivedBytes"] floatValue];
    float total = [[downloadInfo objectForKey:@"totalFileSize"] floatValue];

    NSNumber* percentage= [NSNumber numberWithFloat:received/total];
    NSMutableDictionary* userInfo = [[NSMutableDictionary alloc] init];
    NSLog(@"percentage %f", percentage.floatValue);
    [userInfo setObject:cell forKey:@"cell"]; 
    [userInfo setObject:percentage forKey:@"percentage"];  

    [self performSelectorOnMainThread:@selector(updateProgressView:) withObject:userInfo waitUntilDone:NO];
    NSLog(@"received: %@", [downloadInfo objectForKey:@"receivedBytes"]);

    NSLog(@"Progress: %f", cell.progressView.progress);
    return cell;

The updateProgressView method looks like

- (void)updateProgressView :(NSMutableDictionary *)userInfo
{
    ProgressTableViewCell* cell = [userInfo valueForKey:@"cell"];

    NSNumber* progress = [userInfo valueForKey:@"percentage"];

   [cell.progressView setProgress:progress.floatValue ];
    NSLog(@"Progress after update: %f", cell.progressView.progress);
}

I am updating the progress view on the main thread and I have even tried setting waitUntilDone to YES but to no avail. My progress view stays at the zero point. Occasionally when I am debugging I can see some change in the progress indicator which makes me think it might be a timing problem. But how to solve it?

EDIT: Here is NSURLConnection delegate's didReceiveData method:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [_responseData appendData:data];
    NSNumber* bytes = [NSNumber numberWithUnsignedInt:[data length]];

    NSLog(@"received bytes:%d", [bytes intValue] );
    NSMutableDictionary* userInfo = [[NSMutableDictionary alloc] init];
    [userInfo setObject:_responseId forKey:@"responseId"];  
    [userInfo setObject:bytes forKey:@"receivedBytes"];

    [self fireNotification: [NSNotification
                                   notificationWithName:@"DidReceiveData"
                                   object:self userInfo:userInfo]];
}



- (void)fireNotification :(NSNotification *)aNotification
{
    [[NSNotificationCenter defaultCenter] postNotification:aNotification];
}

And here is my view controller's method that gets the notification:

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

    NSNumber* responseId = [[notification userInfo] objectForKey:@"responseId"];
    NSNumber*  bytes = [[notification userInfo] objectForKey:@"receivedBytes"];

    NSMutableDictionary* downloadInfo = [self getConnectionInfoForId:responseId];

    NSLog(@"received bytes:%ld for response %@", [bytes longValue], responseId );
    NSNumber* totalBytes = [NSNumber numberWithInt:([bytes longValue] + [[downloadInfo objectForKey:@"receivedBytes"] longValue]) ];
    [downloadInfo setObject:totalBytes forKey:@"receivedBytes"];

    float received = [[downloadInfo objectForKey:@"receivedBytes"] floatValue];
    float total = [[downloadInfo objectForKey:@"totalFileSize"] floatValue];

    [downloadInfo setObject:[NSNumber numberWithFloat:received/total] forKey:@"progress"];

    [self reloadRowForResponse:responseId];

}

I have also added a nil check to my cellForRowAtIndexpath method as recommended:

ProgressTableViewCell *cell = (ProgressTableViewCell*)[tableView dequeueReusableCellWithIdentifier:@"ProgressCell"];
    if (cell == nil)
    {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"ProgressCell" owner:self options:nil];
        cell = [nib objectAtIndex:0];
    }
    float received = [[downloadInfo objectForKey:@"receivedBytes"] floatValue];
    float total = [[downloadInfo objectForKey:@"totalFileSize"] floatValue];

    NSNumber* percentage= [NSNumber numberWithFloat:received/total];
    NSMutableDictionary* userInfo = [[NSMutableDictionary alloc] init];
    NSLog(@"cell:%@", cell);
    NSLog(@"percentage %f", percentage.floatValue);
    [userInfo setObject:cell forKey:@"cell"];  
    [userInfo setObject:percentage forKey:@"percentage"]; 

    [self performSelectorOnMainThread:@selector(updateProgressView:) withObject:userInfo waitUntilDone:NO];

    return cell;

Upvotes: 1

Views: 2737

Answers (3)

ganime
ganime

Reputation: 129

Coming back to this almost 2 months later, I seem to find a solution. Not sure if this is good practice but creating a new UIProgressView each time the progress is updated seems to solve my problem. Here is the method:

-(void)updateProgressView :(NSMutableDictionary *)userInfo
{
    ProgressTableViewCell* cell = [userInfo objectForKey:@"cell"];
    cell.backgroundColor =[UIColor darkGrayColor];
    NSNumber* progress = [userInfo objectForKey:@"percentage"];
    NSLog(@"Progress before update: %f", cell.progressView.progress);

    UIProgressView *pView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
    pView.frame = CGRectMake(150, 200, 150, 9);
    pView.progress = progress.floatValue;

    [cell.contentView addSubview:pView];
}

Many thanks to everyone for their help.

Upvotes: 0

Joe Hankin
Joe Hankin

Reputation: 960

I think you're taking the wrong approach by reloading the table cell every time the delegate method gets called. You can instead just grab the visible cell and update the progress indicator directly, rather than going through the data source.

I'm assuming you have some way of converting responseId to the index path of the row you want to update -- let's say that's called indexPathForResponseID: in your controller. Rather than reloading the cell, you can just grab the cell if it's visible and update its progress indicator:

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

    ...

    float received = [[downloadInfo objectForKey:@"receivedBytes"] floatValue];
    float total = [[downloadInfo objectForKey:@"totalFileSize"] floatValue];

    NSIndexPath *cellPath = [self indexPathForResponseID:responseId];
    ProgressTableViewCell *cell = (ProgressTableviewCell *)[self.tableView cellForRowAtIndexPath:cellPath];
    if (cell) {
        // make sure you're on the main thread to update UI
        dispatch_async(dispatch_get_main_queue(), ^{ 
            [cell.progressView setProgress: (received / total)];
        }
    }
}

You should be aware, though, that this solution won't suffice if you have more downloads than visible cells -- you should also be storing the progress of each download somewhere in your data source so that if the table view DOES need to reload a cell (due to a scroll), it knows how to set the progress indicator.

Upvotes: 3

Owen Hartnett
Owen Hartnett

Reputation: 5935

I've found in many cases like this that the cell you think you have isn't the one that's updating. When you reload, your code is popping a cell off the reusable, which is basically an old cell. If you reload, that cell gets replaced by another. (You also haven't included allocating a new cell if the reusable ones return nil) If your cell scrolls off, it gets reloaded, so you have to make sure that you're putting the progress bar on the cell that's there, not the one that used to be there. You might want to NSLog the address of the cell you started the transfer with, and then again each time you update the progress. You need to update the progress bar with the current cell at that index path. It may not be the one you initially started the process with.

Upvotes: 0

Related Questions