Dynamically modifying the height of cells in a UICollectionViewCell in Objective C - ios

I need to make a Pinterest style newsfeed. I have created a collection view with 2 columns. I am downloading the photos async in cellForItemAtIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView*)collectionView cellForItemAtIndexPath:(NSIndexPath*)indexPath {
TFNewsObject *obj = [self.sortedDataSource objectAtIndex:indexPath.row];
[imageView sd_setImageWithURL:url placeholderImage:nil options:SDWebImageRefreshCached
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
obj.tfImage = image;
}];
return cell;
}
The problem is that I need to set the size of the cell in heightForItemAtIndexPath and there the image has not been downloaded yet because it did not enter cellForItemAtIndexPath yet. I am setting a fixed size of 100 for the height.
I need to somehow trigger heightForItemAtIndexPath after I've downloaded the image in cellforItemAtIndexPath.
Another thing that I've tried is downloading the image in heightForItemAtIndexPath synchronous and calculating the height after the image downloaded. In that manner it displays correctly with each cell with a different height depending on the image, but it takes forever to load all the images.
Is there a way to modify the cell's height after I've downloaded the image? Thanks!

Try this
[imageView sd_setImageWithURL:url placeholderImage:nil options:SDWebImageRefreshCached
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
obj.tfImage = image;
collectionView .reloadItems(at: [indexPath])
}];

In -setImageWithURLRequest:..., when the image loads it in a hash table so you should then call -reloadItemsAtIndexPaths: with the current image path. This will reload the cell and thus cause the CollectionView to check what size the cell should be.
Another solution that you can try is to invalidate the collection view layout when the image is downloaded.
- (UICollectionViewCell *)collectionView:(UICollectionView*)collectionView cellForItemAtIndexPath:(NSIndexPath*)indexPath {
TFNewsObject *obj = [self.sortedDataSource objectAtIndex:indexPath.row];
[imageView sd_setImageWithURL:url placeholderImage:nil options:SDWebImageRefreshCached
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
[self.collectionView.collectionViewLayout invalidateLayout];
}];
return cell;
}
After this return a appropriate size in the delegate method:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return /* size */;
}

I got into similar problem while i developed something similar to pinterest layout.
Even we had a lag while downloading images asynchronously and once the download is done refreshing cell by cell will blow away the user and will lead to UI jerks.
The solution which i could suggest is,
For example when the post is created with the image, Calculate the size (width and height) of the image and save it in the DB. So the size of the image is available before the image is downloaded or cached and we could scale the cell to the height of the image and display a default image and when the actual image is cached or downloaded it would silently replace the default image without any UI jerks.
If you are using services like cloudinary then you can avoid the pain of storing the image size (height and width). The cloudinary api will give you the height and width of the image.

Related

How to auto-layout the UIImageview base on its image size after downloading the image via SDWebImage

I built a sample project hosted on GitHub.
There is a table view and its custom cell has a multiple line UILabel and a dynamic image view base on its image size. I built it step by step to verify the auto-layout issue on the custom cell.
In the 3rd sample table view (Table3ViewController) which has the above view structure, I tested it with sample random length text and local images. It succeeds, just like Apple documentation and this thread said.
Actually, all the auto-layout tutorials or examples from google search results are tested base on the local image assets, no SDWebImage usage related, basically.
In Table4ViewController, the UIImageView/UITableViewCell(CustomCell) finished its subview's layout before the SDWebImage set the final image object to UIImageView asynchronously.
Here is my question, how to re-layout the UITableViewCell(CustomCell) after the SDWebImage downloaded the image from the network?
Here are my constraints within CustomCell.
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentView.top);
make.centerX.width.equalTo(self.contentView);
make.bottom.equalTo(imageView.top);
make.height.greaterThanOrEqualTo(#16);
}];
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(label.bottom);
make.bottom.equalTo(self.contentView.bottom);
make.centerX.width.equalTo(self.contentView);
make.height.lessThanOrEqualTo(#200);
}];
I tried to move the implementation to initWithStyle:reuseIdentifier: or updateConstraints or layoutSubviews method, all the results are the same and failed.
Here is the image view assignment within cellForRow without placeholder image.
__weak __typeof(cell) weakCell = cell;
[cell.theImageView sd_setImageWithURL:[NSURL URLWithString:dict.allValues.firstObject]
completed:^(UIImage *_Nullable image, NSError *_Nullable error, SDImageCacheType cacheType, NSURL *_Nullable imageURL) {
__strong __typeof(cell) strongCell = weakCell;
[strongCell setNeedsUpdateConstraints];
[strongCell updateConstraintsIfNeeded];
}];
Assume a network image has size 400x400 points, there is no reserved placeholder image in cell, the update constraint and layout subviews' request will be ready and finished asynchronously successfully. However, the new cell's height won't be recalculated into the contentSize of tableview, the draw process of CustomCell won't be called. It fails.
Here is the image view assignment within cellForRow with placeholder image.
__weak __typeof(cell) weakCell = cell;
[cell.theImageView sd_setImageWithURL:[NSURL URLWithString:dict.allValues.firstObject]
placeholderImage:GetImageWithColor(DarkRandomColor, CGRectGetWidth(tableView.frame), 400)
completed:^(UIImage *_Nullable image, NSError *_Nullable error, SDImageCacheType cacheType, NSURL *_Nullable imageURL) {
__strong __typeof(cell) strongCell = weakCell;
[strongCell setNeedsUpdateConstraints];
[strongCell updateConstraintsIfNeeded];
}];
The final image will be drawn with the same size of reserved placeholder image finally, but I expected the final image's size even specified ratio.
So, how to re-layout the image view's size constraint after downloading the image asynchronously?

