i have a tableview which has image and a text behind, i create the cells like below code :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
NSDictionary * companyProductRow = [DB getCompanyProductRow:[self.companyProductIDs objectAtIndex:indexPath.row]];
int companyProductID = [[companyProductRow objectForKey:#"ID"] intValue];
cell.tag = companyProductID;
cell.textLabel.text = [companyProductRow objectForKey:#"ImagePath"];
NSString* fullPath = [FileManager fullPath:companyProductID fairDirectory:[[self.currentFair objectForKey:#"ID"]intValue]];
[[cell imageView] setImage:[UIImage imageWithContentsOfFile:fullPath]];
return cell;
}
i read some tips about tableview performance on apple developer site but all of they said is :
Reuse cells. Object allocation has a performance cost, especially if the allocation has to happen repeatedly over a short period—say, when the user scrolls a table view. If you reuse cells instead of allocating new ones, you greatly enhance table-view performance.
Avoid relayout of content. When reusing cells with custom subviews, refrain from laying out those subviews each time the table view requests a cell. Lay out the subviews once, when the cell is created.
Use opaque subviews. When customizing table view cells, make the subviews of the cell opaque, not transparent.
the sample in apple site is the same, but i want to know is there any way to have better performance when scrolling on uitableview? (when we should read image from disk).
so thanks
Yes it is.
The first thing you could enhance is the image loading.
Try to avoid [UIImage imageWithContentsOfFile:fullPath] because it always loads the entirely image into the memory. This in fact is slow. Use [UIImage imageNamed:"YOUR_IMGAES_NAME"] instead because it caches the images after the first use or preload/store them directly in your fileManager.
The next step would be to set all views in the cell to nil (like the imageView) and draw all the contents by hand. The reason for this is that UIViews are pretty slow. If you have a lot of labels, images, etc. to display it happens to be much faster to draw everything by hand.
Your tableView:cellForRowAtIndexPath: looks like it's doing 2 disk reads for each cell, one from your [DB getCompanyProductRow:...] which I assume is a database fetch of some sort, and the second from the [UIImage imageWithContentsOfFile:...].
Find a way to load these information to memory, before tableView:cellForRowAtIndexPath: gets called (preferably before [tableview reloadData]).
Tips for large datasets:
If the data you need to fetch is too big for the memory to be loaded into an array, try to implement some paging mechanism so you just need to display a subset of that data. I would even recommend you use Core Data's NSFetchedResultsController instead, but that's if your database structure is compatible for object models.
Related
I'm building a UItableView with custom cells that displays images. The problem is that as I scroll down, the previous cells does not seem to do a dealloc and the device memory is filling up.
This is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
CustomTableCellView *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[CustomTableCellView alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:simpleTableIdentifier];
}
Article *tempArticle = [_objects objectAtIndex:indexPath.row];
cell.titel.text = tempArticle.title;
cell.subTitle.text = tempArticle.subTitle;
cell.category.text = tempArticle.category;
if(tempArticle.imageUrl){
[cell.image setImageWithURL:[NSURL URLWithString:tempArticle.imageUrl]];
} else {
cell.image.image = [UIImage imageNamed:#"video"];
}
return cell;
}
I'm 99% sure that the memory increase is due to the image loading & caching. The AFnetworking it's using a NSCache instance to cache the downloaded images, this cache is performed in memory and it will be drained when the system decide it has low memory, this is not an issue.
Also the imageNamed method caches the images that are loaded in the same way as AFNetworking.
In order to check if you have a leak or something is not working properly with your data, check if the memory increases after you scroll back up, if the memory stays the same, than you don't have a problem nor a leak (also you can check leaks with instruments), the cached images are the ones that cause the increase of memory.
Also remove the view controller (pop, remove) from the stack, this will cause some deallocations on the text, labels or other data (not images), if the memory decrease (it won't be a substantial decrease) then you are on the right way.
As stated above, you are setting the reuse identifier correctly.
Maybe it's filling the memory because you're using AFNetworking library and downloading the images for your cells? Define "filling up the memory" more exactly please.
I have this tableView and I want to build the cells in a custom way.
The way I do it is by adding subviews to the cell.
- (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] autorelease];
}
else
{
UIImageView* img=[UIImage imageNamed:#"abc.png"];
[cell addSubview:img];
//processing the final cell takes time!
}
return cell;
}
But, since some jittery frame rate is affecting the responsiveness of the table, I'd like to pass all this creation of the cell into a thread. so I want to put an image of some sort, for the meantime, and once finished, the thread would update cell to be the final cell.
Is this a normal approach?
If so, how do I update the cell from the thread?
Do I need to define all variables as __block? before going into the thread?
since some jittery frame rate is affecting the responsiveness of the table, I'd like to pass all this creation of the cell into a thread.
The vast majority of UIKit, which includes UITableViewCell, isn't thread safe. From Apple's documentation:
For the most part, UIKit classes should be used only from an application’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your application’s user interface in any way.
...only a few drawing and graphics context methods in UIKit are thread safe. It's a really bad idea to create your cells on a secondary thread. You don't want to be doing this.
There's a lot of material out there are ways to efficiently create and draw cells into table views - as someone has pointed out in the comments, you seem to be adding a UIImageView not at cell creation, but on re-use. This means your cells are going to have many, many image views being constantly added to them. This is obviously not a good idea.
A better idea is to create a UITableViewCell subclass that already contains the views you're going to need, or to add them at creation and access them via view tags.
I'm really frustrated at this point. Dequeueing a reusable cell with identifier is always returning null.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
NSLog(#"INIT");
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
return cell;
}
What am i doing wrong here? Thanks.
You're doing everything right, everything is working as it should. iOS will create enough new cells to fill the screen (plus one). It will start reusing these cells only when your UITableView contains more rows than can fit on one screen and then the user scrolls.
You'll find that if you have a datasource will say, 100 items in it and then scroll, you'll only have your log message show probably 11 times (depends on how many cells fit on your screen) instead of 100 as iOS will start recycling cells as you scroll.
With large lists, it would use too much memory to create new views for every possible row in a UITableView. The alternative would be to allocate new views for rows as you scroll. However, this would create a performance bottleneck that would cause laggy scrolling in any UITableView.
Apple mention the performance bottleneck in their documentation on UITableViews.
Reuse cells. - Object allocation has a performance cost, especially if the allocation has to happen repeatedly over a short period—say, when the user scrolls a table view. If you reuse cells instead of allocating new ones, you greatly enhance table view performance.
Did you set your cell's reuse identifier? Init your cell with -initWithStyle:reuseIdentifier:, or set the identifier in IB.
Apple's iOS TableView and cell reuse is killing me. I searched and searched and studied, but can't find good docs or good answers. The problem is that when the TableView reuses cells things like Checkmarks (cell accessory) set on a selected Cell are repeated in the cells further down in the table view. I understand that cell reuse is by design, due to memory constraints, but if you have a list with say 50 items, and it starts setting extra checkmarks where they're not wanted, this makes whole endeavor useless.
All I want to do is set a checkmark on a cell I've selected. I've tried this using my own custom cell class, and standard cells generated by a boiler plate TableView class, but it always ends up the same.
Apple even have an example project called TouchCell you can download from the dev center, that is supposed to show a different way of setting a checkmark using a custom cell with an image control on the left. The project uses a dictionary object for a data source instead of a muteable array, so for each item there is a string value and bool checked value. This bool checked value is supposed to set the checkmark so it can track selected items. This sample project also displays this goofy behavior as soon as you populate the TableView with 15+ cells. The reuse of cells starts setting unwanted check marks.
I've even tried experimenting with using a truely unique Cell Identifier for each cell. So instead of each cell having something like #"Acell" I used a static int, cast to a string so the cells got #"cell1", #"cell2" etc. During testing though, I could see that hundreds of new cells where generated during scrolling, even if the table only had 30 items.
It did fix the checkmark repeat problem, but I suspect the memory usage was going way too high.
It's as though the cells that are not currently in the viewable area of the table are created all over again when they are scrolled back into view.
Has anyone come up with an elegant solution to this irritating behavior?
cell reusing can be tricky but you have to keep 2 things in mind:
Use one identifier for one type of cell - Using multiple identifiers is really only needed when you use different UITableViewCell-subclasses in one table view and you have to rely on their different behaviour for different cells
The cell you reuse can be in any state, which means you have to configure every aspect of the cell again - especially checkmars / images / text / accessoryViews / accessoryTypes and more
What you need to do is to create a storage for your checkmark states - a simple array containing bools (or NSArray containing boolean NSNumber objects respectively) should do it. Then when you have to create/reuse a cell use following logic:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *reuseIdentifier = #"MyCellType";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if(cell == nil) {
/* create cell here */
}
// Configure cell now
cell.textLabel.text = #"Cell text"; // load from datasource
if([[stateArray objectAtIndex:indexPath.row] boolValue]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
then you will have to react on taps:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[stateArray replaceObjectAtIndex:indexPath.row withObject:[NSNumber numberWithBool:![[stateArray objectAtIndex:indexPath.row] boolValue]]];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
Just remember to use NSMutableArray for your data store ;)
I have a UITableView with custom cells that were defined in the xib file, and am experiencing poor scrolling performance (choppy) on my device when the cells have a UISegmentedControl on them. NSLog statements reveal that the cells are being allocated and reused as they ought. My code for cellForRowAtIndexPath method is below. Connections are made in the xib as per Apple's documentation. (Scrolls smoothly in simulator btw)
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"TableViewCell"
owner:self
options:nil];
cell = self.tvCell;
self.tvCell = nil;
}
cell.layer.shouldRasterize = YES; // build error is here
UILabel *lbl = (UILabel *)[cell viewWithTag:1];
[lbl setText:[NSString stringWithFormat:#"Q%i", indexPath.row+1]];
return cell;
}
Any drawing that a table cell has to do while it's being scrolled is going to cause performance issues; when you have a lot of subviews, there tends to be a lot of drawing going on, and that will—as you've observed—make your scrolling pretty choppy. There are a couple of ways to try to reduce that.
The first step is to make sure that your cells themselves, and as many of their subviews as possible, have their opaque properties set to YES. Opaque views don't have to get blended with the content underneath them, and that saves a lot of time.
You may also want to set your cells' layers to rasterize themselves, like this:
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
This will collapse your view hierarchy into one flat bitmap, which is the kind of thing Core Animation just loves to draw. Note that any animating views—activity indicators, for instance—will force that bitmap to be updated every time they change, i.e. a lot. In that case, you won't want the cell to rasterize everything; you might just use a subview with all of your relatively static views (e.g. labels) beneath another subview with any such dynamic content, and only have the first of those rasterized.
Make sure your identifier is 'MyIdentifier' in the xib. You'll get a good performance hit if it's not. I'm guessing that 'allocated and reused as they ought' means a few allocated on startup and no more allocated after. If that's true then you're probably all set.
Another way to improve performance is to construct your table view with code. It's a good deal faster than using xib's. When I construct table views I usually build them in IB, then copy over the frame values into code and construct in code.
Set aside some time to watch the WWDC 2010 performance video's. A lot of great information, I learn something new each time I watch them.