UITableCell subclass never released? - ios

I'm using ARC but it seems that my custom UITableCellView is not release.
TBMListingLineView is a subclass of TBMGlobalCustomCell which is a subclass of UITableCellView.
In TBMListingLineView there are 10 UILabels (nonatomic, retain)
I've implemented in both classes the method dealloc which is never called (breakpoint doesn't stop the execution)
When I'm scrolling the TableView, the number of UILabel is increasing in Instruments/Allocations and that causes the application crashed after several memory warning.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
static NSString *CellIdentifier = #"Cell";
TBMGlobalCustomCell* cell;
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
switch(sortIndex) {
case 0 :
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil || ![cell isKindOfClass:[TBMListingLineView class]]) {
cell = [[TBMListingLineView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
break;
....
return cell;
}

The first problem is that you call dequeueReusableCellWithIdentifier twice for each cell.
And then you "throw away" the second dequeued cell also if it does not have the right class.
A better solution is to use different cell identifiers for each cell (sub)class used in
the table view, so that dequeueReusableCellWithIdentifier returns instances of the correct
class.

Related

Unable to deque cell with identifier title

I am getting the following error when I press a button that opens a tableview:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier title - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
Here is the code in the view controller for the tableview and the method that's causing the problem:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = [menuItems objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
I researched the error and I tried removing the forIndexPath:indexPath so the code looked like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = [menuItems objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
return cell;
}
Now this caused a new error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:
now I did some logging to find out that cell == nil was true so I added a check for that as suggested by some earlier questions:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = [menuItems objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil){
cell = [[UITableViewCell alloc] init]
}
return cell;
}
Now this removed all errors but now when I open the tableview, the cells are empty when I want the ones that I created in the storyboard.
How do I fix this problem?
Here is what the View Controller looks like in the storyboard:
there are two cell recycle methods you can call on a UITable
View,
-(UITableViewCell *) dequeueReusableCellWithIdentifier:forIndexPath:
-(UITableViewCell *)dequeueReusableCellWithIdentifier:
its somewhat confusing, but they are quite different in how they are used. The one which takes a second argument (of type NSIndexPath) is dependant on you first having registered a class or xib file with the tableView, in order that the tableView can create a cell ad-hoc for you when there isn't one handy for recycling. This first method will always return a cell, so you can code your cellForRowAtIndexPath: much like you have.
the second method (which takes only one argument, the (NSString *)cellIdentifier can and will return nil when there is no cell handy for recycling. So when you use this one you should test the result for nil and create a cell in that case.
eg
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellId = #"cellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellId];
}
//etc etc decorate your cell...
return cell;
}
In order to exploit your cell prototypes you will need to register a class or xib for each row/section, so that the table knows which cell to create. The recycling stuff only really works once enough cells have been created to fill the screen and you start scrolling. Good luck
If you are creating your prototype cells in a Storyboard you need to set the "Identifier" field for them to your "CellIdentifier" string. You can do this by selecting the cell and looking in the Attributes Inspector.
If you are creating a separate .xib file for your UITableViewCell you need to call this method on your UITableView in code somewhere:
registerNib:forCellReuseIdentifier:
If you are doing everything in code and just using a UITableViewCell subclass that knows how to layout itself then you need to call this method on your UITableView:
registerClass:forCellReuseIdentifier:
Here is a link to the reference docs

Nested UITableView not returning any cells

