Custom UICollectionViewCell with UIProgressView shows progressView on reused cells - ios

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;
}
}];

Related

Custom UICollectionViewCell not loading

I'm using a storyboard and my custom UICollectionViewCell is not appearing. I played around with it for a few hours and have googled a ton of different solutions but none worked. Just to clarify, the data exists, and the UICollectionView is appearing, but the cell is not. Here is my code. Any suggestions would be appreciated!
- (NSInteger)collectionView:(UICollectionView *)mutualFriendsView numberOfItemsInSection:(NSInteger)section {
return [self.resultsDictionary count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)mutualFriendsView
cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"PCRRequesterMutualFriendsCollectionViewCell";
PCRRequesterMutualFriendsCollectionViewCell *cell = [self.mutualFriendsView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
NSArray *idArray = [self.resultsDictionary objectForKey:#"id"];
NSArray *nameArray = [self.resultsDictionary objectForKey:#"name"];
cell.profileName.text = [nameArray objectAtIndex:indexPath.row];
cell.backgroundColor = [UIColor redColor];
return cell;
}
- (void)collectionView:(UICollectionView *)mutualFriendsView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"cell #%d was selected", indexPath.row);
}
- (BOOL)shouldAutorotate {
[mutualFriendsView.collectionViewLayout invalidateLayout];
BOOL retVal = YES;
return retVal;
}
EDIT Here is my viewDidLoad
self.mutualFriendsView.dataSource = self;
self.mutualFriendsView.delegate = self;
self.mutualFriendsView.pagingEnabled = YES;
// [self.mutualFriendsView registerClass:[PCRMutualFriendsCollectionViewCell class] forCellWithReuseIdentifier:#"PCRMutualFriendsCollectionViewCell"];
Edit I think I figured out the problem. I don't think the dictionary is being populated after the completion block finishes. Any suggestions for saving the value of the dictionary from the block to be used outside of it?
__block NSMutableDictionary *mutualFriends = nil;
__block NSNumber *total;
NSString *u = [NSString stringWithFormat:#"%#",self.details[#"Requester"][#"profile"][#"facebookId"]];
/* make the API call */
[FBRequestConnection startWithGraphPath:(#"/%#", u)
parameters:params
HTTPMethod:#"GET"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
/* handle the result */
if (!error){
NSLog(#"RESULT OF FB %#", result);
if (result == nil){
NSLog(#"No shared friends");
} else {
total = result[#"context"][#"mutual_friends"][#"summary"][#"total_count"];
NSLog(#"TOTAL FRIENDS %#", total);
for(int i=0;i<[result[#"context"][#"mutual_friends"][#"data"] count];i++)
{
mutualFriends = result[#"context"][#"mutual_friends"][#"data"][i];
NSLog(#"FRIENDDATA %#", mutualFriends);
}
}
} else {
NSLog(#"ERROR %#", error);
}
}];
self.resultsDictionary = mutualFriends;
self.number = total;
NSLog(#"NUMBER %#", self.number);
NSLog(#"RESULTS DICTIONARY %#", self.resultsDictionary);
NSString *friends = [NSString stringWithFormat:#"You have %# friends in common including:", self.number];
After this code:
for(int i=0;i<[result[#"context"][#"mutual_friends"][#"data"] count];i++)
{
mutualFriends = result[#"context"][#"mutual_friends"][#"data"][i];
NSLog(#"FRIENDDATA %#", mutualFriends);
}
// add
self.resultsDictionary = mutualFriends;
mutualFriendsView.reloadData();
All within that completion block. So when FB finally does return and you've accumulated all the mutualFriends, then you tell the collectionView to reload.

Slow iOS Share Extension

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);
});
}];

SDWebImage loading issue.!

hello i am loading remote images into my collection view with SDWebImage.
for the first time its loading fine like first showing placeholder image and when image is retreived then change the image of cell... but second time i load the same image the top visible rows are not showing image but when i scroll down to other images, they load fine. then i load back to top, images are there.
In my collectionview class -
- (void)startIconDownload:(TrialImages *)appRecord forIndexPath:(NSIndexPath *)indexPath
{
TrialPicDownloader *iconDownloader = [_imageDownloadsInProgress objectForKey:indexPath];
if (iconDownloader == nil)
{
iconDownloader = [[TrialPicDownloader alloc] init];
iconDownloader.productRecord = appRecord;
[iconDownloader setCompletionHandler:^{
MyCollectionViewCell *cell = (MyCollectionViewCell *)[self.myCollectionView cellForItemAtIndexPath:indexPath];
cell.trialImageView.image = appRecord.trialImage;
[_imageDownloadsInProgress removeObjectForKey:indexPath];
}];
[_imageDownloadsInProgress setObject:iconDownloader forKey:indexPath];
[iconDownloader startDownload];
}
}
this method is in TrialPicDownloader class -
- (void)startDownload
{
[[SDWebImageManager sharedManager] downloadWithURL:
[NSURL URLWithString:self.productRecord.TrialImagesUrl]
options:SDWebImageCacheMemoryOnly
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
if (image != NULL) {
self.productRecord.trialImage = image;
if (self.completionHandler)
self.completionHandler();
}
}];
}
NeverMind i was able to solve my issue by this code ...
- (void)startIconDownload:(TrialImages *)appRecord forIndexPath:(NSIndexPath *)indexPath
{
TrialPicDownloader *iconDownloader = [_imageDownloadsInProgress objectForKey:indexPath];
if (iconDownloader == nil)
{
iconDownloader = [[TrialPicDownloader alloc] init];
iconDownloader.productRecord = appRecord;
[iconDownloader setCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
MyCollectionViewCell *cell = (MyCollectionViewCell *)[self.myCollectionView cellForItemAtIndexPath:indexPath];
cell.trialImageView.image = appRecord.trialImage;
[UIView animateWithDuration:0.3f animations:^{
[self.myCollectionView.collectionViewLayout invalidateLayout];
}];
[_imageDownloadsInProgress removeObjectForKey:indexPath];
});
}];
[_imageDownloadsInProgress setObject:iconDownloader forKey:indexPath];
[iconDownloader startDownload];
}
}
i used dispatch_async to process the completion handler on main queue and now its working great..!!

UITableView cellForRowAtIndexPath is not being called

I am having a problem with blank table data as this method is not getting called after I press search in my UISearchBar. I have tried everything and the table is just showing up blanking after pressing search.
This code takes the description from my json url and scans it for the searched word and then puts the index location of whatever description has that search term into an array (self.indexArray).
I would like to the call the table to only display those cells of the index values in that array.
- (void)viewDidLoad
{
[super viewDidLoad];
UISearchBar *tempSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 64, 320, 40)];
self.searchBar = tempSearchBar;
self.searchBar.delegate = self;
self.searchBar.placeholder = #"Search listings...";
[self.view addSubview:self.searchBar];
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[searchBar resignFirstResponder];
[self sendData];
}
- (void)sendData
{
NSString *search = self.searchBar.text;
NSString *temp = [NSString stringWithFormat:#"MY JSON RETRIEVAL LINK"];
NSURL *url = [[NSURL alloc] initWithString:temp];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation
, id responseObject) {
NSLog(#"%#",responseObject);
NSLog(#"%#",[responseObject class]);
self.images = responseObject;
self.descriptions = [[NSMutableArray alloc] init];
for (int i = 0; i < [self.images count]; i++)
{
[self.descriptions addObject:self.images[i][#"description"]];
}
for (int i = 0; i < [self.descriptions count]; i++)
{
NSLog(#"%d: %#", i, self.descriptions[i]);
if ([self.descriptions[i] rangeOfString:search options:NSCaseInsensitiveSearch].location != NSNotFound)
{
NSLog(#"ADDING, %d", i);
[self.indexArray addObject:[NSNumber numberWithInt:i]];
}
}
tableView = [[UITableView alloc] initWithFrame:CGRectMake(0,104,320,480) style:UITableViewStylePlain];
tableView.dataSource = self;
tableView.delegate = self;
[self.view addSubview:tableView];
[tableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error.localizedDescription);
}];
[operation start];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"FUNCTION CALLED");
TLCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if(cell == nil) {
cell = [[TLCustomCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"Cell"];
}
for(UIView *view in cell.contentView.subviews){
if ([view isKindOfClass:[UIView class]]) {
[view removeFromSuperview];
}
}
NSString *name = self.images[indexPath.row][#"name"];
NSString *location = self.images[indexPath.row][#"location"];
NSString *body = self.images[indexPath.row][#"description"];
NSString *list_type = self.images[indexPath.row][#"category"];
NSString *millisecs = self.images[indexPath.row][#"_createdAt"];
NSDate *date = [NSDate date];
NSTimeInterval ti = [date timeIntervalSince1970];
double myDouble = [millisecs doubleValue];
double delta = (ti * 1000) - myDouble;
NSString *time = [self calculateInterval:delta];
cell.nameLabel.text = name;
cell.locationLabel.text = location;
cell.bodyLabel.text = body;
CGFloat fixedWidth = cell.bodyLabel.frame.size.width;
CGSize newSize = [cell.bodyLabel sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];
CGRect newFrame = cell.bodyLabel.frame;
newFrame.size = CGSizeMake(fmaxf(newSize.width, fixedWidth), newSize.height);
cell.bodyLabel.frame = newFrame;
cell.timeLabel.text = time;
if ([list_type isEqualToString:#"Sell"])
{
UIImageView *thumbnailView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"sell_icon_small.png"]];
thumbnailView.frame = CGRectMake(12, 12, 50., 50.);
[cell addSubview:thumbnailView];
}
else if ([list_type isEqualToString:#"Trade"])
{
UIImageView *thumbnailView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"trade_icon_small.png"]];
thumbnailView.frame = CGRectMake(12, 12, 50., 50.);
[cell addSubview:thumbnailView];
}
else if ([list_type isEqualToString:#"Wanted"])
{
UIImageView *thumbnailView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"want_icon_small.png"]];
thumbnailView.frame = CGRectMake(12, 12, 50., 50.);
[cell addSubview:thumbnailView];
}
else
{
UIImageView *thumbnailView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ad_icon_small.png"]];
thumbnailView.frame = CGRectMake(12, 12, 50., 50.);
[cell addSubview:thumbnailView];
}
NSString *temp = self.images[indexPath.row][#"link"];
if ([temp isEqualToString:#"no_link"])
{
_thereIsAnImage = FALSE;
}
else
{
_thereIsAnImage = TRUE;
}
if (_thereIsAnImage)
{
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:self.images[indexPath.row][#"link"]
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
// progression tracking code
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
cell.imageView.image = image;
cell.imageView.contentMode = UIViewContentModeScaleAspectFill;
cell.imageView.clipsToBounds = YES;
cell.imageView.tag = indexPath.row;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleImageTap:)];
[tap setNumberOfTapsRequired:1];
[tap setNumberOfTouchesRequired:1];
[cell.imageView setUserInteractionEnabled:YES];
[cell.imageView addGestureRecognizer:tap];
}
}];
}
return cell;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.indexArray.count;
}
If you are doing it programmatically then you need to set tableview.delegate = self and tableview.datasource = self in viewDidLoad method. Put a breakpoint in numberOfRowsInSection: to see the number that it's returning to check whether it's more than zero or not.
I would ask that you please check your header file - it should include the following...
#interface YourTableViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UISearchDisplayDelegate>
Investigate how to implement UISearchBarDelegate and UISearchDisplayDelegate methods to manage search function in your (table) view controller. Read the Apple documentation UISearchBarDelegate and UISearchDisplayDelegate.
When you use a UISearchBar, you need a UISearchDisplayController to manage the search results.
If you are not using storyboards, it is important to set the appropriate data source and delegates for instances of both the UITableView and the UISearchDisplayController in your UITableViewController.
Then in your two table view data source methods, you need to provide information to the searchResultsTableView (accessed via self.searchDisplayController.searchResultsTableView) so that it knows how to prepare the search results table view.
For example...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger nofRowsInSection = 0;
if (tableView == self.searchDisplayController.searchResultsTableView) {
nofRowsInSection = self.searchResults.count;
} else {
nofRowsInSection = self.indexArray.count;
}
return nofRowsInSection;
}
...and...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
... <<other code>> ...
NSString *name = nil;
NSString *location = nil;
etc...
if (tableView == self.searchDisplayController.searchResultsTableView) {
name = self.searchResults[indexPath.row][#"name"];
location = self.searchResults[indexPath.row][#"location"];
...etc...
} else {
name = self.images[indexPath.row][#"name"];
location = self.images[indexPath.row][#"location"];
...etc...
}
... <<other code>> ...
}
Note that self.searchResults should be an NSMutableArray and it should contain data for the search results table view, prepared by filtering self.indexArray based on the search bar text.
Hope this helps.
Your code seems OK . check whether table from storeybaord is connected with the table and delegated correctly.
Did you put
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
to the top of you ViewController.m file?
You need to set tableView.delegate = self and tableView.dataSource = self in the viewDidLoad

Using AFNetworking to update a UITableViewCell once a download is complete

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...");
}];
}

Resources