UICollectionView not reloading cell's imageView - objective-c

I have the below code called within :
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
Unfortunately, when I call [collectionView reloadItemsAtIndexPaths:#[indexPath]]; the imageView within the does not update. If I call reloadData, it does. I'm trying to keep the code as efficient as possible, so I do not want to just call reloadData. Would anyone have some insight on why this isn't working or what else I need to do? I've already had a few other issues with loading up numerous API image queries that conflict with the collectionView's lovely endAnimations stuff. Any help would be appreciated.
SearchResultsCollectionViewCell *searchResultCell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([SearchResultsCollectionViewCell class]) forIndexPath:indexPath];
Product *product = self.products[indexPath.row];
UIImage *productImage = [self.imageCache imageForURL:product.fullImageUrl];
if (productImage) {
searchResultCell.productImageView.image = productImage;
} else {
[self.imageCache downloadImageForURL:product.fullImageUrl indexPath:indexPath withCompletion:^(UIImage *image, NSError *error) {
if (!error) {
searchResultCell.productImageView.image = image;
// [collectionView reloadData];
[collectionView reloadItemsAtIndexPaths:#[indexPath]];
}
}];
UIImage* image = [UIImage imageNamed:#"001_logo"];
searchResultCell.productImageView.image = image;
}

The way you are accessing and storing the images seems odd.
The way I would do this is to create a #property for a UIImage on the productImage.
Then you can ask the product for its image. If the product returns a nil image then put a placeholder in the cell.
When the download is complete you can then reload the cells.
Secondly, I wouldn't try to change the cell in the completion block of the download call you are making.
The completion block should update the model (I.e. Store the image into the product object) and then reload the cell.
The reason for this is that you don't know for certain that the cell you are running the completion block on is (a) the same cell as the one you started it on or (b) it is still displaying the same product.

Related

UITableView fails to load images for first two cells

I have a UITableView in which each custom cell (created in storyboard) contains a UIImageView that is asynchronously populated with an image from a custom object (object.image). Also, the TableView shows different content depending on the state of a UISegmentedControl that has two states (toggling the control triggers reloadData and the cellForRowAtIndexPath method gets data from a different array).
Ordinarily the setup works as expected. However, if I scroll down in the TableView and then toggle the SegmentedControl, the first two cells don't load their images (but the labels in the cells load fine). It appears that the ImageView is occasionally nil. Scrolling down past the cells and then back up causes the image to load normally.
Relevant Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Get the cell
CustomTableViewCell *cell = (CustomTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"CustomTableViewCellIdentifier"];
if (segmentedControlPosition1) {
//Get the object containing all the data.
myObject = [firstObjectsArray objectAtIndex:indexPath.row];
//...Update cell labels...
[cell.myImageView setImage:nil];
//Asynchronously retrieve and conditionally populate the event cover image to optimize rapid scrolling in large table.
//Fetch image data on secondary, asynchronous thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *image = myObject.coverImage;
if (image) {
//Update the UI on the primary thread.
dispatch_async(dispatch_get_main_queue(), ^{
//Verify that the cell is still visible.
if ([[tableView indexPathsForVisibleRows] containsObject:indexPath]) {
//Retrieve the cell (cell may have changed during async processing).
CustomTableViewCell *correctCell = [self.tableView cellForRowAtIndexPath:indexPath];;
[correctCell.myImageView setImage:image];
NSLog(#"Loaded image successfully: %i, %#, %#, %#", indexPath.row, image, correctCell.myImageView, correctCell.myImageView.image);
NSLog(#"%#",);
[correctCell setNeedsLayout];
}
});
}
});
} else {
//...do exactly the same thing just get the data from a different array...
}
}
return cell;
}
My NSLog statement reveals that sometimes, (for instance after scrolling down a bit and then toggling the SegmentedControl), correctCell.myImageView is nil. Again, all of the labels in the same cell populate just fine.

UICollectionView, Images inside a cell does not load until the cell is scrolled off screen

