ios 7 - Async Image Download UITableView For Image with Unknown Dimension - ios

I am using XCode 6.3.1 targeting iOS 7.
I am using AFNetworking's UIImageView category to download images with an unknown dimension to UITableViewCell's. Here is a sample image:
The issue I am having is that since the dimensions of the image is unknown, I just use a placeholder image. If the placeholder image has the exact same dimensions, then there is no issue. However, if the dimensions are different, there are issues with spacing in the cell.
If the image is smaller than there will be too much spacing. Here is an example:
I don't know how to refresh the cell after I finish downloading the image so that the spacing is per my Auto Layout Constraints.
If I scroll away from the cell and scroll back, the spacing is fine.
Here is some sample code for the downloading of the image
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Temp
static NSString *Cell = #"CustomListCell";
CustomListCell *cell = (CustomListCell *)[self.tableView dequeueReusableCellWithIdentifier:Cell];
CustomListRow *customListRow = self.customList.rows[indexPath.row];
// Reset the cell
cell.headerImageView.image = nil;
cell.titleLabel.text = #"";
// Download the image, placeholder image is necessary
NSString *topImageURL = #"sample_image";
__weak __typeof(cell)weakCell = cell;
if ([topImageURL isEqualToString:#""] || [topImageURL isEqualToString:#"false"])
{
// Do nothing
} else {
[cell.headerImageView setImageWithName:topImageURL afterManipulation:^UIImage *(UIImage *image) {
// Manipulation
UIImage *newImage = [UIImage expandImage:image toWidth:Constants.screenWidth - 16];
// CustomListCell *updateCell = (CustomListCell *)[tableView cellForRowAtIndexPath:indexPath];
// if (updateCell)
// updateCell.headerImageView.image = newImage;
return newImage;
} placeholderImage:[UIImage expandImage:Constants.placeholderImage toWidth:Constants.screenWidth - 26]];
}
return cell;
}
setImageWithName:afterManipulation:placeholderImage: is a method I made to wrap around AFNetworking's setImageWithURL.... It first checks of the image exists locally before checking two different URL's (absolute and base_url + relative) if the image exists there.
I put the manipulation block in there so that I could call a UIImage category method I created that will scale the image to fit the width of the UITableView (so the only dynamic part is the height).
Here is a list of things I have tried:
Reloading the particular cell
reloading the entire table
[self.tableView beginUpdates] + [self.tableView endUpdates];
Calling [cell setNeedsLayout], or [cell setNeedsDiplay];
setNeedsLayout and setNeedsDisplay didn't do anything once I finished loading the image (I placed it in the afterManipulation block which is called before the image is assigned, and I have also tried placing it after the image is assigned).
Reloading the cell, the table, or beginUpdates causes some really weird behavior to occur. The cells start getting mixed together and some cells have the same picture (which shouldn't happen). I'm not sure what is happening, but my guess is that reloading the cell causes image to download again (or pull it from the cache) which doesn't finish until after another cell is loaded.

Have you thought about playing with the contentMode property of the image view?
A flag used to determine how a view lays out its content when its bounds change.
An issue that I observed while looking at your implementation is that you might get wrong images in the cells. I see you take the cell as a reference when downloading the image. This is wrong, and here is why:
Table view cells get reused so when you scroll the cells that get off screen will be used again to display the information for other rows. By taking a reference to the cell and not the indexpath, if your download takes time, when the completion block is called, that cell may be displaying information for a different row and thus, the image you apply on it may not be the right one.
You should have a look at Apple's example of how to keep consistency while downloading images for every table view cell: https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009394-Intro-DontLinkElementID_2

Related

Images in the collectionView are changing while scrolling

I am working in xcode 8.3. I have a CollectionView. I have downloaded images from web sevice and placed the images in each CollectionView cells. But when i scrolling the CollectionView, images in the each cells are changing. After a few minutes it shows the correct image. I have tried many solutions available in stackoverflow. But i didnt get a solution. Please help me.
Like the others are saying its most likely because you are dequeuing reusable cells (As you should be) and setting the cell.imageView.image property to your web image.
The issue here is that because iOS is saving on memory by "reusing" these cells, they are literally the same cells in memory. So as it scrolls off one edge of the screen and disappears. As the new cell scrolls on instead of creating a new seperate cell it simply uses the one that it already has that just left the screen. Meaning your old image is still the one displayed in the cell.
Standard practice is setting the content of the cell in the cellForRowAtIndexPath: method. But if you are setting it to an image that is fetched asynchronously its entirely possible(likely) for the cell to appear on the screen with the old image before the new one is fetched. Presumably once the images are downloaded its not so much of an issue anymore as they should return instantly from a cache.
The simple fix here would be to either nil out the image before setting it each time in the cell, or preferably use a placeholder image.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CELL_IDENTIFIER" forIndexPath:indexPath];
// This will clear any existing image from the cell about to be displayed it is an option
//[cell.imageView setImage:nil];
if (ImageExistsLocally)
{
cell.imageView.image = [UIImage imageWithContentsOfFile:#"SomeImagePath"];
}
else
{
[cell.cellImageView sd_setImageWithURL:yourRemoteImage.thumbnailUrl
placeholderImage:[UIImage imageNamed:PlaceHolderImageName]
completed:nil];
}
return cell;
}
Note that sd_setImageWithURL is from the SDWebImage library that I think someone else mentioned here. https://cocoapods.org/pods/SDWebImage
It is because of reusing cells. Try to reset the image in your cell class prepareForReuse method
-(void)prepareForReuse {
[super prepareForReuse];
self.imageView.image = nil;
}
The problem you are facing is due to the Reuse of UITableViewCell.
If you are downloading images from web service use AlamofireImage or SDWebImage. It will handle your problem.
UICollectionView reuses the same UICollectionViewCell to improve performance. Only the data inside the UICollectionViewCell is changed, so before using the UICollectionViewCell, the UICollectionViewCell has to be cleared of its previous data. Cocoa Framework provides a method that is present in UICollectionViewCell that triggers every time when the UICollectionViewCell is to be reused.
just override the function given below in the .m file of your custom UICollectionViewCell class file
-(void)prepareForReuse {
[super prepareForReuse];
// here we clear the previously set data
self.imageView.image = nil; // this will clear the previously set imageView is a property of UIImageView used to display a image data
}
You can use prepareForReuse() method to handle this issue.
override func prepareForReuse() {
super.prepareForReuse()
// still visible on screen (window's view hierarchy)
if self.window != nil { return }
imageView.image = nil
}
Notes: If you are using SDWebImage, you should add this line to cancel current cell image download.
imageView.sd_cancelCurrentAnimationImagesLoad()

UITableView & UICollectionView performance optimisation

Hello colleagues Apple developers!
I am creating an iOS application for both iPhone & iPad. The performance of UITableView and UICollectionView is very important feature in my application.
After long time spent for optimising, human eye can't tell the difference while scroll happens. Although, profiler still finds some issues.
The first inefficient thing is dequeuing. I have a UITableViewCell object, which user interface is created by using .xib files and auto layout constraints. Although, the time profiler instrument complains about the performance while dequeuing this specific cell.
One more problem I can't understand is a NSString setting as UILabel's text. This setter method is executed in method - tableView:cellForRowAtIndexPath:.
One more problem, that might be related with previous one, is a UIImage setting as UIImageView's image. This method is also executed in method - tableView:cellForRowAtIndexPath: and doesn't trigger any download or etc.
UITableViewCell objects, described above, are default size & really simple. The content view of the cell contains only 2 subviews: UILabel and UIImageView.
Images are downloaded according to Apple's example.
// Asks the data source for a cell to insert in a particular location of the table view.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Dequeues & initializes category table view cell.
CategoryTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CategoryTableViewCellIdentifier];
APICategory *category = [self.categories objectAtIndex:indexPath.row];
if (category.thumbnail)
{
[cell updateThumbnailWithCategory:category];
}
else
{
if (!tableView.dragging && !tableView.decelerating)
{
[category thumbnailWithSuccess:^(UIImage *thumbnail)
{
if ([tableView.visibleCells containsObject:cell])
{
[cell updateThumbnailWithCategory:category];
}
}
failure:^(NSError *error)
{
// Handle thumbnail receive failure here!
}];
}
[cell updateThumbnailWithPlaceholder];
}
cell.category = [self.categories objectAtIndex:indexPath.row];
return cell;
}
Specific images used in this case are .png images 300x300 resolution and 24 kb of size.
These are the problems related with the performance of UITableView and UICollectionView.
Can anyone explain me, what could be the reasons for any of those issues?
Also, is there a way to improve it?

