UICollectionView poor scrolling performance with simple UICollectionViewCell - scroll

I'm using UICollectionView to show grid based View in iPad. I have only 200 (20rows x 10 columns) cells at a time on screen, each cell contain one UILabel only. It starts to get slower on scrolling and not smooth like in TableView.
I don't know the reason for this slower scrolling performance. How do I improve?
Thanks in advance!
I have 3 collectionviews, but the main content uicollectionview is the one which has been tagged 2. Here is my code
if (collectionView.tag==0) { //This tagged collection view is for Left side selection box
LeftSelectionBoxItem *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"SelectionCell" forIndexPath:indexPath];
return cell;
}
else if (collectionView.tag==1){ //This tagged collection view is for to show column names
TopColumnItem *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"ColumnCell" forIndexPath:indexPath];
cell.columnName.text = [NSString stringWithFormat:#"%#",[self.columnNames objectAtIndex:indexPath.item]];
return cell;
}
else if (collectionView.tag==2) { //This tagged collection view is for to show main content
GridItem *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"GridCell" forIndexPath:indexPath];
cell.dataLabel.text = [NSString stringWithFormat:#"%#",[[self.dataModel objectAtIndex:indexPath.section] objectForKey:[self.columnNames objectAtIndex:indexPath.item]]];
return cell;
}
else {
return nil;
}

Having a lot of UILabels is sometimes expensive. Try changing them to CATextLayers instead, as these have much less overhead. Note that if you do this and use rasterizing, you will need to tell the layer to rasterize for retina display devices too.

Related

Width of UITableViewCell

I've project which is based on storyboard and on one of the views I've placed UITableView with UIViewTableViewCell (style Right Detail).
In Xcode everything looks OK, but when I launch application on simulator or device, the the table cell looks too wide.
Do you have any suggestions what could be reason of this strange behavior? What should I change to get cell be full visible?
EDIT:
I'm attaching cellforRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyTableCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
Have you put the same width for your custom cell, as the width of a UITableView?
Also, have you managed your "Delete" Label size properly (using autolayout or autoresizing).
With new iPhone 6, we have to pay attention to widtch as well, just like with different heights, when iPhone 5 was introduced.
And I can see from those two screenshots you posted that you develop using iPhone6 sized ViewController and running it on iPhone5.
Im prety sure you didn't manage Autolayout / Autoresizing as you should.
Try doing this, for your "Delete" label:
I had the same problem where the content on the right hand side of my tableView was being cut off.
The reason being, I had set the Autolayout constraints correctly for the views inside my custom tableViewCell. However, I had not set the AutoLayoutConstraints for the tableView itself. Therefore the UITableView wasn't being constrained correctly and in my case was too large.

Efficiently handling UIImage loading in UICollectionView