Using Xcode 6 beta 6, I have used a standard View Controller and placed a Collection view into it. Following this, I have added a prototype cell from which an array on the CollectionViewController.m file provides the images.
While the images do appear, they do not load until after that cell has been scrolled off the screen and then back on. This is stopping any images from loading when the app opens until the user scrolls down.
The images are stored locally in the file, not a Database.
Despite the lack of images, I have linked the cells to another view controller, and even though no image is displayed, the segue still operates.
Thanks for any help in advance.
I guess you are setting the UIImage on the cell image view out of the main thread. Wrap the setter code into a following snippet:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = {UIImage object you've got};
}];
Optionally you could also try different, single-line replacement approach, if possible:
[cell.imageView performSelectorOnMainThread:#selector(setImage:) withObject:{UIImage object} waitUntilDone:NO];
I can't find what kind of cell you mean in fact; in every case, apply the same on a particular target image view, no matter if it's a part of your cell as a property or not.
you should use willDisplayCell method to assign image.
first initial image then create cell.
here is the code :
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
return cell;}
-(void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{
UIImageView *imageView = (UIImageView *)[cell viewWithTag:100];
imageView.image = images[indexPath.row];}
And don't forget to reload collection view after your data is ready to be loaded:
[_myCollection reloadData];

Determine when UITableViewCell is deallocated

I am using core data in my app along with NSFetchedResultsController to populate a table. My database has 40k+ entries so the table is rather long. Each table cell has a thumbnail image that is loaded from the web using SDWebImage. All works great if I scroll slowly, if I begin to scroll fast within a couple of seconds I get a crash.
NSZombies isn't showing anything useful.
I'm guessing that it has to do with SDWebImage and loading from the web. The way SDWebImage works is by loading the image in the background then setting the downloaded image after it completes downloading (wordy). My thought is that the cells are being deallocated by the UITableView, then SDWebImage tries to set the image on the deallocated cell. So if I can determine when the UITableViewCell is going to be deallocated I can stop the SDWebImage downloading process and hopefully fix the issue.
I've tried to add
- (void)dealloc {
NSLog(#"dealloc");
}
to catch when the cell is going to be deallocated but I never get anything.
EDIT
I have my -(void)dealloc method in a subclass UITableViewCell.
EDIT
Here is where/how I create the cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* inventoryCellID = #"InventoryCustomCellID";
InventoryCustomCell* cell = (InventoryCustomCell *)[tableView dequeueReusableCellWithIdentifier:inventoryCellID forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(InventoryCustomCell *)cell atIndexPath:(NSIndexPath *)indexPath {
[cell formatCellWithProduct:[fetchedResultsController objectAtIndexPath:indexPath] enableAdding:NO];
cell.openThumbnailButton.tag = indexPath.row;
[cell.openThumbnailButton addTarget:self action:#selector(presentThumbnailViewWithCell:) forControlEvents:UIControlEventTouchUpInside];
}
In my custom cell this is the configuration method being called:
- (void)formatCellWithProduct:(Product*)product enableAdding:(bool)addingEnabled {
self.titleLabel.text = product.part_number;
self.partNumberLabel.text = [[[product.manufacturer allObjects] objectAtIndex:0] name];
//The SDWebImage UIImageView category method
[self.thumbImageView setImageWithURL:[NSURL URLWithString:product.photo] placeholderImage:[UIImage imageNamed:#"icon.png"]];
}
EDIT
Here is the SDWebImage method that downloads the image and sets it.
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock;
{
[self cancelCurrentImageLoad];
self.image = placeholder;
if (url)
{
__weak UIImageView *wself = self;
id<SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
__strong UIImageView *sself = wself;
if (!sself) return;
if (image)
{
sself.image = image;
[sself setNeedsLayout];
}
if (completedBlock && finished)
{
completedBlock(image, error, cacheType);
}
}];
objc_setAssociatedObject(self, &operationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
Table views don't tend to allocate and deallocate table view cells much. Creating cells is expensive, so they get reused when possible, rather than being discarded when they go off screen.
The UITableViewDelegate method -tableView:didEndDisplayingCell:forRowAtIndexPath: is the better place to update cells to cancel downloads or other no-longer-relevant operations.
It does look like each call to -setImageWithURL:etc:etc: is trying to cancel previous downloads for that image view, though.
You didn't explain where did you put dealloc method..
I consider you can try to add a category to your viewcontroller for debugging just for test if your cell's deallocation is called (not tu subclass UITableviewCell) .
For example:
#implementation UITableViewCell(Dealloc)
- (void)dealloc {
NSLog(#"dealloc");
[super dealloc];
}
#end
Do you use ARC? If no than you forgot
InventoryCustomCell* cell = (InventoryCustomCell *)[[tableView dequeueReusableCellWithIdentifier:inventoryCellID forIndexPath:indexPath] autorelease];

MKNetworkKit - displaying cached images

UPDATE AT BOTTOM
I branched off of the MKNetworkKit Flickr demo for this. I have several images on a webserver I want to display in a table. I have a UITableViewCell subclass, ImageCell.
Here is the custom method for retrieving remote images:
-(void)setImageForCell:(NSString *)remoteFileName {
self.loadingImageURLString =
[NSString stringWithFormat:#"http://myserver.com/%#.png",remoteFileName];
self.imageLoadingOperation = [ApplicationDelegate.imageDownloader imageAtURL:[NSURL URLWithString:self.loadingImageURLString]
onCompletion:^(UIImage *fetchedImage, NSURL *url, BOOL isInCache) {
if([self.loadingImageURLString isEqualToString:[url absoluteString]]) {
if (isInCache) {
self.imageView.image = fetchedImage;
[self.contentView drawRect:self.contentView.frame];
NSLog(#"Image is in cache");
} else {
self.imageView.image = fetchedImage;
[self.contentView drawRect:self.contentView.frame];
NSLog(#"Image is not in cache");
}
}
}];
}
//TableView.h
//it is called like this
//in cellForRowAtIndexPath...
ImageCell *cell = (ImageCell *)[tableView dequeue...etc];
MyObject *obj = [dataSource objectAtIndex:indexPath.row];
cell.textLabel.text = obj.name;
[cell setImageForCell:obj.name];
return cell;
I have inspected the contents of my default cache directory, and there are now items inside. Scrolling the table constantly now prints "Image is in cache". The problem is, the cell's imageView never updates. Mugunth has a class method automaticallyNotifiesObserversForKey: but I don't ever see it implemented anywhere. I'm guessing that there's another step involved to tell the tableView instance to update that row with the new contents.
Thanks for your input.
UPDATE
I got this to work by using a custom Interface Builder xib file with a Cell and a UIImageView IBOutlet. Not sure why it wasn't working with self.imageView.image, and would be interested to find out why, exactly. I still consider this an open question because I'd like to just use the standard UITableViewCell class.
I the call to prepareForReuse. Are you setting the self.imageView.image to nil?
If yes, dont do it because it looks like it's never re-initialized.
Why do you call "drawRect" from your code. That's blasphemy!
Inspect your imageView and check if the IB connections are good.

Any simple tutorial to load custom objects asynchronous on uitableview cells while scrolling

I am trying to find a simple tutorial for inserting custom objects asynchronous on uitableview cells while scrolling because my uitableview does not scroll nicely and smoothly. I have searched but i only have found about images asynchronous loading not helpful. I have an uiview that needs to be loaded asynchronous. Too much processing work is needed before the object loads as a result scrolling is not smooth.
Any help appreciated.
This is not as hard as it seems. There is only one caveat. You must know the height of your cell even when it is not fully loaded.
If the tableView has a constant row height, then set tableView.rowHeight. If you need to determine row height on the fly use UITableViewDelegate's –tableView:heightForRowAtIndexPath: callback.
Then in -tableView:cellForRowAtIndexPath dequeue a cell, set it to some initial state, kick off a NSOperation or a GCD block, and finally return the cell that you have reset.
In the NSOperation or CCG block you will perform the work you need, then call back into the main thread to set the values into the cell. This is the essence of async cell loading.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// dequeue a cell
// Reset the cell
cell.imageView.image = nil;
cell.textLabel.text = nil;
cell.detailTextLabel.text = nil;
// Use gcd
dispatch_queue_t queue = dispatch_queue_create("blah blah replace me blah", 0);
dispatch_async(queue, ^{
// Do work in the background
UIImage *image = value1;
NSString *text = value2;
NSString *detailText = value3;
dispatch_async(dispatch_get_main_queue(), ^{
// Back to main thread to set cell properties.
if ([tableView indexPathForCell:cell].row == indexPath.row) {
cell.imageView.image = image;
cell.textLabel.text = text;
cell.detailTextLabel.text = detailText;
}
});//end
});//end
dispatch_release(queue);
// Return the reset cell
return cell;
}