Within our app we have a free magazine that users can download in PDF format. If they have not downloaded an issue, that UITableViewCell image has a low alpha so that the user can see that it is not downloaded.
If you tap a cell it will start to download using an AFHTTPRequestOperation and once complete you can view the PDF using QuickLook.
The problem I am having is, when the user initiates the download, then scrolls away and then back, the UITableViewCell that they tapped somehow loses reference that it was downloading and therefore doesn't update the UIProgressView or change the alpha to 1.0 when the download is finished. I cannot for the life of me figure out why [[tableView indexPathForCell:cell] isEqual:indexPath] is not equaling:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
PUCViewpointItem *item = [self.items objectAtIndex:indexPath.row];
// Check to see if we are already on the row that is activated
if (indexPath.row == self.selectedIndexPath.row) {
// File Path
NSString *path = [self itemPath:item];
// Should we read the issue
if (item.isDownloaded && path) {
item.downloadPath = path;
[self readIssue:item];
return;
}
// TableView Cell
PUCViewpointTableViewCell *cell = (PUCViewpointTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
if (!path) {
Utility *utility = [[Utility alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:item.url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSString *localPath = [[utility localDirectory] stringByAppendingFormat:#"/%#.pdf", item.name];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:localPath append:NO];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float totalProgress = (float)totalBytesRead/totalBytesExpectedToRead;
if ([[tableView indexPathForCell:cell] isEqual:indexPath]) {
cell.progressView.hidden = NO;
cell.progressView.progress = totalProgress;
item.isDownloading = YES;
item.isDownloaded = NO;
item.progress = totalProgress;
}
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([[tableView indexPathForCell:cell] isEqual:indexPath]) {
cell.fullImage.alpha = 1.0f;
cell.progressView.hidden = YES;
item.isDownloaded = YES;
item.isDownloading = NO;
}
NSLog(#"%d == %d", [tableView indexPathForCell:cell].row, indexPath.row);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if ([[tableView indexPathForCell:cell] isEqual:indexPath]) {
cell.progressView.hidden = YES;
item.isDownloading = NO;
item.isDownloaded = NO;
}
}];
[operation start];
}
return;
}
NSIndexPath *oldIndexPath = self.selectedIndexPath;
self.selectedIndexPath = indexPath;
[tableView beginUpdates];
[tableView endUpdates];
// Which way are we scrolling?
UITableViewScrollPosition position;
if (indexPath.row == 0 || (oldIndexPath && oldIndexPath.row < indexPath.row)) {
position = UITableViewScrollPositionTop;
} else {
position = UITableViewScrollPositionBottom;
}
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:position animated:YES];
}
If I start a download, then scroll down in my tableView, my NSLog statement will log something like 3 == 10 which makes no sense.
Any ideas how I can fix this?
I was curious about your case so I wrote a little test project, you can find it here: https://github.com/mrojas/MRTableViewTest
Basically, the way to solve it was:
Put the logic to download an item, in the item class
Make the cell be the delegate of the item, to be notified about progress/completion
When cells are scrolled (reused), setting the item on them is enough. They figure the current status and set themselves to be delegates.
Check the project, try it, and let me know if you have doubts.
I didn't use AFNetworking but instead simulated some task that takes 10 seconds to complete, in 2 seconds interval.
I think you have to store somewhere (like an NSMutableArray instance variable) the indexPath you are downloading. so you can do something like that :
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
if( [indexArray indexOfObject:indexPath] != NSNotFound )
{
// alpha for downloading
}
}
In your AFNetworking completion blocks you should remove this indexPath from you indexArray.
As comments said, cells are not reliable for storing any kind of information as they are reallocated when you scroll
#interface ViewController ()
{
NSMutableArray *_indexArray;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UITableView *table = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain];
table.delegate = self;
table.dataSource = self;
_indexArray = [#[] mutableCopy];
[self.view addSubview:table];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
return 50;
}
// called when you scroll to new cells or when reloadData is called
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"default"];
cell.textLabel.textColor = [UIColor blueColor];
// red color if I'm downloading, else blue
if ([_indexArray indexOfObject:indexPath] != NSNotFound) {
cell.textLabel.textColor = [UIColor redColor];
}
cell.textLabel.text = #"cell in the table";
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
{
[_indexArray addObject:[indexPath copy]];
NSLog(#"downloading...");
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.textLabel.textColor = [UIColor redColor]; // update UI temporary (until scroll)
// HD Image so I can scroll up and down for testing
NSString *res = #"http://res.cloudinary.com/******/image/upload/*****/Motorola_Razr_HD_Cam_Sample_7_ftzrj0.jpg";
// custom class for download
[[PLSApi api] downloadDataAtURL:[NSURL URLWithString:res] withBlock:^(NSData *data) {
// download complete
[_indexArray removeObject:indexPath];
cell.textLabel.textColor = [UIColor blueColor]; // update UI
NSLog(#"finished...");
}];
}
Related
I started an iOS project and I'm working with UITableView to display a list of pilots with images . I did pagination on my api and I tried to load more once you scrolled the tableview. the problem that I got is that the new cells are always displayed on top of the tableview not in the bottom. Please check on my code if there is a solution I will be grateful
- (void)loadData :(NSInteger)page {
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
url = [NSURL URLWithString:[NSString stringWithFormat:#"%#%#%#%ld",NSLocalizedString(#"get_pilots",nil),mainDelegate.idAccount,#"?page=",(long)page]];
task = [restObject GET:url :mainDelegate.token completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSMutableDictionary* jsonResponse = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:nil];
NSArray *pilotKey = [jsonResponse objectForKey:#"pilot"];
for (NSDictionary *pilotItem in pilotKey ){
PilotObject *pilotObj = [PilotObject new];
[pilotObj getPilot:pilotObj :pilotItem];
[_pilotsAll addObject:pilotObj];
}
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
[self checkTableView:_pilotsDisplay :self.view];
[viewPilots.tableViewPilots reloadData];
});
}];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (currentPage == totalPages) {
return [_pilotsDisplay count];
}
return [_pilotsDisplay count] + 1;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == [_pilotsDisplay count] - 1 && currentPage<totalPages ) {
[self loadData:++currentPage];
NSLog(#"current page : = %ld",(long)currentPage);
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == [_pilotsDisplay count]) {
static NSString *identifier = #"PilotCellTableViewCell";
PilotCellTableViewCell *cell = (PilotCellTableViewCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
cell.hidden=YES;
UIActivityIndicatorView *activityIndicator = (UIActivityIndicatorView *)[cell.contentView viewWithTag:100];
[activityIndicator startAnimating];
return cell;
} else {
PilotObject *pilotObjDisplay = nil;
pilotObjDisplay = [_pilotsDisplay objectAtIndex:[_pilotsDisplay count]-1-indexPath.row];
static NSString *identifier = #"PilotCellTableViewCell";
PilotCellTableViewCell *cell = (PilotCellTableViewCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
cell.hidden=NO;
cell.image.image = pilotObjDisplay.imageDisplayPilot;
cell.titleLabel.text = pilotObjDisplay.firstName;
cell.subTitleLabel.text = pilotObjDisplay.lastName;
cell.backgroundColor = [UIColor colorWithHexString:NSLocalizedString(#"gray_background", nil)];
return cell;
}
return nil;
}
Why you are taking 2 array _pilotsDisplay and _pilotsAll ?
If not necessary then you can also do pagination using one NSMutableArray which you can use in both cases while fetching data from server as well as while filling data to UITableView.
Remember one thing only initialise your NSMutableArray in viewDidLoad method. And when you received new data use addObject method of NSMutableArray which you are already using. And then call reloadData method of UITableView.
And in cellForRowAtIndexPath don't use calculation like [_pilotsDisplay count]-1-indexPath.row, simply use indexPath.row.
Here, inserting rows to the tableview may help you.
[tableView beginUpdates];
NSArray *paths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:[dataArray count]-1 inSection:1]];
[[self tableView] insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationTop];
[tableView endUpdates];
You shouldn't add cells to a tableview. what you should do is add data to the tableview's datasource (in your case, _pilotsDisplay) and then simply reload the table. If you want the new data to appear at bottom or in any particular order, you should do that to your datasource (the array).
I'm using UITableViewController for displaying data from Parse. It runs perfectly on my Xcode Simulator as i think there's no latency in network. But whenever i'm uploading the code to AppStore for Testing. The very first time i run the app it has to load a couple of restaurant's from Parse and display in UITableViewController. Upon clicking a row the first rows data is being loaded into the 3rd row and 4th row data loading in 6th row data irregularly. Why is the data being loaded very unevenly ? Here's my
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *cellIdentifier = #"restaurantIdentifier";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
PFObject *tempObject = [self.objectArray objectAtIndex:indexPath.row];
PFFile *imageFile = [tempObject objectForKey:#"RestaurantIcon"];
PFImageView *imageView = [[PFImageView alloc] init];
imageView.file = imageFile;
[imageView loadInBackground:^(UIImage *img,NSError *error){
if(!error){
cell.imageCell.image = imageView.image;
}
}];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = 4;
cell.imageView.frame = self.view.bounds;
cell.cellLabel.text = [tempObject objectForKey:#"RestaurantName"];
[self.hotelNamesArray addObject:[tempObject objectForKey:#"RestaurantName"]];
cell.cellLabel.lineBreakMode = NSLineBreakByWordWrapping;
return cell;
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
_restaurantName = [self.hotelNamesArray objectAtIndex:indexPath.row];
self.restaurantMenuNameArray = [[NSMutableArray alloc] init];
PFQuery *query = [PFQuery queryWithClassName:[self.hotelNamesArray objectAtIndex:indexPath.row]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *obj in objects) {
if (![_restaurantMenuNameArray containsObject:[obj objectForKey:#"RestaurantMenuName"]]) {
NSLog(#"restaurantmenunames are %#",[obj objectForKey:#"RestaurantMenuName"]);
if ([obj objectForKey:#"RestaurantMenuName"] ==nil) {
[self performSegueWithIdentifier:#"restaurantDetail" sender:self];
return;
}else {
[_restaurantMenuNameArray addObject: [obj objectForKey:#"RestaurantMenuName"]];
}
}
}
}else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
[self.tableView reloadData];
NSLog(#"restaurantMenuNames is %#",_restaurantMenuNameArray);
[self performSegueWithIdentifier:#"restaurantDetail" sender:self];
}];
}
Thanks in advance.
If you mean the images get in the wrong cell, you have to consider that cells are recycled when you scroll, and that if the image loading takes a bit too long, you may get the result after the cell has been reused.
You need to check that the cell is still for the item/row you want (you could store the row in the cell's tag and check it before setting the image in the completion handler, for instance).
If it's other data that is mixed up, then you'll need to show us the code that loads that data.
I'm working on a sharing extension to simply grab a link, choose a few names to share it to, and Share. The data layer isn't added yet, only the UI to display some names in a tableview (using a custom cell) and I'm pulling in the shared URL from the extension context. All of the code in the VC is below. All views are set up in the Storyboard. Two UIButtons, Two UILabels, One TableView and a UIView to hold it all, so I can easily round the corners.
The issue I'm having is that the _linkLabel that I'm using the display the URL doesn't visually update for nearly 10 seconds! What.In.The.World. What I'm a doing here that's causing this?
I'm logging out the URL in the callback from hasItemConformingToTypeIdentifier and it happens as soon as the extension appears, but doesn't update the label??!! Helps. Please.
#import "ShareViewController.h"
#import "UserCell.h"
#interface ShareViewController ()
#end
#implementation ShareViewController
- (void)viewDidLoad{
self.view.alpha = 0;
_friends = [#[#"Ronnie",#"Bobby",#"Ricky",#"Mike"] mutableCopy];
_containerView.layer.cornerRadius = 6.f;
_selectedIndexPaths = [[NSMutableArray alloc] init];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[UIView animateWithDuration:0.5 animations:^{
self.view.alpha = 1;
}];
}
- (void)viewDidAppear:(BOOL)animated{
//pull the URL out
NSExtensionItem *item = self.extensionContext.inputItems[0];
NSItemProvider *provider = item.attachments[0];
if ([provider hasItemConformingToTypeIdentifier:#"public.url"]) {
[provider loadItemForTypeIdentifier:#"public.url" options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
NSURL *url = (NSURL*)item;
_linkLabel.text = url.absoluteString;
NSLog(#"Link: %#", url.absoluteString);
}];
}
else{
NSLog(#"No Link");
}
}
#pragma mark - UITableView Delegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UserCell *cell = (UserCell*)[tableView cellForRowAtIndexPath:indexPath];
if([_selectedIndexPaths containsObject:indexPath]){
[_selectedIndexPaths removeObject:indexPath];
cell.selected = NO;
}
else{
cell.selected = YES;
[_selectedIndexPaths addObject:indexPath];
}
NSLog(#"Share to %i friends", (int)[_selectedIndexPaths count]);
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
//Later, calc height based on text in comment
return 44;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [_friends count];
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"UserCell";
UserCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil){
cell = [[UserCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.selected = ([_selectedIndexPaths containsObject:indexPath]) ? YES : NO;
cell.nameLabel.text = [_friends objectAtIndex:indexPath.row];
return cell;
}
- (IBAction)dismiss {
[UIView animateWithDuration:0.34 animations:^{
self.view.alpha = 0;
} completion:^(BOOL finished) {
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];
}];
}
#end
Delays in updates to UI elements is a classic sign of trying to update the UI from outside the main queue. Which is what is happening here. You have this:
[provider loadItemForTypeIdentifier:#"public.url" options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
NSURL *url = (NSURL*)item;
_linkLabel.text = url.absoluteString;
NSLog(#"Link: %#", url.absoluteString);
}];
Except that NSItemProvider does not guarantee that the completion handler will be called on the same queue that you started on. You're almost guaranteed to be on a different queue here, so you're getting this weird delay. You need to dispatch back to the main queue to perform the update:
[provider loadItemForTypeIdentifier:#"public.url" options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *url = (NSURL*)item;
_linkLabel.text = url.absoluteString;
NSLog(#"Link: %#", url.absoluteString);
});
}];
I tried searching but did not find any solutions helpful.
I am using the following code for icons lazy loading.
The issue is, the icons are downloaded via lazy loading, but they are only seen once that particular cell is out of screen and is scrolled back into the screen.
I think it is some issue with dequeueReusableCellWithIdentifier but am not sure how to resolve it.
The images are downloaded alright, but are only visible in the cell once the cell goes out of screen.
// -------------------------------------------------------------------------------
// tableView:cellForRowAtIndexPath:
// -------------------------------------------------------------------------------
- (UITableViewCell *)tableView:(UITableView *)tableVw cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// customize the appearance of table view cells
//
static NSString *CellIdentifier = #"LazyTableCell";
static NSString *PlaceholderCellIdentifier = #"PlaceholderCell";
// add a placeholder cell while waiting on table data
NSUInteger nodeCount = [dataArray count];
if (nodeCount == 0 && indexPath.row == 0)
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier];
cell.detailTextLabel.text = #"Loading…";
return cell;
}
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
cell.backgroundColor = [UIColor grayColor];
// Leave cells empty if there's no data yet
if (nodeCount > 0)
{
// Set up the cell...
AppRecord *appRecord = [dataArray objectAtIndex:indexPath.row];
cell.textLabel.text = appRecord.appName;
cell.detailTextLabel.text = appRecord.artist;
// Only load cached images; defer new downloads until scrolling ends
if (!appRecord.appIcon)
{
if (tableView.dragging == NO && tableView.decelerating == NO)
{
[self startIconDownload:appRecord forIndexPath:indexPath];
}
// if a download is deferred or in progress, return a placeholder image
cell.imageView.image = [UIImage imageNamed:#"Placeholder.png"];
}
else
{
cell.imageView.image = appRecord.appIcon;
}
}
return cell;
}
- (void)startIconDownload:(AppRecord *)appRecord forIndexPath:(NSIndexPath *)indexPath
{
IconDownloader *iconDownloader = [imageDownloadsInProgress objectForKey:indexPath];
if (iconDownloader == nil)
{
iconDownloader = [[IconDownloader alloc] init];
iconDownloader.appRecord = appRecord;
[iconDownloader setCompletionHandler:^{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
// Display the newly loaded image
cell.imageView.image = appRecord.appIcon;
// Remove the IconDownloader from the in progress list.
// This will result in it being deallocated.
[imageDownloadsInProgress removeObjectForKey:indexPath];
}];
[imageDownloadsInProgress setObject:iconDownloader forKey:indexPath];
[iconDownloader startDownload];
}
}
- (void)loadImagesForOnscreenRows
{
if ([dataArray count] > 0)
{
NSArray *visiblePaths = [tableView indexPathsForVisibleRows];
for (NSIndexPath *indexPath in visiblePaths)
{
AppRecord *appRecord = [dataArray objectAtIndex:indexPath.row];
if (!appRecord.appIcon)
// Avoid the app icon download if the app already has an icon
{
[self startIconDownload:appRecord forIndexPath:indexPath];
}
}
}
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate)
{
[self loadImagesForOnscreenRows];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self loadImagesForOnscreenRows];
}
I did code like following,
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:aURL
options:0
progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
[aCell.imgViewThumb setImage:image];
else
[aCell.imgViewThumb setImage:[UIImage imageNamed:#"Dummy-image.jpg"]];
[aCell.indicator stopAnimating];
}];
I am subclassing UICollectionViewCell because I wanted to add a UIImageView and a UIProgressView to the cell:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// ImageView
_imageView = [[UIImageView alloc] initWithFrame:self.bounds];
_imageView.layer.borderColor = [UIColor whiteColor].CGColor;
_imageView.layer.borderWidth = 0.7;
[self.contentView addSubview:_imageView];
// Progress View
_progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
_progressView.frame = CGRectMake(5, self.bounds.size.height - 20, self.bounds.size.width - 10, 10);
_progressView.hidden = YES;
[self.contentView addSubview:_progressView];
}
return self;
}
When I touch a cell and it calls collectionView:didSelectItemAtIndexPath: I set cell.progressView.hidden = NO; and start my download and updating of the progressView.
But, as I scroll that cell is reused and the progressView is shown on other cells. I have tried a number of different things to only show it on the correct cell, but nothing I have tried is working.
Is there a better way to do this such as doing something in prepareForReuse?
EDIT: Full methods as requested
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
PUCViewpointItem *item = [self.items objectAtIndex:indexPath.row];
PUCImageGridCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CollectionViewCellIdentifier forIndexPath:indexPath];
[cell.imageView setImageWithURL:[NSURL URLWithString:item.imageURLHigh] placeholderImage:[UIImage imageNamed:#"PUCDefaultBackground.png"]];
// See if we have the file already
if (![self.itemPaths objectForKey:item.name]) {
cell.imageView.alpha = 0.4;
} else {
cell.imageView.alpha = 1.0;
}
// See if we are downloading
if (![self.progressItems objectForKey:item.name]) {
cell.progressView.hidden = YES;
} else {
cell.progressView.hidden = NO;
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
PUCViewpointItem *item = [self.items objectAtIndex:indexPath.row];
PUCImageGridCell *cell = (PUCImageGridCell *)[collectionView cellForItemAtIndexPath:indexPath];
// File Path
NSString *path = [self itemPath:item];
if (!path) {
// Set the indexPath we are downloading
[self.progressItems setObject:indexPath forKey:item.name];
Utility *utility = [[Utility alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:item.url]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSString *localPath = [[utility localDirectory] stringByAppendingFormat:#"/%#.pdf", item.name];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:localPath append:NO];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float totalProgress = (float)totalBytesRead/(float)totalBytesExpectedToRead;
if ([[collectionView indexPathForCell:cell] isEqual:indexPath]) {
cell.progressView.alpha = 1.0;
cell.progressView.progress = totalProgress;
}
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// This section seems to not get called or updated correctly when the cell
// that is showing the activityView is offscreen
if ([[collectionView indexPathForCell:cell] isEqual:indexPath]) {
[self.itemPaths setObject:localPath forKey:item.name];
[self.progressItems removeObjectForKey:item.name];
cell.imageView.alpha = 1.0;
cell.progressView.alpha = 0.0;
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
[self.progressItems removeObjectForKey:item.name];
cell.progressView.alpha = 0.0;
}];
[operation start];
} else {
[self readIssueAtPath:path];
}
}//end
The information whether a download is in progress for a certain item or not should be stored
in some data source (or model) and not in the cell (the view).
Then you can update the cell's appearance in the data source delegate method collectionView:cellForItemAtIndexPath: according to the status of the item at that index
path and show or hide the progress view of the cell.
ADDED: The progress, completion and failure block all capture the current cell.
Therefore they will modify this cell even if it has been reused for a different index path.
To solve that, you can check if the cell's (current) index path is still equal
to the original (captured) index path:
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float totalProgress = (float)totalBytesRead/(float)totalBytesExpectedToRead;
if ([[collectionView indexPathForCell:cell] isEqual:indexPath]) {
cell.progressView.alpha = 1.0;
cell.progressView.progress = totalProgress;
}
}];
and similar for the completion and failure block:
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.itemPaths setObject:localPath forKey:item.name];
[self.progressItems removeObjectForKey:item.name];
if ([[collectionView indexPathForCell:cell] isEqual:indexPath]) {
cell.imageView.alpha = 1.0;
cell.progressView.alpha = 0.0;
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
[self.progressItems removeObjectForKey:item.name];
if ([[collectionView indexPathForCell:cell] isEqual:indexPath]) {
cell.progressView.alpha = 0.0;
}
}];
I think what you need to do is only refer to the cell once at the beginning of your didSelectRowAtIndexPath method, and create a property to keep track of the selected indexPath and the value of the progress indicator. The method below works, but I had to create a dummy slow method to test it out, so I hope you can see how to adapt this to your problem.
#interface TableController ()
#property (strong,nonatomic) NSArray *theData;
#property (strong,nonatomic) NSIndexPath *downloadingCellPath;
#property (nonatomic) float progValue;
#end
#implementation TableController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RDCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.label.text = self.theData[indexPath.row];
if ([self.downloadingCellPath isEqual:indexPath]) {
cell.progView.hidden = NO;
}else{
cell.progView.hidden = YES;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
self.downloadingCellPath = indexPath;
[self.tableView reloadData];
[self longMethod:indexPath];
}
-(void)longMethod:(NSIndexPath *) indexPath {
UIProgressView *activeProgressView = [(RDCell *)[self.tableView cellForRowAtIndexPath:indexPath] progView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
float x = 0;
for (int i=0; i < 40000000; i++) {
x = i * 3.14;
}
dispatch_async(dispatch_get_main_queue(), ^{
self.progValue +=.01;
activeProgressView.progress = self.progValue;
if (self.progValue < .99){
[self longMethod:indexPath];
}else{
NSLog(#"done");
self.downloadingCellPath = nil;
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
});
});
}
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float totalProgress = (float)totalBytesRead/(float)totalBytesExpectedToRead;
if ([[collectionView indexPathForCell:cell] isEqual:indexPath]) {
cell.progressView.alpha = 1.0;
cell.progressView.progress = totalProgress;
}
}];