I am using the QuickBlox framework to build a chatting app. Below is the cellForRowAtIndexPath code.
There are some things I'd like to do with each cell only once (like download images), so as I understood, I should add the if (!cell) block to do this.
However, that block never actually fires, even when the tableview is loading for the first time. Why would that be?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
QBChatMessage *message = [[ChatService shared] messagsForDialogId:self.dialog.ID][indexPath.row];
ChatMessageTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ChatMessageCellIdentifier];
if (!cell) {
// some things I only want to do once here, such as download images. but it never fires
}
[cell configureCellWithMessage:message];
return cell;
}
The call to dequeueReusableCellWithIdentifier: will only return nil if you didn't register a cell using registerClass:forCellReuseIdentifier:.
Remove your use of registerClass:forCellReuseIdentifier: then dequeueReusableCellWithIdentifier: can return nil and you can create a new cell and initialize it properly in your if statement:
if (!cell) {
cell = [[ChatMessageTableViewCell alloc] init...]; // use proper init method
// setup cell as needed for first time
}
Instead you could perform the one time actions inside the initialiser of the ChatMessageTableViewCell class. That way you could keep the behaviour you are used to with registering.
Related
I have slightly different cells and for such purpose I need to pass parameter from my UIViewController to my subclass of UITableViewCell. But it does not work. The scenario is written as below:
MessagesViewController.m :
#import "MessagesViewController.h"
#import "MessageTableViewCell.h"
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[MessageTableViewCell class] forCellReuseIdentifier:MessengerCellIdentifier];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MessageTableViewCell *cell = (MessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:MessengerCellIdentifier];
if (cell == nil) {
cell = [[MessageTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MessengerCellIdentifier customParam:YES];
}
return cell;
}
MessageTableViewCell.m :
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier customParam:(BOOL)customParam
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// **** GET Custom Parameter (customParam) HERE ??? ****/
}
return self;
}
At this scenario customParam is my parameter. Everything seems as OK, but cell is not nil and so the procedure fails.
Option 1: Remove the dequeueReusableCellWithIdentifier line so that you create a new custom cell every time. Otherwise you are using a pre-existing cell that already has the previous customParameter set to whatever the last cell displayed was set to.
-Note to option 1 (added as explanation of why it is a very, VERY, VERY bad idea (#Duncan C). Since you are setting up your cells with a ReuseIdentifier in creating your cells iOS will hold on to them for you once they scroll offscreen so that you can reuse them when your code asks for it. But then your code never asks for reusable cells because it makes a completely new one each time the table asks for the next cell. This causes high load times (to create a new cell every time) and high memory use (since the OS is saving the cells for you to use later and not deallocating them immediately). The reusability was built for a reason, so don't use Option 1 unless you have very specific need to do so (and even then, you are probably wrong, don't do it).
Option 2: Change the custom parameter to a separate method call. Instead of in the initializer create a new method that clears the cell and rebuilds it the way your new custom parameter requires. Then you can re-use cells and modify their looks using the new setCustomParameter: method.
Edit: Code example of option 2, as simple as possible:
In table controller
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MessageTableViewCell *cell = (MessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:MessengerCellIdentifier];
if (cell == nil) {
cell = [[MessageTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MessengerCellIdentifier];
}
[cell setCustomParam:customParam];
return cell;
}
In your cell .m
-(void)setCustomParam:(ParamType)type
{
//Do whatever you would like right here to clear the previous
//cell's custom information and add the new custom information
//to this new cell.
}
Then you have to try some another method to add parameters in the MessageTableViewCell. The parameter is nil because the cell aren't nil everytime they are reusing the table view cell from the line
MessageTableViewCell *cell = (MessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:MessengerCellIdentifier];
You have to call another method like
MessageTableViewCell *cell = (MessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:MessengerCellIdentifier];
if (customParam)
{
// IF custom parameter is your labal
cell.yourCustomParameter.text = #"Add your content here"
}
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.
I would like to customize a simple UITableViewCell so that I run the customization only once and add values (e.g., cell title) later. My app's cell is more complex - it has subviews and uses auto layout; however, a simple example, I believe, will help in focusing on the objective.
I am using iOS 8, Xcode 6.X, Objective-C and Nibs (no storyboard) to keep it simple. I have not created a custom class for UITableViewCell. Instead, I have the following code:
- (void)viewDidLoad {
[super viewDidLoad];
//[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1; //FIXED VALUE FOR EXAMPLE'S SAKE
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 3; //FIXED VALUE FOR EXAMPLE'S SAKE
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tableView:cellForRowAtIndexPath:");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
NSLog(#"cell == nil");
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
//CUSTOMIZING CELL THAT I WANT TO RUN ONLY ONCE
cell.backgroundColor = [UIColor redColor];
}
NSArray *numbersArray = #[#1,#2,#3];
cell.textLabel.text = [NSString stringWithFormat:#"%#", numbersArray[indexPath.row]];
return cell;
}
Which outputs:
tableView:cellForRowAtIndexPath:
cell == nil
tableView:cellForRowAtIndexPath:
cell == nil
tableView:cellForRowAtIndexPath:
cell == nil
FIRST QUESTION: Why is cell == nil run 3 times? It seems wasteful to run the customization code cell.backgroundColor = [UIColor redColor]; 3 times.
Now, when I enable:
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
And use:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Instead of:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
I get the output:
tableView:cellForRowAtIndexPath:
tableView:cellForRowAtIndexPath:
tableView:cellForRowAtIndexPath:
SECOND QUESTION: Why isn't cell == nil run at all?
FINAL QUESTIONS: How can I make cell == nil run only once so that I format the UITableViewCell only once? Is there a better way to customize a simple cell, running the customization code only once?
Why is cell == nil run 3 times? It seems wasteful to run the customization code cell.backgroundColor = [UIColor redColor]; 3 times.
The table view most likely displays three cells at once, hence requiring three distinct cell objects.
Why isn't cell == nil run at all?
The documentation states that -dequeueReusableCellWithIdentifier:forIndexPath: always returns a valid cell if you registered the identifier previously. It basically takes care of checking if a new cell is required for you.
How can I make cell == nil run only once so that I format the UITableViewCell only once?
You don't. You will have to customize every single instance. I would recommend to use a custom subclass though, rather then messing with UITableViewCell from the outside.
The best way to do this, is to create a custom class for your cell, and do any customization that isn't dependent on the indexPath there. Usually, I do this in initWithCoder or awakeFromNib. You should register the nib in viewDidLoad; I don't see anything wrong with the code you mention in your comment to Christian's answer, unless the name of the file is wrong. It really isn't the view controller's business to be adding subviews or customizing your cell; that code belongs in the cell's class.
BTW, this doesn't keep the customization code from running multiple times. It needs to run once for each cell instance that you create, just like it does in your original code. The number of cells created will be equal to the number that fit on the screen at one time (plus one maybe).
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 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.