Table View loading performance issue - performance

I have a UITableView with seven rows each containing text and a .png image. When I add the code to set the image for each row and run the app in the simulator I experience a long loading time.
I suspect that the cause of the performance issue is the fact that my image sizes are too big and I am in the process of scaling my images to the appropriate pixel dimensions. However, I wanted to ensure there is nothing that I could be doing differently in my code to further optimize performance. Is there anything I should be doing differently in my code?
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *c = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (!c)
{
c = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"Cell"];
}
//adds text to table view
[[c textLabel] setText:[categoryNames objectAtIndex:[indexPath row]]];
[[c textLabel] setTextColor:[UIColor whiteColor]];
//add pictures to table view
NSString *path = [[NSBundle mainBundle] pathForResource:[categoryURLs objectAtIndex:[indexPath row]] ofType:#"png"];
[[c imageView] setImage:[UIImage imageWithContentsOfFile:path]];
return c;
}

Problem solved by resizing images to 44x44 px (standard UITableViewCell height) and 88x88 for the #2x retina images.

Related

Memory not being released for UICollectionView of large images

I have an NSMutableArray of 15 elements, all of them strings. The strings refer to an image name that is stored locally in the app (images approximately 640x640 each). I am displaying the array of images in a UICollectionView.
When I reload the collectionView to display the images, my memory usage shoots up as you would expect displaying large png's (though its footprint is much larger than I expected).
The real problem is that memory used by these images is never freed. So despite me removing all objects from the array, removing the collectionView and setting everything to nil, none of the memory is freed.
Why is this? I would expect my memory to return to it's original level if I removed these objects and removed the ViewController.
UPDATED: Code snippet
ViewController.m
#implementation ViewController
static NSString *CellIdentifier = #"photoCell";
-(void)viewDidLoad{
[super viewDidLoad];
self.photoCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0,0, self.view.frame.size.width, 320) collectionViewLayout:self.photoSpringFlowLayout];
[self.photoCollectionView setDataSource:self];
[self.photoCollectionView setDelegate:self];
[self.view addSubview:self.photoCollectionView];
[self.photoCollectionView registerClass:[PhotoPreviewCell class] forCellWithReuseIdentifier:CellIdentifier];
self.imagesAray = [[NSMutableArray alloc] initWithObjects:#"photo1", #"photo2", #"photo3", #"photo4", #"photo5", #"photo6", #"photo7", #"photo8", #"photo9", #"photo10", #"photo11", #"photo12", #"photo13", #"photo14", #"photo15", nil];
[self.photoCollectionView reloadData];
}
#pragma mark - UICollectionView Methods
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return self.imagesArray.count;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
PhotoPreviewCell *cell = (PhotoPreviewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
NSString *imageName = self.imagesArray[indexPath.row];
[cell.photoImage setImage:[UIImage imageNamed:imageName]];
return cell;
}
PhotoPreviewCell.m
#implementation PhotoPreviewCell
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self){
self.photoImage = [[UIImageView alloc] init];
self.photoImage.frame = CGRectMake(0, 0, 160, 160);
self.photoImage.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:self.photoImage];
}
return self;
}
Ok so the reason for this is that loading images from flash takes some time and OS tries to avoid loading it on and on, so it's caching loaded images. They're cached only when you're using imageNamed: method. If you're testing on simulator release all objects and try simulate memory warning. This should force OS to remove not used images from cache.

Attributed text in TableView performance issue

iOS7 introduced a wonderful "letterpress" text effect that applied via AttributedText to UILabel texts. I need to have that effect in cells of simple table.
Unfortunately being rendered in standard way it caused significant scrolling lags in compare to "textLabel.text" assignments.
Seems attributed text render is very CPU expensive, but iOS embedded apps like Notes scroll without lags. Is it possible to improve render performance?
Below is code sample:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = (MyCell*)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil)
{
cell = [[MyCell alloc] init];
}
NSDictionary *attrs = #{NSTextEffectAttributeName : NSTextEffectLetterpressStyle};
NSString *text = [self textWithCell:indexPath];
//next line causes lags
textLabel.attributedText = [[NSMutableAttributedString alloc] initWithString:text attributes:attrs];
//this works fine
//cell.textLabel.text = text;
}

Memory Warning in UICollectionView (with image)

