Understanding dequeueReusableCellWithIdentifier - ios

I am little confused regarding the use of dequeueReusableCellWithIdentifier method and how it works. I currently have a searchBar and two mutable Arrays (say filteredTableData and tableData) . Now I just realized that no matter what I do cell always returns nil. I also have a filter feature on my UITableView (using different arrays) however it seems to always return a nil. My question is when does dequeueReusableCellWithIdentifier return something ?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
MMTableCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MMTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
cell.RowNo = indexPath.row;
cell.ImageAlbum.image = [self getMP3Pic:indexPath.row];
cell.table = self.tableViewObject;
}
NSString* sng = isFiltered ? [self.filteredTableData objectAtIndex:indexPath.row] : [self.tableData objectAtIndex:indexPath.row] ;
cell.labelSongName.text = [NSString stringWithFormat:#"%#", sng ];
return cell;
}

This is actually an optimization of displaying UITableView. Imagine that you have 1,000 of data to display and it does not make any sense to create 1,000 UITableViewCell in order to host each of your data. So, we use something called reusability that only a certain amount of cells will be used. When scrolling, it's not actually dealloc the invisible cells and create new cells. It's just move the existing cells around.
The identifiers are just used to identify different cells based on what you need. I might have multiple cell prototypes being used in one UITableView.
For your question, you will need to set up prototyped cells in the Interface builder. In the programmatical way, if we can not find the cell using dequeueReusableCellWithIdentifier method, then we create. But, you can take advantage of Interface Builder and it will always return cell.

dequeueReusableCellWithIdentifier is nothing but reusing the Cell whenever applicable. Initially cell is always nil. So, we check for cell==nil condition and we will create UITableViewCell first time. When u scroll the tableview then dequeueReusableCellWithIdentifier will be called to reuse the cell which is already created at First Time.
You can modify your code as below
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
MMTableCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[MMTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier] ;
}
cell.RowNo = indexPath.row;
cell.ImageAlbum.image = [self getMP3Pic:indexPath.row];
cell.table = self.tableViewObject;
NSString* sng = isFiltered ? [self.filteredTableData objectAtIndex:indexPath.row] : [self.tableData objectAtIndex:indexPath.row] ;
cell.labelSongName.text = [NSString stringWithFormat:#"%#", sng ];
return cell;
}
Try it..Hope it helps you..!

Related

Custom cells in UITableView become blank after scrolling up or down