I have a UITableView which has another UITableView nested inside one its cells (I know this is bad practise, don't worry!).
The problem is that when I call dequeueReusableCellWithIdentifier: I am getting nil back. HOWEVER this works just fine when the UITableView is not nested inside another one.
Is there a way to NOT reuse a UITableViewCell, but instead directly instatiate it every time?
I've tried using this:
ContactFieldCell *cell = [[ContactFieldCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:thisCellIdentifier];
which doesn't return nil, but then nothing appears in my UITableView!
Here's the code for the "parent" UITableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ContactCardCell";
ContactCardCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSArray *objects = [[sections objectAtIndex:indexPath.section] objectForKey:#"objects"];
CDCard *card = [objects objectAtIndex:indexPath.row];
cell.delegate = self;
cell.fieldsTableView = [[CardTableViewController alloc] initWithCard:card];
[cell.fieldsTableView.view setFrame:CGRectMake(17, 12, 256, 163)];
[cell.contentView addSubview:cell.fieldsTableView.view];
return cell;
}
and here's the code for the "child" UITableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *thisCellIdentifier = #"ContactFieldCell";
ContactFieldCell *cell = [self.tableView dequeueReusableCellWithIdentifier:thisCellIdentifier];
cell.delegate = self;
cell.field = [self.card.sortedFields objectAtIndex:indexPath.row];
return cell;
}
ContactFieldCell is a prototype cell within the storyboard. It has the following code:
#interface ContactFieldCell : UITableViewCell
#property (nonatomic, weak) id<ContactFieldCellDelegate> delegate;
#property (nonatomic, strong) CDField *field;
#property (nonatomic, strong) IBOutlet UILabel *displayNameLabel;
#end
dequeueReusableCellWithIdentifier: does not create a cell if none was found for dequeueing.
Create a cell manually, or use dequeueReusableCellWithIdentifier:forIndexPath:
Yes - #vikingosegundo is correct, but to expand his answer, you need to also register your cell first. dequeueReusableCellWithIdentifier: may return nil. And if it is you need to create your cell,s but dequeueReusableCellWithIdentifier: forIndexPath: will always return a valid cell, the catch is you need to tell it what kind of cell, that is what registerClass does.
Do this for both UITableViews.
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[ContactFieldCell class] forCellReuseIdentifier:#"ContactFieldCell"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *thisCellIdentifier = #"ContactFieldCell";
ContactFieldCell *cell = [self.tableView dequeueReusableCellWithIdentifier:thisCellIdentifier forIndexPath:indexPath];
cell.delegate = self;
cell.field = [self.card.sortedFields objectAtIndex:indexPath.row];
return cell;
}
UITableViews are a very powerful element and can be used to build great apps.
The only thing to keep in mind is, the basics must be clear. Now from your code, I cannot make out whether you have assigned the delegates and dataSources properly, but I'll still mention it in case someone else needs it.
You have a subclassed UITableViewCell which in turn contains a UITableView. The UIViewController must be the delegate and dataSource for the outer UITableView. Make sure you have set it in both the .h and .m file.
Next, your custom cell must also be the delegate and dataSource, but for the inner UITablewView. I suppose here, you have created the inner UITableView in the init method of the UITableViewCell. Set the delegate and dataSource there itself. Then you set other runtime properties in the drawRect method (if needed) and call it's reloadData.
The UIViewController must override the delegate and dataSource methods for the outer table and the cell must override the methods for the inner table.
Also, make sure, the time the cells are plotted, your data is not nil or null.
And a very important fact, that people miss is the following code:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
Just dequeueing the cell is not enough. The first time a cell is dequeued, it is nil because it has not been created yet. Hence the if condition. Once it is allocated and initialized and added to the table, the dequeue code works thereafter.
NOTE : After looking more closely to your code (sorry for not looking the first time), I noticed you have allocated a UITableViewController to your cell. How do you think the cell is going to display a controller? Use a UITableView instead. Try to follow the pattern I have mentioned in paragraph 3. Use a table in the custom cell as a private member (or property, your wish), allocate it in init. Assign the data to the cell from your view controller. Then use this data to set the inner table view cell's properties in it's drawRect. It should work fine.

Is there a way to populate two or more different UITableViews using the same cellForRowAtIndexPath function?

I have a custom UITableView class that I have multiple instances of in a single ViewController. What is the most elegant way to populate these different instances with unique cell data? Thanks for any help in advance!
Right now I can think of two possible solutions:
Have different table view data sources for each table. You can create the data sources in the same file as the view controller if that is important to you.
Have your tableView:cellForRowAtIndexPath: method conditionally load the cells depending on the table. You can find out which table view is calling the method with the first argument of the method. You could also use the tag property of UITableView for differentiation.
I personally prefer the first one.
The answer I wrote here:
https://stackoverflow.com/a/19568737/480415
May help you achieve this.:
You could also put 2 separate UITableViews on your UIViewController,
then handle it in the delegates/datasource methods, ie:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(tableView == _leftTableView)
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//fill cell data here
return cell;
}
else if(tableView == _rightTableView)
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//fill cell data here
return cell;
}
return nil;
}
Notice that the method signature tableView:cellForRowAtIndexPath: includes a reference to the tableView. You can use that to determine which tableView is requesting a cell, and return the same/different data accordingly.
in your controller, you should have your tableViews defined as a properties
#property (nonatomic, strong) UITableView *myTableView
Then, in tableView:cellForRowAtIndexPath: you can check which table is making the callback using something like:
if (tableView == self.myTableView){
//return cell
} else if (tableView == someOtherTableView) {
//return some other cell
}

Using alloc and init when using a UITableViewCell from Storyboard

I'm doing a simples app using Storyboard that a have a View with a UITableView with a UITableViewCell that do the navigation to another UIView.
So a have to code to populate the cell on the table view.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"SampleCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
NSLog(#"cai no init da cell");
}
GPItem *item = [self.items objectAtIndex:indexPath.row];
cell.textLabel.text = #"Post";
cell.detailTextLabel.text = item.imageURL;
return cell;
}
I realised that the code if (cell == nil) { ... never executes so I really need to do that on uses the cell from Storyboard?
Thanks.
You are correct; that code is guaranteed to return a non-nil cell if you are using a storyboard. Also, in iOS 6, the new call dequeueReusableCellWithIdentifier:forIndexPath: never returns nil. See the discussion in my book:
http://www.apeth.com/iOSBook/ch21.html#_registering_a_cell_class
If you've declared your UITableViewCell in table view's prototype cells it's already allocated and just needs to be dequeued. If you're using a custom UITableViewCell subclass, then you must check if it's nil and allocate new entities when necessary.
Nope you don't need that code when using a cell made in your storyboard.
It is probably best to remove this code so that you crash nice and early if the identifier you gave to the cell in interface builder and the identifier you use in code ever drift. This snippet will mask this error and just provide a cell that you most likely was not intending to have.

dequeueReusableCellWithIdentifier does not work

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.

Resources