I have a UITableView embedded inside a parent UIView. I have a CustomUITableViewController class set as delegate and datasource for the tableview.
After a certain background operation, I get an updated array of objects to be displayed in the tableview.
When I update the datasource array and call tableview.reloadData method, the tableview doesn't refresh. It only refreshes if I scroll the tableview.
However, if I call the API as follows:
tableview.beginUpdates -> tableview.reloadSections -> tableview.endUpdates,
it works perfectly and immediately reloads the table.
The problem is that depending on the new data, I have to add a new section, or remove an old section from the tableview.
Hence I am not able to use the reloadSections API.
Any thoughts on how to fix this?
Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = #"tempCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
[cell initializeWithModel:modelsToShow[indexPath.row]];
return cell;
}
-(void) showModelsInList:(NSMutableArray*) models {
[modelsToShow removeAllObjects];
[modelsToShow addObjectsFromArray:models];
[self setupDataForList];
[self reloadTable];
}
-(void) reloadTable {
[self.tableView beginUpdates];
NSMutableIndexSet* index = [[NSMutableIndexSet alloc]init];
[index addIndex:0];
[self.tableView reloadSections:index withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
//[self.tableView reloadData]
}
The showModelsInList method is invoked from the other class, in the main thread itself.
The modern way to initialize table view cells is to register the cell class (or nib, if the cell is defined in its own nib). viewDidLoad is a good time to do this...
// if the cell is a prototype defined in the nib containing the table view, or if
// the cell is built in code in its init method
[self.tableView registerClass:[CustomCell self] forCellReuseIdentifier:#"tempCell"];
// or, if the cell is defined in its own nib
UINib *nib = [UINib nibWithNibName:#"your cell's nib name goes here" bundle:nil];
[_tableView registerNib:nib forCellReuseIdentifier:#"tempCell"];
In either case above, the cell must have it's "tempCell" identifier initialized in IB or in code. Then, in cellForRowAtIndexPath, dequeue the cell using the method...
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:#"tempCell" forIndexPath:indexPath];
No further check is required to see if (cell == nil). This version of dequeue will just work (or crash, if something's not setup correctly).
I think, technically, it's a bug, but the truth is that, though it's not documented, you shouldn't be recreating subviews in cellForRowAtIndexPath when reusing cells.
Create the cells with all needed subviews at design time in Interface Builder. Changing their positions, sizes, and other properties in cellForRowAtIndexPath is okay.
If your cells have different subviews, each cell "type" should be its own class. Create a different prototype cell class with a different identifier for each, and simply use that identifier when you dequeue the cell. That way, you have the proper cell class in cellForRowAtIndexPath.
To reference additional properties (subviews) from your view controller, simply create class files for each cell type (derived from UITableViewCell). Assign it to the prototype UITableViewCell in IB, drag the views to the .h file to create outlets like you do for a view controller, then import that class in your view controller.
So, you might end up with code like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (whatever) {
MyBasicCell *cell = [tableView dequeueReusableCellWithIdentifier:#"basicCell"];
cell.specialLabel.Text = ...
return cell;
} else {
MyOtherCell *cell = [tableView dequeueReusableCellWithIdentifier:#"otherCell"];
cell.otherLabel.Text = ...
return cell;
}
}
Related
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 have a few UITableViewCells that I am loading from an XIB file. Everything is great until I call the [UITableView reloadRowsAtIndexPaths:withRowAnimation:] method when the cell disappears. When I call [UITableView reloadData] everything loads find, when I scroll the cell on and off the view it will also reappear also. Weird.
I've also noticed that when I call [UITableView reloadRowsAtIndexPaths:withRowAnimation:] the UITableView will not try to reuse a cached cell and will try to get a new one with cell = [tableViewCells objectAtIndex:cellId];
Here is my code:
- (void)viewDidLoad
{
...
// the objects from the XIB files are loaded onto an NSArray instance variable at viewDidLoad
tableViewCells = [[NSBundle mainBundle] loadNibNamed:#"ProfileTableViewCell" owner:nil options:nil];
...
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
int cellId = /* some logic to get the correct cellID */
NSString *CellIdentifier = [NSString stringWithFormat:#"ProfileTableViewCell_%d", cellId];
ProfileTableViewCell *cell = (ProfileTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [tableViewCells objectAtIndex:cellId];
// additional work to setup subviews for the cell
[cell prepareSubviews];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
}
and just in case here is some of the stuff I'm doing in [ProfileTableViewCell prepareSubviews]
- (void)prepareSubviews
{
...
[self.containerView.layer setCornerRadius:3];
[self.containerView.layer setBorderColor:UIColorFromRGB(0xCDCDCD).CGColor];
[self.containerView.layer setBorderWidth:2.0];
[self.containerView.layer setMasksToBounds:YES];
[self.containerView.layer setShouldRasterize:NO];
[self.containerView.layer setRasterizationScale:[UIScreen mainScreen].scale];
...
}
Thanks in advance to the awesome person that can help me out.
I'm not sure this is your problem, but the way you get your cells is not the normal way. You should make each type of cell in its own nib (with a unique identifier), and then register the nibs with registerNib:forCellReuseIdentifier:. In cellForRowAtIndexPath, just dequeue the cell you need based on the index path, and don't put anything in an if (cell == nil) clause, because that will not be invoked when you do it this way.
It seams that reloadRowsAtIndexPaths: doesn't work when you only need to change the height for a static cell.
Try to call just
[tableView beginUpdates];
[tableView endUpdates];
As the documentation says:
You can also use this method followed by the endUpdates method to
animate the change in the row heights without reloading the cell.
I want to alter the font size and color etc. for my UITableView cells. I've designed the cells custom in Xcode and got everything working.
First of I'll post my code here:
UITableViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:MainCategoryTableViewCell.class forCellReuseIdentifier:#"MainCategoryCell"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MainCategoryCell";
MainCategoryTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
And my custom cell:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.title.font = [Theme tableCellTitleFont];
self.title.textColor = [Theme tableCellTitleColor];
self.subcategories.font = [Theme tableCellSubTitleFont];
self.subcategories.textColor = [Theme tableCellSubTitleColor];
self.costs.font = [Theme tableCellValueFont];
self.costs.textColor = [Theme tableCellValueColor];
}
return self;
}
I'm confused now how this dequeue works:
As far as I understood if I register the class in the viewDidLoad, the initWithStyle method of the cell gets ONLY called, when theres no cell for reuse. If theres a cell for reuse it will be used. I've seen a lot of if(cell == nil) calls in other code snippets but is that really necessary? I thought the registerClass method takes care of that anyway?
And at the moment my cells will be displayed completely empty. Before I registered the class everything worked, however the initWithStyle didn't get called..
Complete cellForRowAtIndexPathMethod:
#pragma mark Delegate methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MainCategoryCell";
MainCategoryTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
MainCategory *mainCategory = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.title.text = mainCategory.name;
cell.subcategories.text = [NSString stringWithFormat:#"%i subcategories", [[mainCategory getNumberOfSpendingCategories] integerValue]];
cell.costs.text = [[mainCategory getMonthlyCostsOfAllSpendingCategories] getLocalizedCurrencyString];
if(!mainCategory.icon){
cell.icon.image = [UIImage imageNamed:#"DefaultIcon.png"];
} else {
cell.icon.image = [UIImage imageNamed:mainCategory.icon];
}
if(!mainCategory.color){
cell.backgroundColor = [PresetColor colorForPresetColor:PresetColorsWhite];
} else {
cell.backgroundColor = [PresetColor colorForPresetColor:(PresetColors)[mainCategory.color intValue]];
}
cell.cellBackground.image = [[UIImage imageNamed:#"content-bkg"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
return cell;
}
If you have defined the cell as "prototype cell" for the table view in the xib/storyboard file, then you don't have to register it at all. If the custom cell is in a separate nib file, you register the custom cell with registerNib, not registerClass. For example:
[self.tableView registerNib:[UINib nibWithNibName:#"MainCategoryTableViewCell" bundle:nil]
forCellReuseIdentifier:#"MainCategoryCell"];
For cells instantiated from a nib file, initWithCoder is called, not initWithStyle.
To configure any outlets of your custom cell, override awakeFromNib. The connections are
not yet established in initWithCoder.
For best understanding see the below image for just a deque reference.
Deque means you can add and delete cells from both the ends.
By ends I mean up and down.
Lets say you have 4 cell containg Acell,Bcell,Ccell and Dcell and height for row is for three cells.
so at a time only 3 cells would be visible.
when you scroll to see the Dcell , Acell would become as invisible row and memory for it will be reused for Dcell.
In the same way when you scroll to see the Acell , Dcell would become as invisible row and memory for it will be reused for Acell.
It says clearly in documentation
dequeueReusableCellWithIdentifier:forIndexPath:
For performance reasons, a table view's data source should generally
reuse UITableViewCell objects when it assigns cells to rows in its
tableView:cellForRowAtIndexPath: method. A table view maintains a
queue or list of UITableViewCell objects that the data source has
marked for reuse. Call this method from your data source object when
asked to provide a new cell for the table view. This method dequeues
an existing cell if one is available or creates a new one based on the
class or nib file you previously registered.
.
dequeueReusableCellWithIdentifier:
Return Value : A UITableViewCell object with the associated identifier
or nil if no such object exists in the reusable-cell queue.
Discussion : For performance reasons, a table view's data source
should generally reuse UITableViewCell objects when it assigns cells
to rows in its tableView:cellForRowAtIndexPath: method. A table view
maintains a queue or list of UITableViewCell objects that the data
source has marked for reuse. Call this method from your data source
object when asked to provide a new cell for the table view. This
method dequeues an existing cell if one is available or creates a new
one using the class or nib file you previously registered. If no cell
is available for reuse and you did not register a class or nib file,
this method returns nil.
If you registered a class for the specified identifier and a new cell
must be created, this method initializes the cell by calling its
initWithStyle:reuseIdentifier: method. For nib-based cells, this
method loads the cell object from the provided nib file. If an
existing cell was available for reuse, this method calls the cell’s
prepareForReuse method instead.
Before introducing storyboard.The tableview checks the returned cell which can be nil .So if nil we must reallocate the cell and tehn initialize and provide the cell in the datasource method
I followed Connect outlet of a Cell Prototype in a storyboard
So my outlets are connected. I have this method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
HomeCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
cell = [[HomeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"] ;
}
// Configure the cell.
Group * group = [self.groups objectAtIndex: [indexPath section]];
Friend * friend = [[group friends ]objectAtIndex: [indexPath row]];
NSLog(#"%#", friend.name);
[[cell nameLabel] setText:friend.name];
[[cell statusLabel] setText:friend.status];
[[cell picImageView] setImage:[UIImage imageNamed:#"similey.jpg"]];
return cell;
}
It compiles fine but the cells show up blank with nothing in it. Any suggestions?
Did you set the identifier of the cell in the storyboard to "Cell" to match your dequeueReusableCellWithIdentifier call?
Interface Builder creates objects, and then allows you to replicate them. It doesn't change the definition of a class. So if you design an instance of a class in Interface Builder, you're only designing that single instance. Loading Storyboards and NIBs are mechanisms you can use to replicate that instance.
Designing an instance of a class in IB, and then later going and doing a direct alloc+init of that class will result in an instance completely independent of the one in Interface Builder.
This following line of code indicates to me that you're expecting the instance in the storyboard to have an influence on what happens when you alloc+init your class directly:
[[HomeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"]
If you're ever hitting that line, that's probably your problem. That line shouldn't be there if you created the cell in the storyboard. For cells created in the storyboard, -dequeueReusableCellWithIdentifier: will always return an instance of the cell provided that you matched up the identifiers.
Since HomeCell is a custom UITableViewCell it won't be in a reuse queue, so your table view will need to know about it through the registerClass method, like this:
- (void)viewWillAppear:(BOOL)animated
{
...
[self.tableView registerClass:[HomeCell class] forCellReuseIdentifier:#"Cell"];
}
I have a problem with my cell textfield values when scrolling on a UITableView. When I scroll down and hide a custom cell, the value of the textField is deleted. The dequeueReusableCellWithIdentifier method doesn't work. I have this:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SectionsTableIdentifier = #"MyCustomCell";
MyCustomCell *cell = (MyCustomCell *) [tableView dequeueReusableCellWithIdentifier:SectionsTableIdentifier];
if (cell == nil) {
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:#"MyCustomCell" owner:self options:nil];
cell = [objects objectAtIndex:0];
}
cell.labelCustomAttribute.text= #"Attribute Name";
cell.textFieldCustomAttribute.delegate = self;
return cell;
}
I find it easier to register the custom cell with the tableView in the viewDidLoad method and then simply use dequeueReusableCellWithIdentifier. If you register the cell, the dequeue method will automatically pick up a reusable cell OR allocate a new custom cell (if none is available).
Example:
-(void)viewDidLoad
{
[super viewDidLoad];
// Get a point to the customized table view cell for MyCustomCell
UINib *myCustomCellNib = [UINib nibWithNibName:#"MyCustomCell" bundle:nil];
// Register the MyCustomCell with tableview
[[self tableView] registerNib:myCustomCellNib forCellReuseIdentifier:#"MyCustomCell"];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SectionsTableIdentifier = #"MyCustomCell";
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:SectionsTableIdentifier];
cell.labelCustomAttribute.text= #"Attribute Name";
cell.textFieldCustomAttribute.delegate = self;
return cell;
}
Normally the reuseIdentifier is assigned in the UITableViewCell's initWithStyle:reuseIdentifier: method, which you are not using because you are loading your view from a Nib.
You cannot set this property after because it is read only.
Maybe you can try instanciating the cell using the standard initWithStyle:reuseIdentifier: and add the view from your Nib as a subview of the cell's ContentView...
Now what is happening in your case is that you create a new cell every time that the Table View needs to display one. Clearly, this is not going to work. Actually, if you were reusing cells, you would have to also store the content of your text field somewhere (preferably in your data source) and put it when you reuse the cell. If you do not store it, when the cell is going to be reused, it will contain the data from the previous row in which it was displayed.