I was tasked to solve this bug in the app (It's for comercial use so I can't link the project). I'm also completely new to Objective-C or IOS development and I have no idea why the following happens. Reminder, I'm using custom cells.
When the tableview loads, everything looks fine.
Click here to see the example
But when I scroll up and back to the same postion the cell becomes blank, as if it had no data. Blank cell
If I repeat the same process everything looks fine again.
Here's the code for cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellId = #"cell2";
PedTableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell == nil)
{
cell = [(PedTableCell*)[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}
if (tableView==_table3)
{
NSString *docsDirr;
NSString *documentsDirectoryForSaveImages;
docsDirr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
documentsDirectoryForSaveImages = [docsDirr stringByAppendingPathComponent:#"/CarpetaImatges"];
NSString *match = #".jpg";
NSString *preCodigo;
NSScanner *scanner = [NSScanner scannerWithString:[pedarray objectAtIndex:indexPath.row]];
[scanner scanUpToString:match intoString:&preCodigo];
NSString *nomimg=[NSString stringWithFormat:#"%#.jpg", preCodigo];
UIImage *img = [UIImage imageWithContentsOfFile:[documentsDirectoryForSaveImages stringByAppendingPathComponent:nomimg]];
cell.img.image = img;
cell.layer.borderWidth=1.0f;
cell.img.layer.borderWidth=1.0f;
cell.cart.text = preCodigo;
cell.descart.text = [nomarray objectAtIndex:indexPath.row];
cell.cant.text = [cantarray objectAtIndex:indexPath.row];
cell.precart.text = [precioarray objectAtIndex:indexPath.row];
cell.pvpcart.text = [pvparray objectAtIndex:indexPath.row];
[cell layoutIfNeeded];
}
return cell;
}
I've debugged the method and everytime it returns a valid cell. When I click the empty cell it still works as expected. Essentially, the data is there, it's just not rendered for some reason.
I've checked other posts with the same/similar issue but nothing seems to really match. PrepareForReuse is a no go since there's no UI to be changed, just content. I'm getting the values of the arrays with indexPath.row to make sure I'm fetching the correct value. I've even tried to set the heightForRowAtIndexPath to the same height the cell should have (all cells are of the same size and never change) or let AutoLayout handle it but to no avail.
Code of numberOfRowsInSection (Returns the amount of orders that exists in database currently):
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if(tableView == _table3)
{
[self cargacarro];
return pedarray.count;
}
return pedarray.count;
}
Any ideas?
Register your cell nib in storyboard or in code before tableview is loaded, and after replace dequeueReusableCell... with this one
PedTableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId forIndexPath:indexPath];
cell.img...
cell.cart...
remove this part, is no longer needed
if (cell == nil)
{
cell = [(PedTableCell*)[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}

label value in custom tableview cell is changing while scrolling

Label value in custom tableview cell is changing while scrolling the tableview
static NSString *cellIdentifier = #"Cell";
cell = (CheckOutCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// If there is no cell to reuse, create a new one
if(cell == nil)
{
cell = [[CheckOutCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.delegate = self;
CheckOutGroup * checkOut=_cartArray[indexPath.row];
cell.name.text=checkOut.name;
cell.size.text=checkOut.size;
cell.priceValue=checkOut.price ;
[array addObject:checkOut.dictionary];
[itemIdArray addObject:checkOut.itemId];
cell.price.text=[NSString stringWithFormat:#"Price:%#%#",self.currValue,checkOut.price];
cell.quantity.text=[NSString stringWithFormat:#"Qty:%d",1];
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://sqwip.ignivainfotech.net/%#",checkOut.image]]];
cell.checkOutImage.image=[UIImage imageWithData:imgData];
NSUserDefaults *storeDefaults = [NSUserDefaults standardUserDefaults];
[storeDefaults setObject:self.currValue forKey:#"cur"];
cell.increaseBtn.tag=indexPath.row;
val=[checkOut.price intValue];
if (check==0) {
check=check+[checkOut.price intValue];
}
else
check=check+[checkOut.price intValue];
self.totalPrice.text=[NSString stringWithFormat:#"TotalPrice:%#%d",self.currValue,check];
return cell;
}
When a cell needs to be displayed (IE scrolling back onto the screen) the tableview controller is going to look at the data source and fire the some of its methods so it can display the correct information. In short its going to recall this method every time a cell needs to be displayed:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
If the cell is changing its because the data source is changing! I suggest placing a break point inside cellForRow and make sure the data from your:
CheckOutGroup * checkOut=_cartArray[indexPath.row];
or whatever is correct for the right cell before and after scrolling. If it is different and it shouldn't be, possibly you are grabbing the wrong data from the URL on the next pass or your indexes are getting out of place.
It's because of re-useability. Since UITableView reuse its cell on scrolling. To resolve this just make your cell nil.
Try this on CellForRowAtIndexPath:-
static NSString *cellIdentifier = #"Cell";
cell = (CheckOutCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell)
cell = nil
// If there is no cell to reuse, create a new one
if(cell == nil)
{
cell = [[CheckOutCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}

My custom UITableViewCells are affecting the status of each other

I have created a custom UITableViewCell with a UISwitch, a UIStepper and two labels inside.
When I run my app in the simulator, and the tableview lists each instance of this custom cell. I notice that when I toggle the switch in the first cell and increase it's stepper (affecting one label), the ninth cell is affected the same way.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *items = [self arrayForSection:indexPath.section];
static NSString *CellIdentifier = #"Cell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if(indexPath.section == 0){
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.notificationTitle.text = [items objectAtIndex:indexPath.row];
return cell;
}
I also have two sections in this tableview, and set the first one so that the selection style is off.
What is going on exactly and how to do I keep it from happening?
Where is the part where you are creating the custom cell? Are you doing that or is it just that you've missed it out while pasting it here?
Try this (hope you're using a NIB file to create a custom cell):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *questionTableIdentifier = #"CustomCell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:questionTableIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.notificationTitle.text = [items objectAtIndex:indexPath.row];
return cell;
}
When you use this [tableView dequeueReusableCellWithIdentifier:questionTableIdentifier]; you are actually reusing a already made instance of your cell(if there is any to be reused else create a new one). UITableViews work this way in order to conserve memory. If you have a very large number of cells it will still only consume about the same amount of memory as if there were only enough to cover the screen. In order to fix your problem you need to keep the state of your cell's some other place then the cell itself. Maybe a data structure in your tableviewcontroller or viewcontroller. And then set the values when your tableview wants to display the cell.
If you go with the non reusable cells then you could do something like this.
#property(nonatomic, strong)NSArray *cells;
- (id)init
{
self = [super init];
if ( self )
{
_cells = #[#[[[YourCell alloc] init],
[[YourCell alloc] init],
[[YourCell alloc] init]
],
[#[[YourCell alloc] init],
[[YourCell alloc] init],
[[YourCell alloc] init]]];
}
return self;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return _cells[indexPath.section][indexPath.row];
}
Assuming you have 2 sections with 3 cells in each section.

How to set and dequeue custom UITableViewCell with dynamic reuse identifier?

Here's what I am trying to do ultimately. I want to display a menu of items in a UITableView, but dynamically, so that the type of item displayed determines the custom cell view loaded. For example, let's say the menu item type is 'switch', then it will load a nib named 'switch.xib' and the state will be on/off depending on that particular menu item's value. There may be 5 items that are "switch" type, but different values. So I want to use the same xib for each one, but 5 instances.
So, long way around to the question. When I load the cell view from the nib, I would think it would need unique reuse identifiers for the dequeue for when it scrolls back on the screen, right? (Unique for each instance, i.e. each menu item.) In the UITableViewCell in Interface Builder, I see where I can set a reuse identifier property, but I want to set it at run time for each instance of the switch. For example, Menu Item #1 is a switch, #2 is a text field, #3 is a switch, etc. So #1 and #3 both need unique cell ID's to dequeue.
Here's what my cellForRowAtIndexPath looks like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Cells are unique; dequeue individual cells not generic cell formats
NSString *CellIdentifier = [NSString stringWithFormat:#"Cell%d", indexPath.row];
ITMenuItem *menuItem = [menu.menuItems objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
// Load cell view from nib
NSString *cellNib = [NSString stringWithFormat:#"MenuCell_%#", menuItem.type];
[[NSBundle mainBundle] loadNibNamed:cellNib owner:self options:nil];
cell = myCell;
self.myCell = nil;
}
// Display menu item contents in cell
UILabel *cellLabel = (UILabel *) [cell viewWithTag:1];
[cellLabel setText:menuItem.name];
if ([menuItem.type isEqualToString:#"switch"]) {
UISwitch *cellSwitch = (UISwitch *) [cell viewWithTag:2];
[cellSwitch setOn:[menuItem.value isEqualToString:#"YES"]];
}
else if ([menuItem.type isEqualToString:#"text"]) {
UITextField *textField = (UITextField *) [cell viewWithTag:2];
[textField setText:menuItem.value];
}
return cell;
}
You can set the reuse identifier in the nib file. So for switch.xib you would use 'switch' as the reuse identifier. Then just change
NSString *CellIdentifier = [NSString stringWithFormat:#"Cell%d", indexPath.row];
to
NSString *CellIdentifier = menuItem.type;
assuming that menuItem.type is 'switch'
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil)
{
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
TextFieldFormElement *item = [self.formItems objectAtIndex:indexPath.row];
cell.labelField.text = item.label;
return cell;
}
For my idea, if your types are not too much design each cell in different xib and swift file. Mainly for performance issues.
If not possible to dequeue, give them different identifier. You can register more than one identifier for tableview or collection view (one of our apps using 12 different cells this way.)
But handling IBActions will be little messy this way.
Your custom cell from xib file can be loaded in a way like this. The xib file name is the same as the cell class name and the reuse id.
- (YourCell *)tableView:(UITableView *)_tableView getCellWithId:(NSString *)cellId
{
YourCell *cell;
/* Cell id and xib have the same name. */
cell = [_tableView dequeueReusableCellWithIdentifier:cellId];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:cellId owner:self options:nil];
cell = [nib objectAtIndex:0];
}
return cell;
}

iOS: UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:

I've checked a few other questions but still can't figure out why this is happening. All I know is after using the description code from this question the cell's value is null. I've set the delegate and datasource of the table correctly and I can't see where this is going wrong.
If I set the return value of the following method to 0, no error occurs because the cellForRowAtIndexPath method doesn't really take place. So if I set it as 1 (as it should be) it'll then throw the error. I've synthesized the NSArray and checked that its populated (although that shouldn't matter I guess) and basically what is meant to happen is I press a button that searches and then places the results in the table. So before this the tableview is empty.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
Here's the cellForRowAtIndexPath method itself. I've tried just using the basic template method too, which still throws the same error.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
NSDictionary *aResult = [results objectAtIndex:[indexPath row]];
NSLog(#"RESULTS IN TABLE %i", [results count ]);
id key = [results objectAtIndex:[indexPath row]];
resultTitle.text=#"Hello";
NSURL *url = [NSURL URLWithString:[aResult objectForKey:#"url"]];
NSData *data = [NSData dataWithContentsOfURL:url];
cell.imageView.image = [UIImage imageWithData:data];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[theTableView reloadData];
return cell;
}
I've no idea what is wrong. I've used tableviews before and have compared this project to others to see if I've missed something, but I can't see any real differences.
you are not initisialing the cell if nil is returned from the dequeue method
change
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
to
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
You do not have a method to return the number of rows in section 0 - this is a required data source method (in this case).

Resources