I am trying to reduce duplicate code when laying out my tableview but running into lifecycle problems. Basically its that heightFroRowAtIndexPath is called before cellForRowAtIndexPath. Which is what should happen and I understand why.
But...
I have a cell that is laid out in a storyboard. It has an optional field. If the optional field is not in the data then I remove a label for that field. However I am removing that label in a custom cell implementation:
CustomCell (extends UITableViewCell)
- (void) configureCellForData: (Data *) data {
if (data.optional) {
self.optionalLabel.text = [data.optional];
} else {
[self.optionalLabel removeFromSuperview];
}
}
Then in cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:self.tableLayout[indexPath.section][indexPath.row]];
[cell configureCellForData:self.data];
return cell;
}
Which works great for setting up the cell. However the height is wrong if the optional field is removed, ie I need to adjust if the optional field was removed.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:self.tableLayout[indexPath.section][indexPath.row]];
CustomCell *headerCell = (CustomCell *) cell;
if (self.data.optional == nil) {
return cell.bounds.size.height - headerCell.optionalLabel.bounds.size.height;
}
return cell.bounds.size.height;
}
}
It does not seem like much but I simplified my check to "data.optional == nil" and it is more complex than that and involves a DB call.
Is there a better way to set this up such that I don't have to make the check twice once when the height for cell is calculated and once when the cell is initialized?
If you wanted to only check once you could store an array of booleans that stores whether or not the data is there or not. So, make the check for each row, store the result into the array, before you make the check next time, check to see if the array has an value for that cell, if it does, use that value, if not, make the database call.
Make sure that you only store values in the array index associated with the indexPath, and if the array is shorter than the indexPath you're at, you know you need to make the call and add the value into the array.
Edit: As I think more about it, I would put the bool value on the cell itself, and then just call cell.isDataAvailable (or whatever you want the value to be) in order to avoid the second call when you go to set the cell up, as you would have already checked this in heightForRowAtIndexPath.
Related
I have a populated array which I can display in the tableview, but I want to hide 3 of the cells text (out of 7 cells). I know the below code is wrong, but in this case I only want to show the text in cell 0.
cell.animal.text[0] = animalarray[0]
cell.animal.hidden = true
Because you don't have codes, I can only use words to describe how it should be done.
You need to have an array of the unwanted text that you do not want to show.
Inside your cellForRowAtIndexPath, you need to have a for loop, to go through the animalarray, and within the for loop, have an if-else statement to check whether if(unwantedtext == animalarray), then cell!.textLabel.text = " "
You need to show me codes for me to help you.
I'm gonna try to help you in Objective C, hopefully I can make the logic so clear the language difference doesn't matter.
Generally you are telling the TableView what to print for each cell in the below delegate method in your ViewController.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
cell.textLable.text = animalArray[indexPath.row];
return cell;
}
This is where you will decided which index in the animalArray you do or do not want to print. If your requirement is a static the simplest is to hardcode the the blocking.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if(!(indexPath.row == self.indexIDontWantToPrint)) {
cell.textLable.text = animalArray[indexPath.row];
}
return cell;
}
If the indexes you do not want to print is dynamic and submitted to you by say an array.
You need to replace if(!(indexPath.row == self.indexIDontWantToPrint)) with checking if indexPath.row is inside the array of indexes you are to ignore.
NSArray has a handy containsObject method you can use to check if the array contains the current index the tableView wants to print. Be careful of the type difference of indexPath.row is NSInteger while NSArray needs to carry NSNumber for simple numeric numbers.
Adding more efficient logic than jo3birdtalk
1) Instead of having a extra array, you can creat an Object which contains a string & Bool.Add these objects in animalarray
2) Get the object from array at indexpath & check
if(animal.isShow == YES),if Yes show the text else hide label Or set blank string whatever you required
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
Animal * animal = animalArray[indexPath.row];
if(animal.isShow == YES)
{
show the text
}else
{
hide label Or set blank string whatever you requered
}
return cell;
}
I am using a custom cell class in a tableview controller.
When I include a statement in the tableviewcontroller in cellForRowAtIndexPath NSLog(#"method called"): it does not seem to get called.
Is it possible that this method is not called when you have a custom cell?
Edit:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"cell for row at index path called");
NSDictionary *item= [self.getItems objectAtIndex:indexPath.row];
//This sets place in storyboard VC
IDTVCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"Cell"];
cell.item = item;
if (cell == nil) {
cell = [[IDTVCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"Cell"];
}
return cell;
}
cellForRowAtIndexPath is not called if no rows are returned.
-tableView:cellForRowAtIndexPath: is not getting called
That is what happened in my case.
It can also not get returned if you reload table on wrong thread and in certain other scenarios.
cellForRowAtIndexPath: not called
However, a custom cell per se does not cause this..
To answer your question - Yes, it is.
There could be n-number of reasons why cellForRowAtIndexPath: is not getting called. This may be because delegate / dataSource is not set or UITableView frame is not set... etc. etc.
You should easily find a solution with more online research and closure look at your code.
(note: The tableView I am using is from the Parse.com iOS SDK - PFQueryTableViewController)
Scenario = I have a TableViewController that has two different types of cells (each with their own identifier). Each object upon being queried and loaded into the datasource is checked if a key on the object is true. Depending on the result I dequeueReusableCellWithIdentifier to the correct cell.
-(PFTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object {
myTableViewCell *cell;
if ([object[#"orientation"] isEqualToString:#"left"] || [object[#"orientation"] isEqualToString:#"right"]) {
myTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
else {
myTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell2"];
}
This all does its job. Each cell is being loaded at the correct indexPath.row and everything. Problem is my tableView "Row Height" itself does not readjust for the new cell. This causes overlapping of cells and makes everything ugly. I can tell the tableView in storyboard to set the row height to whatever the larger of the two cell heights is, but that leaves big spaces in-between cells too which also makes it look ugly.
Question = It is my belief (and correct me if I'm wrong) that I need to use the
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
method in order to achieve this. Where I need help is I am not sure how to set the height of each cell at indexPath depending upon the 'identifier' that I gave each cell in the cellForRowAtIndexPath method.
What I'm looking for = Something like this (please excuse the SuedoCode)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([cell.identifier isEqual:#"Cell"] {
return 100;
}
else {
return 200;
}
}
ANSWER: I figured it out! (I marked the answer below as accepted because it pointed me in the right direction)
Because I am using a PFQueryTableViewController all I had to do this...
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
PFObject *object = [self.objects objectAtIndex:indexPath.row];
if ([object[#"orientation"] isEqual:#"left"] || [object[#"orientation"] isEqual:#"right"]) {
return 100;
}
else {
return 200;
}
}
First, some things to keep in mind. heightForRowAtindexPath is calledbefore CellForRowatIndexPath, and simply says, if object is at indexPath X, then return Y or Z.
The more correct approach might be to subclass the tableCell class, set a property in the .h file and then figure out the path... I'll give you a dirty way :)
Create an NSMutableArray property (don't forget to init it somewhere/somehow), and based on your dataSource, populate it with Height A or Height B (a float). Now, back in heightForRowAtIndexPath, you can say something to the effect of:
return (int)self.myMutableArray[indexPath.row];
I'm having trouble with the following code below, which basically is instantiating an extend uitableviewcell from a storyboard. The problem I'm having is that it seems leftMenuCell is never equal to null, and thus never enters the initiating block. What am I doing wrong?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"LeftMenuCell";
MenuCell *leftMenuCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(leftMenuCell == nil) {
NSLog(#"creating a new cell");
leftMenuCell = [[MenuCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
} ....
You're not doing anything wrong, that's just the way table views work when you make the cell in the storyboard. The method dequeueReusableCellWithIdentifier:, always returns a valid cell when that cell is in a table view in a storyboard. It seems that many programmers haven't figured this out, and still include the if cell==nil clause. This is from the docs:
"If the dequeueReusableCellWithIdentifier: method asks for a cell that’s defined in a storyboard, the method always returns a valid cell. If there is not a recycled cell waiting to be reused, the method creates a new one using the information in the storyboard itself. This eliminates the need to check the return value for nil and create a cell manually"
I'm adding items (e.g. gesture recognizers, subviews) to cells in cellForRowIndexPath. I don't want to add these if the cell is being reused (presumably) so is there a way of easily telling if the cell is new, or is being reused?
The cell prototype is defined in a storyboard.
I'm not using a custom subclass for the cell (seems like overkill). I'm using the cell tag to identify subviews, so can't use that.
I could use the pre-iOS 6 approach, but surely there's a better way to do something so simple?
I couldn't find anything online, so afraid I may be confused about something - but it's a hard thing to search for.
The simplest way to tackle this is to check for the existence of the things you need to add.
So let's say your cell needs to have a subview with the tag 42 if it's not already present.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
UIView *subview = [cell viewWithTag:42];
if (!subview) {
... Set up the new cell
}
else {
... Reuse the cell
}
return cell;
}
It's probably overkill compared to using the pre-iOS6 (no registered class) approach, but if you really want to stick with that, you can use associated objects.
#import <objc/objc-runtime.h>
static char cellCustomized;
...
-(UITableViewCell *)getCell
{
UITableViewCell *cell = [tableView dequeueReusableCellForIdentifier:myCell];
if(!objc_getAssociatedProperty(cell, &cellCustomized)) {
[self setupCell:cell];
objc_setAssociatedProperty(cell, &cellCustomized, #YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return cell;
}
...
(not tested)