Show very big size images to the tableview cell

I want to show some images that user saved in his Document directory in a uitableview. I use below simple code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"uploadHistoryCellfa";
BRUploadHistoryCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[BRUploadHistoryCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
//Some Other assignemt to UILables Here...
[cell.imagePreview setImage:[UIImage imageWithContentsOfFile:myImagePathInDocument]];
//Some other configurations here ....
return cell;
}
my question is that:
The images size is about 11-25 MB. and I will load one image at each cell. Does this code that I use potentially can cause memory leak or some thing like this?
11-25 MB is large file actually if you have UITableView you will have more then 5 images so it will become 11-25 (*5) so you should resize images while adding them on tableView, but if you will resize it in proccess of showing it will slow up your app. So you first step is to save resized image which will size of max 1 mb.
Second Step is to generate thumbnail and after that add thumbnail image on cell.
First Link: Resize Image
Second Link: Image Resizing Techniques
imageIO for my practice is fastest way to resize image because it uses CPU and user will not see slow scrolling problem.
After all steps done do not forget to use foreground thread and main thread for representing images on cell.
If only 1-2 cells are shown in screen at a time then it won't cause you much trouble.
As TableView will update the contents of already created cells with new Images. (Deque Reusable Concept)
But the better Approach will be to resize the Images first and then use.