I am currently working on a UICollectionView with a lot of images. However, it sometimes crashes in this view with memory warning. I am using AFNetworking and UIImageView+AFNetworking category to set image through setImageWithURL: method. One issue can be caching. I am not sure if AFNetworking deals with image caching. Anyway, is there a way to optimize this code in terms of memory management? Or if I am to implement didReceiveMemoryWarning method in this view controller, what can be put in this method? I attach the code for cellForItemAtIndexPath for this collection view.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"RecipeCell" forIndexPath:indexPath];
// setting the image view for the cell using AFNetworking. Does this do caching automatically?
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:6];
if (PRODUCTION) {
[recipeImageView setImageWithURL:[[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_image"] placeholderImage:[UIImage imageNamed:#"default_recipe_picture.png"]];
} else {
[recipeImageView setImageWithURL:[NSString stringWithFormat:#"http://localhost:5000/%#", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_image"]] placeholderImage:[UIImage imageNamed:#"default_recipe_picture.png"]];
}
// configure the back of the cell. fill all the info.
UITextView *recipeNameView = (UITextView *)[cell viewWithTag:8];
recipeNameView.text = [NSString stringWithFormat:#"%#", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_name"]];
UILabel *recipeNameLabel = (UILabel *)[cell viewWithTag:2];
recipeNameLabel.text = [NSString stringWithFormat:#"%#", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_name"]];
NSDictionary *user = [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"user"];
UIButton *chefNameButton = (UIButton *)[cell viewWithTag:3];
[chefNameButton setTitle:[NSString stringWithFormat:#"%# %#", [user objectForKey:#"first_name"], [user objectForKey:#"last_name"]] forState:UIControlStateNormal];
NSMutableArray *missingIngredientsStringArray = [[NSMutableArray alloc] init];
NSArray *missingIngredients = [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"missing_ingredients"];
for (NSDictionary *missingIngredient in missingIngredients) {
[missingIngredientsStringArray addObject:[missingIngredient objectForKey:#"name"]];
}
NSString *missingIngredientsString = [missingIngredientsStringArray componentsJoinedByString:#","];
UITextView *missingIngredientsView = (UITextView *)[cell viewWithTag:4];
missingIngredientsView.text = [NSString stringWithFormat:#"%u Missing Ingredients: %#", missingIngredients.count, missingIngredientsString];
// configure the front of the cell. chef name button and missing ingredients and likes on front view
UIButton *frontNameButton = (UIButton *)[cell viewWithTag:11];
[frontNameButton setTitle:[NSString stringWithFormat:#"%# %#", [user objectForKey:#"first_name"], [user objectForKey:#"last_name"]] forState:UIControlStateNormal];
[frontNameButton sizeToFit];
frontNameButton.frame = CGRectMake(160 - [frontNameButton.titleLabel.text sizeWithFont:[UIFont boldSystemFontOfSize:13]].width - 7, frontNameButton.frame.origin.y, frontNameButton.frame.size.width, frontNameButton.frame.size.height);
UILabel *likesLabel = (UILabel *)[cell viewWithTag:9];
likesLabel.text = [NSString stringWithFormat:#"%# likes", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"likes"]];
UIButton *missingIngredientsButton = (UIButton *)[cell viewWithTag:12];
[missingIngredientsButton setBackgroundImage:[UIImage imageNamed:#"badge_green.png"] forState:UIControlStateSelected];
if (missingIngredients.count == 0) {
missingIngredientsButton.selected = YES;
[missingIngredientsButton setTitle:#"" forState:UIControlStateNormal];
} else {
missingIngredientsButton.selected = NO;
[missingIngredientsButton setTitle:[NSString stringWithFormat:#"%u", missingIngredients.count] forState:UIControlStateNormal];
}
// make back view invisible.
UIView *backView = [cell viewWithTag:1];
UIView *frontView = [cell viewWithTag:5];
frontView.alpha = 1.0;
backView.alpha = 0;
// adding flip gesture recognizers
UIView *flipView1 = [cell viewWithTag:12];
UIView *flipView2 = [cell viewWithTag:1];
UITapGestureRecognizer *flipGestureRecognizer1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(flipCell:)];
UITapGestureRecognizer *flipGestureRecognizer2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(flipCell:)];
[flipView1 addGestureRecognizer:flipGestureRecognizer1];
[flipView2 addGestureRecognizer:flipGestureRecognizer2];
return cell;
}
[Edit] I attach a screenshot of my Instruments run.
You can see that memory allocation increases as I just push segue and press back button repeatedly. Things that just keep increasing are CFData, CALayer, CABackingStore, UITableView. I doubt these are things that are created after segue, and they are not being released... Please help!
You're probably going to want some sort of image caching strategy to avoid re-downloading images. And UIImageView+AFNetworking category does cache images for you. But you may also have the responses being cached in the in-memory URL cache, which in this case is somewhat redundant.
So you might consider reducing or turning off the in-memory URL cache. I had the issue you're describing and the following reduced my memory issues quite a bit:
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
AFNetworking automatically stores images in an NSCache collection, which automatically removes some or all of the images from memory on a low memory warning. AFNetworking is probably not your issue.
In fact, I don't think displaying images is your issue unless you're downloading lots of very large images and displaying them simultaneously. (If this is the case, you should try optimizing your images for display on the device so they don't need to be resized.)
One issue I see is that you are adding a gesture recognizer to the cell every time it comes into the view, but cells are reused, so when a cell comes in again you are adding unnecessary gesture recognizers to it. You could resolve this by subclassing UITableViewCell and assigning the gesture recognizers as properties. You could also resolve this by checking flipView1 and flipView2 to see if they have gesture recognizers attached before adding them. (I'm not sure if this is enough to cause a memory warning though.)
I'd recommend going to Build -> Profile and selecting the Allocations instrument. On the left, select Objective C only, and hide system calls. Then, scroll through your collection view and look at the instrument to see what's taking up all the memory.
UPDATE
Here's a screenshot of the Allocations tool:

Table view not working smoothly with SDWebImage

Why isn’t my table view not working smoothly? I use SDWebImage to set the image from a URL. I'm trying to draw 3 images in each row of the table view.
It works fine when arrList is small, but when I increase arrList, it becomes slower and slower (look like a little vibration when I scroll the table view).
This is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CustomCell";
CustomCellTable *cell = (CustomCellTable*) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray * nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCellTable" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
[cell setBackgroundColor:brgColor];
// item 1
if ((indexPath.row*3) > arrList.count-1) return cell;
vod *Item = [arrList objectAtIndex:(indexPath.row*3)];
[cell.image1 setHidden:NO];
[cell.image1 setImageWithURL:[NSURL URLWithString:Item.imageURl] placeholderImage:[UIImage imageNamed:#"poster_default.png"]];
[cell makeRating:[Item.rating floatValue] positionOf:14];
// item 2
if ((indexPath.row*3 +1) > arrList.count-1) return cell;
Item = [arrList objectAtIndex:(indexPath.row*3+1)];
[cell.image2 setHidden:NO];
[cell.image2 setImageWithURL:[NSURL URLWithString:Item.imageURl] placeholderImage:[UIImage imageNamed:#"poster_default.png"]];
[cell makeRating:[Item.rating floatValue] positionOf:118];
// item 3
if ((indexPath.row*3 +2) > arrList.count-1) return cell;
Item = [arrList objectAtIndex:(indexPath.row*3+2)];
[cell.image3 setHidden:NO];
[cell.image3 setImageWithURL:[NSURL URLWithString:Item.imageURl] placeholderImage:[UIImage imageNamed:#"poster_default.png"]];
[cell makeRating:[Item.rating floatValue] positionOf:225];
return cell;
}
Look into down sampling the images so you're not storing/loading the large versions of the images while scrolling.
https://github.com/toptierlabs/ImageCacheResize
This library combines SDWebImage with UIImage+Resize. Using it instead of just SDWebImage might help. Specifically use the following method to load the images:
- (void)setImageWithURL:(NSURL *)url andResize:(CGSize)size withContentMode:(UIViewContentMode)mode;

UIImage doesn't show unless i click on my table cell

I've spent some time writing an xcode application today. I'm using a UITableView with a seperate xib for the table view cell. All is working well besides one quirky thing. In my code I set my image on the table cell xib from an array. When i run the app I've found out that the image does not appear on the table cell until I click on the table cell. And when I click on a different cell, the image on my previously selected cell disappears.
Funny thing is that I set my label on the table cell xib exactly the same way however I don't get the issue with the label.
Here is my code. I would greatly appreciate some help.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = nil;
cell = [tbHealthCheck dequeueReusableCellWithIdentifier:#"CONTENT"]; //all the cells in this table are named content (identifier for all cells in table)
if (cell == nil){
[[NSBundle mainBundle] loadNibNamed:#"FirstViewTableCell" owner:self options:nil];
cell = tcHealthCheck;
tcHealthCheck = nil;
}
UILabel *l1 = (UILabel *) [cell viewWithTag:0];
[l1 setText:[healthCheckCategory objectAtIndex:indexPath.row]];
l1.font = [UIFont fontWithName:#"arial" size:14];
[l1 setTextColor:[UIColor blackColor]];
UIImageView *img1 = (UIImageView *) [cell viewWithTag:1];
NSString *status = [healthCheckStatus objectAtIndex:indexPath.row];
if ([status isEqualToString:#"RED"])
{
img1.image = [UIImage imageNamed:#"red-light-badge.png"];
}
else if ([status isEqualToString:#"YELLOW"])
{
img1.image = [UIImage imageNamed:#"yellow-light-badge.png"];
}
else if ([status isEqualToString:#"GREEN"])
{
img1.image = [UIImage imageNamed:#"green-light-badge.png"];
}
return cell;
}

Resources