UITableViewCell with UIImage scaling performance - ios

The UITableViewCell has UIImage to be displayed within it.
Images are obtained from the server, and stored in NSDictionary as NSData.
This image is resized for non-retina devices using UIImageGraphicsBeginImageContext as listed below.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID=#"Cell";
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellID];
NSDictionary *myDictionary=[self.tableObject objectAtIndex:indexPath.row];
NSData *imgData=[myDictionary objectForKey:#"icon"];
UIImage *img=[UIImage imageWithData:imgData];
if(isRetina]){
cell.iconImageView.image=img;
}else{
CGRect imgRect=CGRectMake(0, 0, img.size.width/2.0, img.size.height/2.0);
UIGraphicsBeginImageContext(imgRect.size);
[img drawInRect:imgRect];
UIImage *newImg=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
cell.iconImageView.image=newImg;
}
}
Would it be better approach and less memory intensive or should store it in the disk, and then access the image from it and assign it to cell.iconImageView.image;

write it to disk when you download it.
that way, os can lazy read the data, cache it and you dont have the image data in memory all the time

Related

Setting image in custom uitableviewcell causes lag

I have a uitableview where I use a custom cell. However, when I scroll the table view there is some serious lag. It happens when I set the UIImaveView's image property with an image. I am accessing an image from the directory. But since file IO is slow I am using the dispatch_async to load the image into a UIImage object on a separate thread.
However there is still some lag. When I scroll up and down the rows without any images, the scrolling is very smooth. However when the row actually has an image, there is lag. the momentum scrolling will halt, then the app becomes unresponsive, then when the image finally loads the momentum continues where it left off.
I am not sure what is causing the lag. At first I thought it had to do with the image being too large so i tried scaling it down. Still lags. Again, if I don't set the image in the custom cell there is no lag. But when I do set it there is lag. I am not sure how to fix this.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
DHTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kReuseIdentifierGoalCell forIndexPath:indexPath];
[self configureCell:cell forIndexPath:indexPath isForOffscreenUse:NO];
return cell;
}
- (void)configureCell:(DHTableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath isForOffscreenUse:(BOOL)offscreenUse {
if (cell == nil) {
return;
}
[cell setDelegate:self];
PATH_TO_FILE = SQLITE_QUERY_TO_GET_PATH; //some pseudo codes
__weak typeof(sSelf)wSelf = sSelf;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong typeof(wSelf)sSelf = wSelf;
UIImage *unscaled_image = [UIImage imageWithContentsOfFile:PATH_TO_FILE];
UIImage *image = [unscaled_image imageScaledToFitInSize:kCellUIImageSize];
__weak typeof(sSelf)wSelf = sSelf;
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(wSelf)sSelf = wSelf;
DHTableViewCell *cell = (id)[sSelf.tableView cellForRowAtIndexPath:indexPath];
if (cell) {
[cell.imageStored setImage:image]; //Commenting this out relieves all lag
}
});
});
}
#Calimari328 I hate if to put this as an answer because it does not really answer your question but the truth is what you should really do is use a library to achieve this. for example SDWebImage
Example:
#import <SDWebImage/UIImageView+WebCache.h>
...
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.textLabel.text = #"My Text";
return cell;
}
You have to rethink this, cells get reused, that means each time you scroll your app will try to download images again.What about performance? memory ? cache ? processing?
As you think more about it is a lot more complex then a simple async task. Fortunately there are opensource projects to achieve this. No need to reinvent the wheel.
Please note I am not advertising any library if you want to write your own code you can do this as well. You can also search on the web for easy ones to implement.

Async image loading from url inside a UITableView cell - wrong image while scrolling

I had an IPhone application in which i am loading images asynchronously from an array ,using this in cellforrowatindexpath,
` - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
sCell *cell = (sCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[sCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
NSMutableDictionary *dicttable=[sarray objectAtIndex:indexPath.section];
NSString *icon= [dicttable objectForKey:#"thumb_image"];
cell.image.image = [UIImage imageNamed:#"thumbnail-default.png"];
if(icon.length!=0)
{
if(![[ImageCache sharedImageCache] hasImageWithKey:icon])
{ cell.image.image = [UIImage imageNamed:#"thumbnail-default.png"];
NSArray *myArray = [NSArray arrayWithObjects:cell.image,icon,#"thumbnail-default.png",[NSNumber numberWithBool:NO],nil];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate performSelectorInBackground:#selector(updateImageViewInBackground:) withObject:myArray];
}
else
{
cell.image.image = [[ImageCache sharedImageCache] getImagefromCacheOrUrl:icon];
}
}`
and all working fine,Because i enabled paging i need to call the request to load the next set of values, like this
`- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.section==[sarray count]-1)
{
[self performSelectorInBackground:#selector(loadingfeedcntents:) withObject:count];
}
}`
but here the problem is when i am scrolling the table view wrong images are assigned to the image views in the custom cell,Can anybody help me on this ?
This is most probably due the reuse of the cells. Probably what is happening is that you trigger an asynchronous request to download an image, while that is happening you scroll the table an that cell is reused so when the image finishes downloading that new cell gets the image instead of the one originally requested it.
I think you will have to find another way to do it or detach that request from the cell on prepareForReuse
Many people is recommending this class "SDWebImage" for that situation (although I haven't test it)
I have been using SDWebImage for last 2 years and have used in 5 apps, Really easy and I would say it is the best way to load images in background with caching.
Please give a try. You will love it.
A nice tutorial is here.

iOS: crash in collectionview reoloaddata with UIImage

In my app I have this code:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return images.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"gallerycell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
UIImageView *backImageCell = (UIImageView*)[cell viewWithTag:100];
[backImageCell setImage:[images objectAtIndex:indexPath.item]];
if([indexPath row] == ((NSIndexPath*)[[collectionView indexPathsForVisibleItems] lastObject]).row){
[activity_view stopAnimating];
[UIView animateWithDuration:0.8 animations:^{
back.alpha = 0;
}];
}
return cell;
}
the array images contains UIImage of 200x150 size, and their dimension in kb is about 42kb, a normal array of UIImage.
When I reload data for this collectionview I have a memory warning after 15 image... is there a way (as a thread) to don't have a memory warning?
Don't store Images to Array, that's not a good practice. As the number of images or size of images increase it'll throw memory warnings and crash.
Alternatives:
Store image names in array
Store file path in array
Store image url in array and use Async methods to load the image to your UITableView or UICollectionView
In addition to resizing the images, you can nil out the image when the cell scrolls out of view - just implement:
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
Also, a trick to maintain a cache that will not swamp the system is to do as Midhun suggested in the comments - use imageWithContentsOfFile. Then create a NSCache, and stuff the images in it using the image name or some other identifying key. When you need an image, if its in the cache, pull it out. If not you can read it from the file system again. iOS will purge a NSCache if it needs more memory.

