I've subclassed UITableViewCell and registered it to my UITableView like so:
[self.tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:#"cell"];
But as I've created my own styles for these cells like so:
typedef enum : NSInteger {
MyTableViewCellStyleSent,
MyTableViewCellStyleReceived
} MyTableViewCellStyle;
Which is then used like this:
-(MyTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"cell"];
}
I'd like to have the initWithStyle: method use that enum, rather than the "default" UITableViewCellStyle.
From Apple Docs:
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.
But I cannot figure out how to get initWithStyle called, with my new enum.
What's the correct way to approach this?
You don't normally create your own styles and if you dequeue a reusable cell this way, it doesn't call initWithStyle:.
You should probably add your own "style" property to MyTableViewCell with a different name and then set that property once you dequeue the cell.
Another way is to have different cell classes if the design of the cell is different. They can have a common base class if they share a lot of code.
Related
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;
}
}
I'm trying understand how [tableView dequeueReusableCellWithIdentifier:CellIdentifier] works. Here is my situation:
I have designed my UITableViewCell subclass in IB and now I'm trying style my sub elements of the cell in my subclass. Unfortunately the method [tableView dequeueReusableCellWithIdentifier:CellIdentifier] seems to not call any method in my cells subclass. With methods I mean initWithCoder,initWithFrame or init.
This is my actual code:
static NSString *BasicCellIdentifier = #"BasicCell";
GSFeedBasicTableViewCell *basicCell = [tableView dequeueReusableCellWithIdentifier:BasicCellIdentifier];
My Cells are showing up but I would to customize the cell further in my subclass to avoid styling code in my TableController. Any hints are really appreciated.
When using XIB,you need to register first
[self.tableView registerNib:[UINib nibWithNibName:#"IBNameofthiscell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:#"youridentifier"];
Then you can use dequeueReusableCellWithIdentifier
If using storyboard,do not forget to set the class of this cell
to your custom class,and set identifier to your identifier.
3.Then use this function
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
When you dequeue a cell, your subclass's initWithCoder: method is called. So you can override it to implement custom behaviour. You can also perform your customization is the awakeFromNib method.
If you designed the cell completely in code, then the subclass's initWithStyle:reuseIdentifier: method will be called instead of initWithCoder:.
In my project I'm creating custom cells by subclassing UITableViewCell. When cellForRowAtIndexPath: is fired I do a pretty basic stuff like:
MyCustomCell *cell = [self.tableView dequeueReusableCellWithIdentifier:[MyCustomCell identifier]];
I don't want to manually configure cell properties in cellForRowAtIndexPath: so I thought I'd create a method inside MyCustomCell called configureWithModel: which is filling MyCustomCell with proper data. So far, so good! Now inside cellForRowAtIndexPath: I have something like:
MyCustomCell *cell = [self.tableView dequeueReusableCellWithIdentifier:[MyCustomCell identifier]];
[cell configureWithModel:model];
In configureWithModel: I assign some data (image also) to cell so as you'd guess it could be slow'n'heavy so I wonder if this is a good solution to have a method like this in subclass of MyCustomCell? What is more, how it's related to prepareForReuse?
Doing this [cell configureWithModel:model]; is the best approach because take for a case when you want to use configureWithModel: in more than 2 tableViews you can avoid code redundancy and cell level control would be there with cell itself.
Use of [cell configureWithModel:model]; will make your code look like more structured, but for image use the following delegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
Example :
- (void)tableView:(UITableView *)tableView willDisplayCell:(AlbumCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
AlbumBO *album = [self.arrAlbums objectAtIndex:indexPath.row];
dispatch_async(imageQueue_, ^{
UIImage *image = [self retrieveImageWithImageUrl:album.coverPhoto];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.imgVwAlbum setImage:image];
});
});
}
Here
AlbumCell is my Custom table cell
AlbumBO is the object for containing image object
And
[self retrieveImageWithImageUrl:album.coverPhoto]
is the user defined method to download image.
This sounds like a fairly decent usage of the singular responsibility principle. Where this might bite you is if your cells need to be binded with images that must be downloaded from a server. In this instance you don't want your cell responsible for triggering a download since the cell will then also be responsible for monitoring the progress of the download. Since these cells are reusable this becomes more problematic as the cell becomes reused.
So yes, in a simple case where you need to bind data to a cell it makes sense for the cell to be responsible for configuring its subviews with the relevant data.
Regarding prepareForReuse a casual glance at the documentation details
Discussion If a UITableViewCell object is reusable—that is, it has a
reuse identifier—this method is invoked just before the object is
returned from the UITableView method
dequeueReusableCellWithIdentifier:. For performance reasons, you
should only reset attributes of the cell that are not related to
content, for example, alpha, editing, and selection state. The table
view's delegate in tableView:cellForRowAtIndexPath: should always
reset all content when reusing a cell. If the cell object does not
have an associated reuse identifier, this method is not called. If you
override this method, you must be sure to invoke the superclass
implementation.
I have a static table with 6 cells and a couple sections. When I initialize a cell it always returns nil, although I have used this exact same method in the passed...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == SAVE_SECTION) {
ATSaveCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SaveCell"];
if(cell == nil) {
NSLog(#"nil cell");
cell = [[ATSaveCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"SaveCell"];
}
cell.textLabel.text = #"test";
return cell;
}
nil cell is always outputted. In my storyboard, I have a tableviewcontroller that has the cell defined and the id is "SaveCell". I have also checked to make sure the table ciew controller is the same class as the class I am working in... I have used this exact same method in the passed, so I am not sure why the cells are returning nil everytime.
Also, to initialize my tableviewcontroller:
ATSearchSettingsViewController *mySearchSettings = [sb instantiateViewControllerWithIdentifier:#"SearchSettings"];
It's pretty clear that the table view is not registering the prototype cells in the storyboard.
If the problem is the UITableView is not dequeuing a cell from a storyboard. Try checking that you are using prototype cells instead of static cells. The UITableView will not dequeue a static cell.
If the problem is that you are always calling [[ATSaveCell alloc] init]. The table view will need to if it doesn't have anything the reuse.
From the docs: dequeueReusableCellWithIdentifier
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.
Therefore, if there are not enough cells to fit the view, it will always create a new one.
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