When I want to create a table with custom cells, this is how I will write the standard codes:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"CustomCell";
CustomCell *cell = (CustomCell*)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
else
{
NSLog(#"reuse!");
}
cell.property1 = ....
cell.property2 = ....
return cell;
}
This is roughly how I will create the custom cells:
create a CustomCell.h (inheriting UITableViewCell) and CustomCell.m and a CustomCell.xib
in the CustomCell.xib, change the 'class' property of the top level UIView to 'CustomClass'
I have been doing this for sometime but today I finally decided to do a test to see if the table is really reusing the cells correctly.
Nope. The table is NOT reusing any cell!. The NSLog(#"reuse!") in the code snippet above is never triggered.
I decided to do a performance comparisons, with a tableview with 1 million cells, using these two methods:
Method #1 used the method described above. Take note that UIView is the top level view in my CustomCell.xib and no reuse identifier is configured on xib
cell not reused (NSLog reused! not printed)
peak memory about 3.4MB, peak CPU 60%
scrolling is smooth
Method #2 uses a UITableViewCell in the top level of the xib, and I put all my controls on the content view of this UITableViewCell. In the XIB, I configured the reuse identifier of this cell to be 'CustomCell'.
cell reused (NSLog reused printed multiple times)
peak memory about 6.1MB, peak CPU about 88%
scrolling is smooth
Two questions:
Why am i not seeing much lower CPU and memory usage on method #2, isn't method #1 wrong and method #2 right because method #1 is not reusing any cell at all? Or looking from another point of view, why method #1 is still scrolling so well even though it is stupidly loading nib again and again?
When creating a custom xib for a custom cell, does it make any difference whether the top level object is a UIViewnor UITableViewCell? (Looks like no difference?)
It seems that reuseIdentifier is not set properly in your XIB file.
You can set it programmatically also by adding
[yourTable registerNib:[UINib nibWithNibName:#"CustomCell" bundle:nil] forCellReuseIdentifier:#"CustomCell"];
somewhere before using table view. In viewDidLoad for example.
UPDATE
Question 1. If you do not set cell reuse identifier cells life circle is following:
1.It is created.
2.It is shown.
3.It is moved out of visible area.
4.It is not needed anymore so it is deleted.
If you use reuse identifier cells TYPICAL life circle is:
1.It is popped from reusable queue..
2.It is shown.
3.It is moved out of visible area.
4.It is pushed to reusable queue.
So performance difference is due to what is executed faster: create/release operation or pop/push. Memory usage should be approximately the same for both variants.
you need implement
-(NSString *)reuseIdentifier {
return #"CustomCell";
}
in you cell class CustomCell, because reuseIdentifier is readonly property you can't set it from outside
Related
I am asking very basic doubt belongs to Tableview, I created tableview programmatically without Storyboard/Xib.
The tableView numberOfRowsInSection it will return 14, the cell view fully dynamic and each cell height will different from one another.
My questaion is in tableview delegate method
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell=(UITableViewCell *)[tableview dequeueReusableCellWithIdentifier:cellIdentifer];
if (cell == nil )
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifer];
// how many time entering this loop
}
// ( adding subview to cell view).
cell==nil means need to enter loop right. Depending on what parameter cell object become nil? how many times it will enter, is it once? not at all.
When I checked, it entered 6 times.
if I use cellIdentifier, it will enter 14 times because Identifier different and every time it will create space for cell, its right because each time name will different and while scrolling it will reused.
NSString *cellIdentifer= [NSString stringWithFormat:#"%ld,%ld",(long)indexPath.section,(long)indexPath.row];
which basis it will enter 6 times. why not one's or 14 times. Please suggest what I did wrong. Because if used #"Cell" identifier, while scrolling repeatedly view will overlap. If I used second one cell view object will not overlaps & looks like perfect, but device memory size will increase
ref by https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewCell_Class/index.html
If I right got your question you need register your class in viewDidLoad method or loadView where you created a tableview like that [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CellIdentifier]; for right reuse in tableview
Code : 1
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell=(UITableViewCell *)[tableview dequeueReusableCellWithIdentifier:cellIdentifer];
if (cell == nil ){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifer];
// how many time entering this loop
}
In above code the loop will execute as number of visible rows in your tableview at first time. After that it will reuse cell as the cellIdentifier is same Cell and you need to update cell data as per indexpath.
it means if your table display 6 rows then it will execute for 6 times. Change rowheight and you can check.
Code : 2
NSString *cellIdentifer= [NSString stringWithFormat:#"%ld,%ld",(long)indexPath.section,(long)indexPath.row];
for above code the loop will execute total number of rows you have declared, Because it will create new cell for each indexpath.
If we give same Identifier to all cells, Disappearing cell uses the memory of Appearing cell. But, If we give different Identifier then every cell will have its own memory location and shows data perfectly.
Now suppose we have 1000 or more records to load in Table-view. If we will give different Identifiers, there will be lots of allocations in memory. This is the benefit of re-using cells.
Why 6 times ?
Because, if you give same identifiers, table will re-use cells. Maximum number of cells visible at the moment, are allocated at first. Then on scroll, appearing cell uses a memory location of a disappearing cell (cell dequeuing). So, every time you scroll, new cells are not allocated. Instead, already allocated cells are re-used
Why 14 times ?
Because, every cell has different identifier in this case. Every cell will have its own memory location.
Remember
Add subviews inside cell nil condition. Cell specific content should be assigned outside nil condition. Have a look at following code snippet:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = #"MY_CELL";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
/* Everything that is similar in all cells should be defined here
like background colors, label colors, indentation etc. */
}
/* Everything that is row specific should go here
like label text, progress view progress etc. */
return cell;
}
With same identifier:
When table cell is going to disappeared then this cell will be added in stack and these are reusable.
Now when we are going to show a cell then:
If we are using same identifier then controller will check that cell is available in stack with same identifier.
If yes, then we will get a table cell which is already used and UI was already set for this cell. So we need to reset UI before using it.
If not, then it will create new cell and trying to use it.
In your case I think 4-5 table cell is visible at a time so it is creating 6 table cell and reusing those cell.
With different identifier:
Table cell cell will not available in stack at creating cell for different indexpath. So it will create new one. so cell method will be called 14 times.
I'm developing an app where the user can add events to a particular contact in contact list. So, if the user scrolls the contact list, each contact will show the list of events in it. My problem is when I scroll down the outer table view, the events are showing up fine, but when scrolling up it hides some of the events(i.e., UITableViewCells). And again if I start scrolls down its working fine and again scrolls up the events are hiding. For eg., If a contact has three events in it, it shows only first two and last one is hiding. Checked will the datasource methods, all are fine(i.e., the CellForRowAtIndexPath getting called 4 times).
My CellForRowAtIndexPath of inner tableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"EventViewCell";
EventsCellAtViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
// load a new cell from the nib file
[[NSBundle mainBundle] loadNibNamed:#"EventsCellAtViewCell" owner:self options:nil];
cell = self.eventsAtCell;
self.eventsAtCell = nil;
}
cell.selectionStyle = UITableViewCellSeparatorStyleNone;
cell.event = [_eventsFeed eventAtIndex:indexPath.row];
return cell;
}
Please help me out in this issue. Thanks in Advance!!
Putting a vertical scroll view inside another vertical scroll view is often a cause of problems. The user often gets too confused about which view should have scrolled when they dragged up or down.
In this case it sounds like the data inside the cell is taller than the cell itself, causing it to need to scroll to display it all.
Instead of trying to scroll the data inside a cell, you should make the cell tall enough to display all its data. The heightForRowAtIndexpath method will give you the chance to change each cell's height.
It also looks like this is based on older Apple sample code. The UINib method of instantiating cells is preferable to using "loadNibNamed" to reset the IBOutlet with a new cell. Using storyboards is more modern than both of these methods.
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.
Ahoy!
I'm trying to create a reusable UIView (for various reasons) similar to the UITableViewCell implementation used in UITableViewController. I'd like to use the reusable view in a UIScrollView so I know i'm not trying to achieve something that's entirely unattainable.
The default implementation of this is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//declare cell identifier
static NSString *cellIdentifier = #"cell_identifier";
//dequeue cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
//check cell is valid
if(cell == nil)
{
//create a new cell
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
//
//return cell
return cell;
}
From this, it's worth noting that the cell is dequeued from the UITableView. If the cell is invalid, a new cell is created. My question is, how does this cell then become "queued" for reuse later?
My current attempted implementation looks like this:
- (TestScrollViewCell *)scrollView:(TestScrollView *)_scrollView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//declare cell identifier
static NSString *cellIdentifier = #"cell_identifier";
//dequeue cell
TestScrollViewCell *cell = (TestScrollViewCell *)[scrollView dequeueReusableCellWithIdentifier:cellIdentifier];
//check cell is valid
if(cell == nil)
{
//create a new cell
cell = [[TestScrollViewCell alloc] initWithFrame:CGRectZero];
}
//
//return cell
return cell;
}
I'm thinking that adding a NSMutableDictionary to my TestScrollView to store the cellIdentifier and the TestScrollViewCell (UIView) and then plucking them back out based on the dictionary key would be a good start but is this really a true implementation of "reusable" cells?
The issue I can see is that I would then be adding the UIView to the ScrollView which is positioned based on the frame. Dequeing a view in this sense wouldn't allow me to then add the view to the scroll view without affecting the first view (by modifying the frame) but surely this is how UITableViewCells work, as well as section headers/footers?
I've been looking at this implementation which seems to be following the same route I was intending on implementing but i'm not 100% sold that this is a true implementation of reusable cells.
Has anyone had any luck with this previously? I'm trying to take Apple's lead on this one but other than UITableViewCell and MKAnnotationView (MapKit) there aren't any accessible implementations of this for me to glean from.
Any help would be greatly appreciated.
It's not just the view, it's the whole UITableViewController you'll need to recreate. The reuse flow goes like this:
dequeueReusableCell gets empty reused cell from some storage, I guess, from NSMutableArray (grab first object from array, then delete it from array and return it). If array is empty, method returns nil. You check for cell value, if it's nil, you create a new instance of cell class. If it's not nil, you fill it with your data.
This goes for every visible cell, that is, every cell that can fit on screen. Any non-visible cells are not initialized. When user scrolls the table, cell that are gone completely off-screen (not a single pixel visible) sent to reuseQueue – all their subviews and values return to default values or just nilled, and then cell gets added to the end of our NSMutableArray that is the queue.
I hope I explained well enough.
EDIT: Oh, and one more thing - you'll need different reuse queues for each reuse identifier.
I am wanting to create a custom UITableView cell. I would like to know how to do this. I understand how to actually create it and write code for it, but how can i create 1 style and then when i have more cells added, i want the same style. How can i do this? Is there a way to create 1 custom cell and have all the other cells that i want to add later follow this cells style?Thanks for the help!
In my projects I'm implementing method that creates custom style programmatically. Also it is possible to make custom cell via IB and when you need just take custom cell from it.
Don't forget that if you will write your code correctly then your cells will be reused and that method will be called only for number of cells that are visible in your table view.
may be this can help you http://iphone-bitcode.blogspot.com/2011/06/custom-tableview-cell.html
Write a separate .h/.m/.xib for the cell, and in the .xib set File's Owner to the class you want multiple copies of it in (your table view controller class, most likely). Attach it to an IBOutlet you created in the table view controller for new cells.
Then, each time you want a cell, try and dequeueReusableCellWithIdentifier: on your tableView, and if that doesn't work (you have no reusable ones), make a new cell using your custom class by simply loading the nib file. It will automatically create an instance of the cell and attach it to your IBOutlet, and then just retain the cell and set the outlet back to nil for the next time you need to create a cell. Essentially, I mean this (I have an IBOutlet UITableViewCell *cellOutlet):
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *reuseIdentifier = #"CustomCell";
UITableView *cell = [self.tableView
dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"MyCustomTableViewCell"
owner:self options:nil];
cell = cellOutlet;
self.cellOutlet = nil; // autoreleases
cell.reuseIdentifier = reuseIdentifier;
}
// configure the cell here
return cell;
}