I think this a rather complex question. I have a TableView that displays a number of downloadable content. When you click it on a button within the cell the download starts.
Your issue is you're mixing up your data and your UI. You're relying on each table view cell to store it's current download status which will cause issues. Instead, you should create a custom object that can store the data you need for a cell (i.e. a bool indicating if it's downloading, current download progress, the downloaded data, etc.). You should then have a mutable array of these objects such that objectAtIndex:0
is the data for the first cell in the table and so on.
When a download starts, update the relevant object in the array. Ideally when you create the table view cells you should use the NSKeyValueObserving protocol so they are automatically notified when their underlying data has changed and can update their data appropriately. When a cell is reused that will be fine because the object that stores it's data will still be there, and if it needs to be made again because it's about to come back on screen then it will be able to use that data to display properly.
Let me know if you need any further explanations on any part of this.
It's important to realize that your progress bars will not be shown all the time (i.e. the user can scroll the table, and once offscreen that same cell can be reused at another index position for different content). So what you will need to do is have somewhere you can store the data about any active downloads, including the index position in the table, the total file size, and the number of bytes downloaded so far. Then, whenever your cell is drawn, you'll need to check whether the item for that cell is currently being downloaded and if so, show the bar with the appropriate percentage progress.
The easiest way to do this would be to add a property to your view controller to store this info. It can be an NSMutablerray
that will hold a collection of NSMutableDictionary
objects, each dictionary will contain the necessary info about an active download.
@property (nonatomic, strong) NSMutableArray *activeConnections;
First you'll initialize the array in viewDidLoad:
:
- (void)viewDidLoad
{
[super viewDidLoad];
//...
self.activeConnections = [[NSMutableArray alloc] init];
}
Whenever a button is pressed, you'll add an NSMutableDictionary object to your array with the info you'll need.
- (void)downloadFileWhenPressedButton:(UIButton*)sender
{
// ...
// then create dictionary
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:con forKey:@"connection"]; // save connection so we can reference later
[dict setObject:[NSNumber numberWithInt:[sender.tag]/10] forKey:@"row"]; // this is the row index from your table
[dict setObject:[NSNumber numberWithInt:999] forKey:@"totalFileSize"]; // dummy size, we will update when we know more
[dict setObject:[NSNumber numberWithInt:0] forKey:@"receivedBytes"];
[self.activeConnections addObject:dict];
}
Also we'll create two utility methods so we can find easily retrieve the connection info from our array, using either the connection object itself, or the row index position in the table.
- (NSDictionary*)getConnectionInfo:(NSURLConnection*)connection
{
for (NSDictionary *dict in self.activeConnections) {
if ([dict objectForKey:@"connection"] == connection) {
return dict;
}
}
return nil;
}
- (NSDictionary*)getConnectionInfoForRow:(int)row
{
for (NSDictionary *dict in self.activeConnections) {
if ([[dict objectForKey:@"row"] intValue] == row) {
return dict;
}
}
return nil;
}
When the connection is established, update your dictionary with the expected length
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// ...
NSDictionary *dict = [self getConnectionInfo:connection];
[dict setObject:[NSNumber numberWithInt:response.expectedContentLength] forKey:@"totalFileSize"];
}
As you receive data, you'll update the number of received bytes and tell your tableView to redraw the cell containing the progress bar.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// ...
NSDictionary *dict = [self getConnectionInfo:connection];
NSNumber bytes = [data length] + [[dict objectForKey:@"receivedBytes"] intValue];
[dict setObject:[NSNumber numberWithInt:response.expectedContentLength] forKey:@"receivedBytes"];
int row = [[dict objectForKey:@"row"] intValue];
NSIndexPath *indexPath = [NSIndexPathindexPathForRow:row inSection:0];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
When your connection is done downloading, you should remove the connection from your activeConnections array, and reload the table cell.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// ...
NSDictionary *dict = [self getConnectionInfo:connection];
[self.activeConnections removeObject:dict];
int row = [[dict objectForKey:@"row"] intValue];
NSIndexPath *indexPath = [NSIndexPathindexPathForRow:row inSection:0];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
Finally, in cellForRowAtIndexPath:
you'll need to draw the cell's progress bar based on the info in your activeConnections array.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// ...
// remove any previous buttons or progress bars from this cell
for (UIView *view in [cell.contentView subViews]) {
if ([view isKindOfClass:[UIProgressView class]] || [view isKindOfClass:[UIButton class]]) {
[view removeFromSuperView];
}
}
// look for active connecton for this cell
NSDictionary *dict = [self getConnectionInfoForRow:indexPath.row];
if (dict) {
// there is an active download for this cell, show a progress bar
UIProgressView *dlProgress = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
dlProgress.frame = CGRectMake(cell.frame.size.width-150, 17, 50, 9);
dlProgress.tag = indexPath.row*10+1;
dlProgress.progress = [[dict objectForKey:@"receivedBytes"] intValue] / [[dict objectForKey:@"totalFileSize"] intValue];
[cell.contentView addSubview:dlProgress];
} else {
// no active download, show the download button
UIButton *dl = [UIButton buttonWithType:UIButtonTypeCustom];
dl.tag = indexPath.row*10;
[dl setBackgroundImage:[UIImage imageNamed:@"downloadButton.png"] forState:UIControlStateNormal];
[dl setBackgroundImage:[UIImage imageNamed:@"downloadButtonH.png"] forState:UIControlStateHighlighted];
[dl setFrame:CGRectMake(230.0, (cell.frame.size.height-28)/2, 28, 28)];
[dl addTarget:self action:@selector(downloadFileWhenPressedButton:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:dl];
}
}