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.
Related
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];
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.
I have a UITableView filled with cells that display file names, and the cells also indicate the upload/download progress of the files. When a file is being moved, its corresponding cell should show a UIActivityIndicatorView in its accessory view. I have a UIActivityIndicatorView set up and ready to go in viewDidLoad, but when I tried to set it as the accessory view for multiple cells, it only shows up in one cell.
-(void)viewDidLoad {
[super viewDidLoad];
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[activityIndicator startAnimating];
}
//...code that detects file changes and calls fileChange
-(void)fileChange {
for (int i = 0; i < [self.cloudNames count]; i++) {
//detect whether file name in array is uploading, downloading, or doing nothing
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (downloading) {
cell.accessoryView = activityIndicator;
//other changes here specific to downloads
} else if (uploading) {
cell.accessoryView = activityIndicator;
//other changes here specific to uploads
} else {
cell.accessoryView = nil;
}
}
}
As I said, the activity indicator only shows in one cell even where there are multiple cells that should show it.
I don't want to set up the UIActivityIndicatorView in the fileChange method (even though it works) because this method is called many times during the upload/download. If the method is called and the activity indicator is set up there, the activity indicator resets in all of the table view cells when the method is called, resulting in glitchy and unsmooth animations, and it causes a huge memory problem.
Any ideas of what to do? Thanks.
Even if you are setting the activity indicator for the cell, you only ONE instance variable for it. The way to do this is to create an indicator for each cell inside tableView:cellForRowAtIndexPath:
You can set a tag for the UIActivityIndicatorView and whenever you want to access it or grab it you can get the cell, and get the indicator view using [cellView viewWithTag:theTag]. No need for instance variables.
If you want to make things even fancier you can subclass UITableViewCell and do whatever you want to do inside your custom cell..
EDIT:
To get the view you can either assign to the accessory view and just get the cells accessoryView:
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
UIActivityIndicatorView *indicator = (UIActivityIndicatorView *) cell.accessoryView;
or you can add the UIActivityIndicatorView to the cell`s contentView (that way you can place it where ever you want, you have more flexibility):
adding the indicator:
myIndicatorView.tag = 1;
[cell.contentView addSubview:myIndicatorView];
getting the indicator:
UIActivityIndicatorView *indicator = [cell.contentView viewWithTag:1];
hope this helps
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;
}
I have what must be a simple problem with EasyTableView (https://github.com/alekseyn/EasyTableView)
I have a number of horizontally scrolling tables that function properly.
I am able to select a cell and perform a segue, however, once the new view controller is dismissed, I am no longer able to select that cell and perform the same action until I have selected another cell in the same table.
My question is: How can I deselect previously selected the cell programmatically to renable this particular action.
Thanks in advance!
The selectedIndexPath is intentionally persistent in case a user scrolls the selected tableview cell offscreen and then back again. If you don't want this persistence please add the line shown below, after the delegate method (in EasyTableView.m):
- (void)setSelectedIndexPath:(NSIndexPath *)indexPath {
if (![_selectedIndexPath isEqual:indexPath]) {
NSIndexPath *oldIndexPath = [_selectedIndexPath copy];
_selectedIndexPath = indexPath;
UITableViewCell *deselectedCell = (UITableViewCell *)[self.tableView cellForRowAtIndexPath:oldIndexPath];
UITableViewCell *selectedCell = (UITableViewCell *)[self.tableView cellForRowAtIndexPath:_selectedIndexPath];
if ([delegate respondsToSelector:#selector(easyTableView:selectedView:atIndexPath:deselectedView:)]) {
UIView *selectedView = [selectedCell viewWithTag:CELL_CONTENT_TAG];
UIView *deselectedView = [deselectedCell viewWithTag:CELL_CONTENT_TAG];
[delegate easyTableView:self
selectedView:selectedView
atIndexPath:_selectedIndexPath
deselectedView:deselectedView];
// Add this line here!
_selectedIndexPath = nil;
}
}
}