I have a simple UITableView using custom UITableViewCells.
The options set on the UITableView's properties are only that the style is set to Grouped.
When I'm trying to scroll down through the different items the scroll is extremely jumpy.
I've researched this quite a bit looking at Tricks for improving iPhone UITableView scrolling performance? and a few other questions on this website. I haven't really been able to find a solution though.
EDIT ****
I use a WSDL webservice to load data into the UITableViewCells.
The cells only have a UITextView and three buttons in it.
EDIT ****
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"NavigatorCell";
NewCell *cell = (NewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"NewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.postId = [[items objectAtIndex:indexPath.row] objectForKey:#"PostID"];
cell.post.text = [[items objectAtIndex:indexPath.row] objectForKey:#"Post"];
return cell;
}
I see your NewCell is subclassed.
Don't forget to include this method into your NewCell.m
- (NSString *) reuseIdentifier
{
return #"Cell Identifier";
}
Of course #"Cell Identifier" should be the same that you use in your cellForRowAtIndexPath:.
If you fail to implement this method each cell will be generated from scratch.
Are you using a dequeReusableCellWithIdentifier? Follow the format below. Since you now mention you are loading data from the web you need to do this asynchronously to allow for smooth scrolling. To load from a webservice (asynchronously) there is a nice project just for that here
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"yourCellName";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
Setting your tableview to Reuse cells is the most basic way to ensure good performance. Basically it means that instead of creating a new cell for every cell in the tableview, your tableview will recycle the cells that are off screen. The basic setup is below, and more can be learned from the apple documentation on UITableViewDelegate linked here
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = #"Cell Identifier";
CustomCellClassName *cell = (CustomCellClassName *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil){
cell = [[CustomCellClassName alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, tableView.frame.size.height)];
//Do basic cell construction common to all cells of this type here
//Set background, image etc.
}
//Do specific cell construction here
return cell;
If you're loading data over the network for each cell, you'll see poor performance. Batch your data fetch, then when it's ready tell your tableview to reload itself.
Using Core Data as a temporary backing store, and an NSFetchedResultsController to retrieve the info from Core Data, will save you some work.
Related
I have a project where I need to use a custom UITableViewCell. I'm designing the cell as a prototype in storyboard and it looks fine there. I assign the prototype to my custom UITableViewCell subclass, give it the same reuse identifier I'm using in my UITableView and link the UILabel on the prototype cell to an IBOutlet in my UITableViewCell subclass.
When I call it from the UITableView the cell is created and if I add labels and buttons in the code of that class (create them with a CGRect and all) they all work but the labels I've added in the storyboard never show up.
I don't understand how my subclass can be called and created successfully but its layout and subviews from the storyboard don't seem to exist as far as my app is concerned. What am I doing wrong?
Here's my cellForRowAtIndexPath code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
// Configure the cell...
return cell;
}
I've run into this issue before, and in my case, the problem was that the auto-generated code for the view controller included a call to:
[UITableView registerClass:forCellReuseIdentifier:]
I would suggest checking for and removing any calls to the above, or to
[UITableView registerNib:forCellReuseIdentifier:]
and trying your original code again.
acreichman, add casting in cellForRow and put an NSLog in you cell's awakeFromNib to see if you get there. Let me know...
Your cellForIndexViewPath should look like this, to create a simple custom cell with label,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableCell";
SimpleTableCell *cell = (SimpleTableCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"SimpleTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.nameLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
Make sure that you have made all connections well, set datasource and delegate to table and then set the “Identifier” of the custom cell to "MyTableViewCell" in “Attributes Inspector” like this,
For storyboard:
Add "MyTableViewCell" instead of "SimpleTableCell" as shown in above screenshot.
I'm newbie in IOS and again i face another issue. How can i prevent data vanish from a table cell when i scroll a tableview.
I'm using the code below to load data on the table...Works fine but the data disappear when table cell go in not visible to the screen.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
list = [self.listas objectAtIndex:[indexPath row]];
static NSString *CellIdentifier = #"drop";
item_drop *cell = (item_drop*) [tabela_listas dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"item_drop" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.texto_drop.text = list.nome_lista;
return cell;
}
In android i used a holder to do it. There is anything similiar on IOS?
Since you are using reusable cells of a custom subclass of UITableViewCell, make sure you register the cell identifier in the UITableView, associating it to your custom cell type. i.e:
[yourTableView registerClass:[item_drop class] forCellReuseIdentifier:#"drop"];
You typically do this when you configure subviews in the UIViewController that controls the view your UITableView is a part of, in viewDidLoad.
With that in place, you should never hit the code inside if (cell == nil).
I am NOT using Storyboards. I have a UITableViewController and I would like to display a list of songs from user's library.
This is my code so far:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
NSArray *songs = [songsQuery items];
return [songs count];
}
But for this block, I do not know what to do. I found a tutorial for storyboards, but it is not valid here:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
NSArray *songs = [songsQuery items];
MPMediaItem *rowItem = [songs objectAtIndex:indexPath.row];
cell.textLabel.text = [rowItem valueForProperty:MPMediaItemPropertyTitle];
cell.detailTextLabel.text = [rowItem valueForProperty:MPMediaItemPropertyArtist];
return cell;
}
This only works in Storyboard because I can click the cell in the storyboard and rename its identifier to 'Cell'. In the .xib/nib file in my project, all I see is a view filled with country's names. I cannot click a single cell, I can only edit the whole table.
My question is, what code must I put in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath in order to display this list?
Thanks!
The basic problem you're encountering is that, when used in conjunction with a properly configured storyboard, dequeueReusableCell... will create a cell of the appropriate type if none is available to dequeue.
If you are using a standard UITableViewCell, you can use the following block to dequeue and/or create an appropriate cell:
static NSString* reuseIdentifier = #"Cell";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if(!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
}
If, on the other hand, you're loading your cells from a nib file, you can just add:
[self.tableView registerNib:[UINib nibWithNibName:nibName bundle:[NSBundle mainBundle]] reuseIdentifier:reuseIdentifier];
to your viewDidLoad.
Alternatively you can use registerClass:forCellReuseIdentifier: if you have a custom cell class that sets up it's own subviews.
First off, check if the cell being dequeued is nil or not.
The next thing I can see a problem with is the fact that you aren't really loading anything in cellForRowAtIndexPath:. Basically what this method does is dequeue some cell which has been marked for reuse in an attempt to save memory, and if you haven't set the proper Restoration Identifier in the Interface Builder, then there is no way to know which Nib you want initialized to use here.
What used to happen (or at least my understanding of it) is that in cellForRowAtIndexPath: you would have to check if the cell returned from dequeueReusableCellWithIdentifier: was nil, and if so, you'd have to create the cell from scratch using something like this:
SomeCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell)
{
cell = [[[NSBundle mainBundle] loadNibNamed:#"SomeCellNib" owner:self options:nil] objectAtIndex:0];
}
and this would ensure that if no cell was dequeued that you would load up a fresh one from scratch, although I believe that Apple actually changed how dequeueReusableCellWithIdentifier: works, and as long as you've registered the Identifier, it will create a new cell for you.
For this reason I'm not sure what the problem is if you've set the Identifier properly, and all I can suggest is to try and manually load the cell.
EDIT: I forgot to mention where Restoration Identifiers can be set. The Restoration Identifer field is in the Identity tab, or the third tab in the Interface Builder:
To create a prototype cell inside a .nib, drag a UITableCellView out from the right sidebar. From there you can create your cell prototype, as well as set the cell reuse identifier.
I want to load around 6000 - 8000 rows in a UITableview. I get the data from the server using a async call and when I get the data I call
[tableView reloadData]
This is to refresh the table view . But because of some reason my app gets stuck and freezes .
When I debug , I found that cellforrowatindexpath is called 6000 times (on main thread) and
dequeueReusableCellWithIdentifier always returns null .
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath{
CDTableRowCell *cell = nil;
// Create and Resue Custom ViewCell
static NSString *CellIdentifier = #"CellIdentifier";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// got into render/theme objec
if(cell == nil){
cell = [[CDTableRowCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// MODIFYING CELL PROPERTIES HERE FROM AN ARRAY
// NO HTTP CALLS
}
Also, tableview starts reusing cell once I start scrolling but before that I never always create a new one.
Any clue why this strange behavior ???
try like this,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier =#"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
return cell;
}
The method in your question is not a table view datasource method. The datasource method has the table view as an argument. The method you have written is one that can be used to obtain a cell from the tableView itself, not to obtain a new cell from the datasource.
I don't know how often that method is called but overriding it is almost certainly not what you want to do.
I'm guessing you have subclassed a uitableview to be its own datasource? If so, you need to have the code in your question in the datasource method tableView:cellForRowAtIndexPath:, and not override the method as you have now.
If we give same Identifier to all cells, Disappearing cell uses the memory of Appearing cell. Means content will repeat when I scroll Table-view. But If we give diff Identifier then every cell will have its own memory location and shows data perfectly.
Now suppose I have 1000 or more records to load in Table-view. If I will give different Identifiers, there will be lots of allocations in memory. So Is there any solution to show data perfectly with minimum memory allocation ?
Here is how I define cell identifier:
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = [NSString stringWithFormat:#"%d%d",indexPath.section,indexPath.row];
UITableViewCell *Cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (Cell == nil)
{
Cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:cellIdentifier];
}
}
The problems that you are experiencing are caused by you improperly using cell identifiers. Cell identifier should be the same for all cells that you want to reuse. Take a look at this template, it should explain the correct approach:
-(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;
}
Btw. use camel case to name your variables, capitalized names are meant for class names.
you should clear the contents of dequeued cell like emptying labels and others. if you allocate separate memory for each cell you will easily go low memory. perfect memory management is still reusing cells.