Large Images Crashing UITableView app

So I've been searching for an answer to this and still am unable to figure it out. I have an array that contains 15 images or so I'm trying to display using a subview in a UITableViewCell. Code below - everything I've read mentions using autorelease/release to fix the issue, but I simply get ARC errors when attempting to do that. Any help would be much appreciated.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
int countthis = indexPath.row;
NSString *image = imgarr[countthis];
UIImageView *iv = [[UIImageView alloc] initWithFrame:(CGRect){.size={320, tableView.rowHeight}}];
iv.image = [UIImage imageNamed:image];
cell.imageView.image = iv.image;
return cell;
}
Large files tend to cause problems, no matter what your domain. Specifically, Apple says:
You should avoid creating UIImage objects that are greater than 1024 x 1024 in size.
It looks like you're trying to resize the image, but UIImages are immutable. So your allocated UIImageView serves only to waste processor cycles.
If you're stuck with big images that need to be scaled down, consider scaling before you assign them to the cell. You might find these routines useful: The simplest way to resize an UIImage?
Re autorelease/release: those have been deprecated since ARC. Your code doesn't appear to be leaking memory. I wouldn't sweat it. But you should edit your question to include details about the crash.
Your code can be cleaned up to this, which may help slightly with performance. You don't need to convert the indexPath.row to an int, since it's already a NSInteger, which is an architecture dependent type (int for 32-bit, long for 64-bit). You also probably want to use self.imgarr since its probably a property in your class. The image changes are as Neal mentioned.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSString *image = self.imgarr[indexPath.row];
cell.imageView.image = [UIImage imageNamed:image];
return cell;
}
As for autorelease/release, you mentioned you're getting ARC errors using them, which indicates you're on the iOS 5 or higher SDK. They are no longer needed in your code.
You can try to resize images using CGImageSourceCreateThumbnailAtIndexfirst before displaying them in the tableview.
If you have the path to the image you want to resize, you can use this:
- (void)resizeImageAtPath:(NSString *)imagePath {
// Create the image source (from path)
CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef) [NSURL fileURLWithPath:imagePath], NULL);
// To create image source from UIImage, use this
// NSData* pngData = UIImagePNGRepresentation(image);
// CGImageSourceRef src = CGImageSourceCreateWithData((CFDataRef)pngData, NULL);
// Create thumbnail options
CFDictionaryRef options = (__bridge CFDictionaryRef) #{
(id) kCGImageSourceCreateThumbnailWithTransform : #YES,
(id) kCGImageSourceCreateThumbnailFromImageAlways : #YES,
(id) kCGImageSourceThumbnailMaxPixelSize : #(640)
};
// Generate the thumbnail
CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src, 0, options);
CFRelease(src);
// Write the thumbnail at path
CGImageWriteToFile(thumbnail, imagePath);
}
More details here.

Binding UICollectionView with rest api data issue

I am trying to grab images from external API and bind it to my UICollectionView & UIImageView cell with in that View. I am able to get the data and print it in the log file. However, I am not able to see the images on my UICollectionView. Here is the code to my data bindings.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ImagesViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
// imagesArray is an array with serialized json data.
NSDictionary *finalImages = [self.imagesArray objectAtIndex:indexPath.row];
NSLog(#"Entering collection view.....");
[[cell imageViewCell]setImage:[UIImage imageNamed:[finalImages valueForKey:#"link"]]];
return cell;
}
The is data is coming in JSON format.
data
{
abc: 'abc',
xyz: 'xyx',
link: 'link to an online image'
}
imageNamed is a method to get image from a image file in your bundle (image.png).
In the case you want to get image from URL, download it as Mr Richhard Brown answer or use SDWebImage(set directly with imageView)
Sample colectionView SDWebImage code(nonARC): https://github.com/lequysang/github_zip/blob/master/CollectionViewNonARC.zip
UIImage imageNamed: takes a filename, not a URL.
You need to manually load the image file into an NSData object before you can display it.
NSData dataWithContentsOfURL will do this for you.
UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:[finalImages valueForKey:#"link"]]];

Resources