In my app the user can take a photo of their pet, or whatever, and that will show in the collection view as a circular picture. Using cellForItemAtIndexPath to check for the existence of a user generated photo (which is stored in the Documents directory as a png file), if not display a default photo.
Issues:
- When Scrolling the UICollectionView the image load halts the
smoothness of the scroll UI as the picture is going to appear on
screen.
- If you scroll the picture off the screen many times the
app crashes due to memory pressure.
QUESTION: Is there an efficient way of loading and keeping the images in memory without having to keep loading the image after the first time it is displayed?
I thought rasterizing the images would help, but it seems the UI scrolling and memory issues persist.
Here is the code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifierPortrait = #"allPetCellsPortrait";
AllPetsCell *cell = (AllPetsCell *)[collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifierPortrait forIndexPath:indexPath];
CGRect rect = CGRectMake(cell.petView.center.x, cell.petView.center.y, cell.petView.frame.size.width, cell.petView.frame.size.height);
UIImageView *iv = [[UIImageView alloc] initWithFrame:rect];
//set real image here
NSString *petNameImage = [NSString stringWithFormat:#"%#.png", self.petNames[indexPath.section][indexPath.item]];
NSString *filePath = [_ad documentsPathForFileName:petNameImage];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:filePath];
iv.image = nil;
if (fileExists){
[_allPetsCollectionView performBatchUpdates:^{
if (iv && fileExists) {
iv.image = [UIImage imageWithContentsOfFile:filePath];
}
} completion:^(BOOL finished) {
//after
}];
//loadedImage = YES;
[collectionView layoutIfNeeded];
}//file exists
else{
iv.image = [UIImage imageNamed:[NSString stringWithFormat:#"%#", self.contents[indexPath.section][indexPath.item]]];
[cell.petNameLabel setTextColor:[UIColor darkGrayColor]];//be aware of color over pictures
shadow.shadowColor = [UIColor whiteColor];
}
cell.petView.clipsToBounds = YES;
UIColor *color = MediumPastelGreenColor;
if ([[NSString stringWithFormat:#"%#", self.contents[indexPath.section][indexPath.item]] isEqualToString:#"addpet.png"]) {
color = MediumLightSkyBlueColor;
}
[cell.petView setBounds:rect forBorderColor:color];
[cell.petView addSubview:iv];
if (![[NSString stringWithFormat:#"%#", self.contents[indexPath.section][indexPath.item]] isEqualToString:#"addpet.png"])
[iv addSubview:cell.petNameLabel];
//TODO: optimize the cell by rasterizing it?
if ([_contents count] > 4) {
cell.layer.shouldRasterize = YES;
}
return cell;
}
Here is a screen shot:
Thanks!
Assigning an image to the collection view cell's imageView doesn't cost you any performance issues at all..
There are 2 problems here.
You are fetching the contents (datasource for the collection view) from within the collection view's delegate method. This is causing the delay.
You are adding the imageView holding the image again and again. Thus, each time the cell is displayed, a new imageView holding a newly fetched data is being added as the subView to the reused cell (As the new imageView covers the old one as both have the same frame, you wouldn't notice. And at the sametime this eats up memory). This is causing the memory pressure crash.
SOLUTION
FOR IMPROVING PERFORMANCE
Fetch the image contents from your model class(if you are using one), or from you viewController's viewDidLoad() (You should never fetch datasource from within the collection view's delegate method). Perform the fetching process in the background queue as it wouldn't affect your UI in any manner.
Store the contents onto an array and fetch the contents only from the array to populate your collection view cells.
FOR AVOIDING MEMORY CRASH
As long as you are not using a custom collection view cell, its better to remove all the subviews from the collectionViewCell's content view each time the cell is dequeued.
I hope this solves your problem :)
I recently discovered NSCache which is a dictionary type object that I can store my *already loaded image*s into and check to see if they are loaded with the -cellForItemAtIndexPath method.
I changed the above code to accommodate for this also by using a custom dispatch queue that would be sent to the main queue. This still causes a slight pause bump in the scrolling when the initial image is loaded but after that it is read from the image cache. Not totally optimal yet but I think I am headed in the right direction.
here is the code:
if (fileExists){
//DONE: implement NSCACHE and dispatch queue
const char *petImageQ = "pet image";
dispatch_queue_t petImageQueue = dispatch_queue_create( petImageQ, NULL);
//check to see if image exsists in cache - if not add it.
if ([_petImagesCache objectForKey:petNameImage] == nil) {
dispatch_async(petImageQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
[collectionView performBatchUpdates:^{
iv.image = [UIImage imageWithContentsOfFile:filePath];
} completion:^(BOOL finished) {
//add to cache
[_petImagesCache setObject:iv.image forKey:petNameImage];
}];
});
});
}
else{
//load from cache
iv.image = [_petImagesCache objectForKey:petNameImage];
}
I hope this helps. Please add more to this for improving it!

Programmatically created UICollectionView: cells overlap after scrolling

I'm trying to implement a custom UICollectionView that shows all elements from the asset library. While displaying the items is not that big of a deal, I run into a weird bug which I think has to do something with the layout I'm using.
Let's start with some screenshots to indicate what's going on. I'll just post the links to these images to be easy on your scroll wheels :-)
1. Initial
https://dl.dropboxusercontent.com/u/607872/stackoverflow/01-uicollectionview-start.png
Everything ok.
2. Scrolled down
https://dl.dropboxusercontent.com/u/607872/stackoverflow/02-uicollectionview-scrolleddown.png
The images from the first row (which isn't visible anymore) repeat behind the images in the last row. The first one is invisible because it is entirely overlapped.
3. Scrolled up again
https://dl.dropboxusercontent.com/u/607872/stackoverflow/03-uicollectionview-scrollup.png
The first image (in the background) is from the same row, but should be the third. The 2nd and 3rd image are from the last row.
Here's some code. I hope I didn't miss any relevant stuff - if so, please let me know.
MultiImagePickerController.m (:UICollectionViewController, protocols: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout)
- (void)loadView {
[...] // layout stuff
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
[layout setScrollDirection:UICollectionViewScrollDirectionVertical];
_collectionView = [[UICollectionView alloc] initWithFrame:collectionRect collectionViewLayout:layout];
[_collectionView setDataSource:self];
[_collectionView setDelegate:self];
[_collectionView registerClass:[MultiImagePickerCell class] forCellWithReuseIdentifier:#"MultiImagePickerCellIdentifier"];
[_collectionView setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:_collectionView];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MultiImagePickerCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MultiImagePickerCellIdentifier" forIndexPath:indexPath];
if ( !cell ) {
CGSize sizeCell = [MultiImagePickerCell defaultSize];
cell = [[MultiImagePickerCell alloc] initWithFrame:CGRectMake( 0, 0, sizeCell.width, sizeCell.height )];
}
NSString *groupname = [self getKeyFromMutableDictionary:assetList forIndex:indexPath.section];
[cell addImageUsingAsset:[[assetList objectForKey:groupname] objectAtIndex:indexPath.row]];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGSize sizeCell = [MultiImagePickerCell defaultSize];
return CGSizeMake( sizeCell.width + 10.0, sizeCell.height + 10.0 );
}
MultiImagePickerCell - (void)addImageUsingAsset:(ALAsset *)asset;
This method adds a UIImageView and a UIImage to the cell to show the asset. Nothing special, in my opinion. When I set breakpoints in cellForItemAtIndexPath, I can see that the correct asset is being read from the assetList and the views are calculated correctly. The drawRect method of the cell is NOT implemented.
I think I experienced the same problem a few years back when I tried to programmatically create a UITableViewController, but I can't remember how I solved it.
Any suggestions?
Got it - I had to remove the subviews from my cells.

Custom UITableViewCell with large UIImageView causing scroll lag

I have a custom UITableViewCell that consists of a UIImageView and a UILabel. The cell is 320x104px and the imageView takes up the whole area with the label in front. There are only 8 cells.
in ViewDidLoad I am creating all needed images up front and caching them in a dictionary at the correct dimensions.
When I scroll the UITableView there is a noticable lag every time a new cell is encountered. This makes no sense to me as the image it is using is already created and cached. All that I'm asking of the cell is for its UIImageView to render the image.
I am using a custom cell with its view in a xib and configuring my UITableView to use it with:
[self.tableView registerNib:[UINib nibWithNibName:#"ActsCell" bundle:nil]
forCellReuseIdentifier:myIdentifier];
Cell creation and configuration:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString* reuseIdentifier = #"ActsCell";
ActsCell* cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
// Configure the cell...
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(ActsCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Act* act = [self.acts objectAtIndex:indexPath.row];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.title.text = act.name;
cell.imageView.image = [self.imageCache objectForKey:act.uid];
}
What could be causing the lag? There would seem to be no benefit in trying to do anything Async as all the time-intensive work is done.
Do you load images from local files by any chance?
By using Instruments I've found that there is some lazy loading mechanism in UIImage - the real image data was decompressed from PNG only at the stage of rendering it on the main thread which caused the lag during scrolling.
So, after the loading of UIImage with -initWithContentsOfFile: method, I've added code to render the contents of this image to offscreen context, saved that context as new UIImage and used it for UIImageView in UITableViewCell, this made the scroll to be smooth and pleasant to the eye again.
In case of reference there is the simple code I'm using to force reading of image contents in separate thread (using ARC):
UIImage *productImage = [[UIImage alloc] initWithContentsOfFile:path];
CGSize imageSize = productImage.size;
UIGraphicsBeginImageContext(imageSize);
[productImage drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
productImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
And I think the surface of UIImage created in such a way will be in the supported format suited for rendering, which will also offload work needed to render it on main thread.
EDIT: The docs for UIGraphicsGetImageFromCurrentImageContext() say that it should be only used from main thread, but searching on the net or SO shows that starting from iOS 4 the UIGraphics.. methods became thread safe.

How to hide UIImageView on the cell when it is swiped to delete

I am implementing this. But the image is not hiding. What is the proper way?
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath;
{
PlayerCell *cell = [tableView dequeueReusableCellWithIdentifier:#"PlayerCell"];
cell.ratingImageView.hidden= YES;
return;
}
You have to get the cell that was selected using that IndexPath, and then HIDE the imageview from that cell. Im pretty sure the apple tutorials have an example of getting the selected cell.
You need to get the actual cell at the index path, see if the following replacement does the trick:
PlayerCell *cell = [tableView cellForRowAtIndexPath:indexPath];
Cheers

Resources