Reusable TableViewCell loads an old image

I have a dynamic tableview that can be searched through with a search box, and if I load an image in a cell in the background and the search text changes, the object in the cell may also change, and sometimes an old image that was loading in the background will finish and display when it shouldn't. Not every image is loaded in the same way, but this is the method that causes the problem:
[self.CarImage sd_setImageWithURL:imageURL
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageurl){
[self.CarImage setAlpha:0.0];
[UIImageView animateWithDuration:.5 animations:^{
[self.CarImage setAlpha:1.0];
}];
}];
Any ideas would be appreciated
You need to do two things here
1) in prepareforresue method Cancel current downloading request
2) in prepareforresue method set image to nil
Hope this will fix your issue
Here the issue is, image is loaded from url after you have already reused the cell. You can store the url of the image, each and everytime the cell being reused the url of the images should be stored inside the custom cell class. Then inside the completion block check that the loaded image url and the stored image url are same or not using isEqual: method of NSURL. If they are same then everything is ok, cell is not being reused, otherwise the cell is been used for another row, then just dont set the image.
self.carImgUrl = imageURL;//here carImgUrl is a property which you gonna set everytime when the cell is being reused.
[self.CarImage sd_setImageWithURL:imageURL
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageurl){
if([imageurl isEqual:self.carImgUrl])
{
[self.CarImage setAlpha:0.0];
[UIImageView animateWithDuration:.5 animations:^{
[self.CarImage setAlpha:1.0];
}];
}
}];

Loading images of "Next" TableView Cell Asynchronously

So I am using SDWebImage to load images into the cells.
I want to preload images of the next few cells so that when the user scrolls, the image is already loaded.
Whats the ideal way to do this.
Current code -->
[cell.BGImage sd_setImageWithURL:[NSURL URLWithString:section.imageURL] placeholderImage:[UIImage imageNamed:#"PlaceholderF"]];
Ensuring that the current displayed image is kept at priority loading.
Any suggestions?
You can use SDWebImageDownloader method to download image before your cell load or in other controller. So it will cache image and when your cell load it will show immediately.
[[SDWebImageDownloader sharedDownloader]downloadImageWithURL:nil options:SDWebImageDownloaderContinueInBackground progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
}];
and you can set download image priority in LIFO manner using below code.
[[SDWebImageManager sharedManager].imageDownloader setExecutionOrder:SDWebImageDownloaderLIFOExecutionOrder];
Maybe this will help you.

Some problems with SDWebImage

When I used the method load image, after the image gets downloaded, it does not show until I select or scroll the cell, but if I don't set the placeholderImage to nil, it works well. I don't know what's wrong with. Here's my code:
[cell.imageView sd_setImageWithURL:(my url) placeholderImage:nil];
Some problems with SDWebImage
It's weird, but you can try [cell setNeedsLayout]; after you set the image to reload all the cell views.
I guess you have all the calls inside tableView:cellForRowAtIndexPath:
use completion block to change the image view.
[myImage sd_setImageWithURL:[NSURL URLWithString:url] placeholderImage:[UIImage imageNamed:IMG_PLACEHOLDER] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {}];
use this in your project and try.
Maybe you can use another method to do this, as an example:
[_theImageView sd_setImageWithURL:[NSURL URLWithString:_imageUrl]]

iOS CollectionView: Display View, then download images, then display images

I have a collection view, which contains cells in each section, and each cell has an image in it.
Possibly, the collection view holds 20 or more cells.
I want to load the images off the internet into the cells. The issue is that this can take a few seconds, and I want the collectionView to be displayed even if the images have not downloaded completely.
The collection view is given an array that contains the URLs, and so far, I have been downloading off the internet within collection view cellforitematindexpath
However, the view only becomes visible after all the cells have been loaded, and since each call to collection view cellforitematindexpath downloads an image, if 20 images are pulled off of URLs, it takes way to long.
What can I do to display the view, and THEN download the images, and then display them?
Hope I made myself understandable, if not, please ask!
Thanks!
C
Yes, you could use SDWebImage (as mention at comment above).
Another interesting side you could face out, that if image haven't load yet and user scroll view, it could be problems with dequeueReusableCellWithReuseIdentifier:forIndexPath, so it's better to download images throw id <SDWebImageOperation>.
Prepare for reuse will be something like this:
- (void)prepareForReuse
{
[super prepareForReuse];
if (self.imageOperation)
[self.imageOperation cancel];
self.imageOperation = nil;
}
And load will be like this:
SDWebImageManager *manager = [SDWebImageManager sharedManager];
self.imageOperation = [manager downloadWithURL:[NSURL URLWithString:urlString]
options:SDWebImageRetryFailed
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
if (image)
{
self.imageView.image = image;
}
}];

Resources