iOS Images display abnormally in UITableView

I'm trying to put some images into a UITableView, so I put an Image View into the UITableViewCell and set it aspect fit.
After starting the simulator, it appears to be right, but when I click at one cell or scroll out the originally hidden cells(may be the reused ones), the image breaks the limit of the image view's setting and destroys the layout.
the middle picture is normal, but the upper or lower one is out of shape.
cellForRowAtIndexPath function as follow:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ShangbaoOriginCell";
ShangbaoOriginCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.newsSampleLabel.text = [NSString stringWithFormat:NSLocalizedString(#"Cell %d",), indexPath.row];
cell.cellView.contentMode = UIViewContentModeScaleAspectFit;
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
cell.imageView.image = self.tableItems[indexPath.row];
return cell;
}
whether cell.imageView.contentMode is set or not, this problem exists.
I've added bg color, ImageView is black, Content View is blue, Cell is green.
So I saw incredible thing, the (table view) cells become not wide enough (you can see from the narrow gap between two pictures). Which is set properly in story board that the cell's width cover the whole screen.
I forgot to say that this table view is in a tabbed application, but I think this doesn't matter . And this time I have added constraints to the image view, but in vain.

Loading images asynchronously, weird problems

There is my question:
i have custom class that parse through an XML and get string i need to use as URL for my strings, now i modified my code as follow:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *labelText = (UILabel *)[cell viewWithTag:1000];
labelText.text = [[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:#"name"];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:#"imageCell"]];
NSData *image = [[NSData alloc] initWithContentsOfURL:imageURL];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = [UIImage imageWithData:image];
});
});
return cell;
}
This is pretty straightforward, but, i got unpredictable errors! During scrolling table, images start to chaotically change, sometimes it show 3 or more images and final image is correct one, sometimes final (correct) image does not appear at all. Also, when table is first shown, its actually blank, so i need to scroll it bottom, and then up again to see my images!
In attempt to fix that, i add following code, to determine is my image link correct for that indexPath:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:#"imageCell"]];
NSLog(#"%#", imageURL);
}
And when i tap to any cell, it does show me proper link in console log, but image on cell is one of the image shown before (invalid), and it is not the image for that link. How to fix that weird errors?
Any advice would be appreciated, thank you.
When you dequeue a cell object, most of the time you'll get a reused cell i.e. a cell that has been configured by tableView:cellForRowAtIndexPath: once or more times before.
To visualise what's happening in your case, consider one likely sequence of events for a single cell as you perform a long, quick scroll:
The cell is created and an image load is spun off in the background
The cell is scrolled off screen, so added to the table view's cache, ready for dequeuing. The image loading is not canceled at this point
The cell is dequeued and an image load is spun off in the background
Steps 2 and 3 are repeated a few times
The cell is visible, but the several image loading tasks are now completing and each is updating the cell's imageView with the loaded image. This will indeed look like the images are chaotically changing as each loading operation finishes.
(What's more, with a concurrent queue, there's no guarantee that the image loads will complete in the order that they're started - you may not end up with the correct final image!)
So what do we do about it? Now that we understand the problem, there are lots of different solutions. A very simple solution (that I don't really recommend) is to check that the cell's label text matches the value for that indexPath, when you come to set the image:
if ([labelText.text isEqualToString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:#"name"]]) {
cell.imageView.image = [UIImage imageWithData:image];
}
Obviously, this assumes that all the place details have unique names.
A better solution might be to create an object that handles the image download, and is something that you can register/unregister cells against to handle download completion. This object could enforce the condition that a cell cannot be waiting for more than one image load. As #Leena pointed out, caching is a good idea and this object could be responsible for that too.
As for the blank images, calling [cell setNeedsLayout] after setting the image should sort that out.
Default property "imageView" will not be added to the cell until its (imageView's) property "image" is nil (you can check cell.imageView.superview will be nil too).
That's why your tableView is blank when it is loaded and all images for cells are also loaded.
So when you scroll it down (or up) cells will be reloaded, their "imageView" will have image data. That is the reason why they are on cell and you can see them.
The other problem is that your images are flashing all the time. It happens because your cells are dequeued.
So, when the image for the first cell is downloaded and setted it will not be shown until you relayout cell's subviews (for example by calling [cell setNeedsLayout];-).
And when you scroll table down, your first cell (with an image now) will be dequeued from "tableView" and will become last cell, and then your first cell will be shown with image that actually belongs to the first row.
At the same time you start downloading image for this (last) row and after it was downloaded you will set it. And this is the monent when flashing happens.

Resources