I am reading the apple docs on setting up custom subclasses of UITableViewCell - Docs
In this example I need to setup a custom cell which does not have a NIB/storyboard file. The apple docs provide an example of using a predefined style and configuring that but not creating a completely custom layout.
How should the cell be called in.. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath ?
I am looking to have a completely custom layout so is this correct? As the cell is being called initWithStyle...?
MESLeftMenuCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
cell = [[MESLeftMenuCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
In the custom cell subclass how/where should I implement the setup of the views within the contentView?
Which method is called for the init, would it be initWithStyle as above? If so, can I simply create the cell outlets in there once only?
Then in the cellForRowAtIndexPath can I access the outlets as i.e. cell.MainLabel.text ... ?
This is how I have been shown to set up my Collection View Cells in their custom class. I know you are using a tableview but this is threw me for a while so decided to add here. Hopefully it helps you.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)encoder
{
self = [super initWithCoder:encoder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit
{
// set up your instance
}
To access the outlets of that cell I just add outlets to the header file of the custom class and you can easily access them.
As of iOS 6, you no longer need to check whether the dequeued cell is nil.
What you would do is as follows:
In the viewDidLoad method of the view controller containing the table view you could say
[self.tableView registerClass:[MyCellClass class] forCellReuseIdentifier:MyCellIdentifier];
This results in your dequeueReusablecellWithIdentifier call to never return nil. In essence, in the background, initWithStyle is called. So you would set your stuff up when overriding that function.
Related
I've created my own CustomTableView and CustomCell. The cell is in a xib, and I'm registering it when initializing the tableView, like this:
[self registerNib:[UINib nibWithNibName:#"CustomCell" bundle:nil]
forCellReuseIdentifier:kCustomCellIdentifier];
If I don't do this, I won't be able to define what ReuseIdentifier should "point" to this class. This is my cellForRow:
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = [self dequeueReusableCellWithIdentifier:
kCustomCellIdentifier forIndexPath:indexPath];
if(!cell)
{
cell = [[[NSBundle mainBundle] loadNibNamed:#"CustomCell"
owner:self options:nil] objectAtIndex:0];
cell.delegate = self;
[cell initialSetup]; //Other stuff
}
else
[cell resetCell];
//And other awesome stuff
return cell;
}
This 'works'. When I lauch my app, my own custom cells are showing.
However, it turns out the cell is NEVER returned as nil from [self dequeue... Thus, the if-statement if(!cell) is never true. I have additional setup inside this statement that I want to perform, but I don't know where the cell's are being initialized the first time now. If I remove registerNib, then this statement is true, but then it's true for all cells, and none will ever be dequeued.
I can probably work around this, and put my initialSetup (and other stuff) inside the -(id)initWithCoder..-method in the CustomCell-class, but I'd like to know where my cells are being initialized right now. Why do my cells exist before cellForRowAtIndexPath?
When you register a class or a nib in a table view using method registerClass:forCellReuseIdentifieror registerNib:forCellReuseIdentifier the tableview internally will create an instance of the cell if no one is available when you call dequeueReusableCellWithIdentifier:, so initialization code is no longer needed inside the delegate.
From the UITableView.h code:
// Beginning in iOS 6, clients can register a nib or class for each cell.
// If all reuse identifiers are registered, use the newer -dequeueReusableCellWithIdentifier:forIndexPath: to guarantee that a cell instance is returned.
// Instances returned from the new dequeue method will also be properly sized when they are returned.
- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
Depending of the which register method is used the init methods called are:
initWithStyle:reuseIdentifier for cells registered using registerClass:
initWithCoder: for cells registered using registerNib:
If you are using registerNib: you can use too awakeFromNib method in the cell, that is also a good place to put initialization code of the cell. The main difference between using initWithCoder: or awakeFromNib its explained in this question.
When a cell is reused, you have the method prepareForReuse in the cell to make some cleanup in the cell and left it prepared to be configured again.
A good approach to work with all of this will be:
//ViewController code
- (void)viewDidLoad
{
...
[_tableView registerNib:[UINib nibWithNibName:#"CellSample" bundle:Nil] forCellReuseIdentifier:#"cellIdentifier"];
...
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
//configure the new cell, no if (!cell) needed
return cell;
}
//Cell code
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
//You can put initialization here
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
//But is better initialize here
}
- (void)prepareForReuse
{
//Reuse and reset the cell here
}
Hope it helps
When using dequeueReusableCellWithIdentifier: forIndexPath: you don't have to alloc the cell like you would if you were using default UITableViewCell. In your custom UITableViewCell subclass, this is called when it is initialized:
- (void)awakeFromNib {
[super awakeFromNib];
}
So add that in there and you should be good.
- (instancetype)initWithStyle:(UITableViewCellStyle)style
reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
//custom your cell
}
return self;
}
I have custom UiTablleviewCell with some images and labels, and I would like to have rotated label in tableview cell...so I would like to edit initWithStyle method, but it seems like it's never called.
- (id)initWithStyle:(UITableViewCellStyle)stylereuseIdentifier:(NSString*)reuseIdentifier{
NSLog(#"creating cell");
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
}
return self;}
but in my log, I cant see this message. In tableview I have standard cellForRow method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"messagesCell";
TBCellMessagesCell *cell = (TBCellMessagesCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
// smt stuff
return cell;
}
so I'm wondering how does tableview initialize tableviewcells, I can think about some workarounds but I would like to have it clean.
Thank you.
If the cells come from a storyboard or nib file, then initWithStyle:reuseIdentifier is not called, initWithCoder: is called instead.
Here's a typical implementation of an overwritten initWithCoder::
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Do your custom initialization here
}
return self;
}
Will not work if you need to access IBOutlet during custom initialization.
You might also consider the awakeFromNib method if you need to talk with IBOutlet.
Discussion
The nib-loading infrastructure sends an awakeFromNib message to each object recreated from a nib archive, but only after all the objects in the archive have been loaded and initialized. When an object receives an awakeFromNib message, it is guaranteed to have all its outlet and action connections already established.
I have created .h and .m files for UITableView called mainTableViewgm.h and mainTableViewgm.m resp. and I am calling -initWithFrame: method from my main view controller to this mainTableViewgm.m implementation file
[[mainTableViewgm alloc]initWithFrame:tableViewOne.frame]
Note that this tableview is in my main view controller. But I have created separate files for the tableView and have also set the custom class to mainTableViewgm in storyboard.
the -initWithFrame: methods appears as follows
- (id)initWithFrame:(CGRect)frame
{
//NSLog(#"kource data");
self = [super initWithFrame:frame];
if (self)
{
[self setDelegate:self];
[self setDataSource:self];
[self tableView:self cellForRowAtIndexPath:0];
[self tableView:self numberOfRowsInSection:1];
// Initialization code
}
return self;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"kource data");
return 1;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"kource data2");
UITableViewCell*cellOne =[[UITableViewCell alloc]init];
cellOne.detailTextLabel.text=#"text did appear";
return cellOne;
}
the -initWithFrame: is being called fine along with the 'if (self)' block in this method. But the problem is numberOfRowsInSection: and cellForRowAtIndexPath: are not being automatically called here . kource data/kource data2 never appear in log. What do I do to load the table? Are the delegate/datasource being set incorrectly?
I must mention that I have also set the UITableViewDelegate and UITableviewDataSource protocols:
#interface mainTableViewgm : UITableView <UITableViewDelegate,UITableViewDataSource>
#end
Help will be much appreciated. Thank you.
Your tableview is not loaded when the controller is initializing, so you cannot do that in the init methods. You have to move your code to the viewDidLoad method.
Also you are not setting the delegate and datasource on the tableview object (probably a type, you are setting them on the view controller). It should look like this:
- (void)viewDidLoad:(BOOL)animated {
[super viewDidLoad:animated];
[self.tableView setDelegate:self];
[self.tableView setDataSource:self]; // <- This will trigger the tableview to (re)load it's data
}
Next thing is to implement the UITableViewDataSource methods correctly. UITableViewCell *cellOne =[[UITableViewCell alloc] init]; is not returning a valid cell object. You should use at least initWithStyle:. And take a look how to use dequeueReusableCellWithIdentifier:forIndexPath:. A typical implementation would look like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
// Reuse/create cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Update cell contents
cell.textLabel.text = #"Your text here";
cell.detailTextLabel.text=#"text did appear";
return cell;
}
I can't believe I've been doing XCode programming for two years, and still hit this issue.
I had the same problem with XCode 6.1 - I was setting my UITableView's delegate & dataSource in the viewWillAppear function, but none of the delegate functions were kicking in.
However, if I right-clicked on the UITableView on the Storyboard, the circles for delegate and dataSource were empty.
The solution, then, is to hold down the CTRL key, and drag from each of these circles up to the name of your UIView which contains your UITableView:
After doing this, my UITableView happily populated itself.
(So, we're upto v6.1 of XCode now are we ? Do you think Apple ever going to make this thing, you know, friendly...? I would quite like to add a Bookmark in my code... that'd be a nice feature.)
I'm using Xcode 5, and want to be using the recommended best practices from Apple, which includes dynamic prototype cells and using registerClass:forCellReuseIdentifier.
I have created a storyboard and dropped a UITableView with 1 prototype dynamic cell on it. I've set the class of the cell to ItemCell, and set the reuse identifier to ItemCell.
The ItemCell class contains a nameLabel IBOutlet, which I've connected to the label within the prototype cell by dragging.
In the ViewController, I register the ItemCell class to be used for the ItemCell reuse identifier:
- (void)viewDidLoad {
[super viewDidLoad];
[_tableView registerClass:[ItemCell class] forCellReuseIdentifier:#"ItemCell"];
}
In tableView:cellForRowAtIndexPath, I dequeue the cell and set the properties for the nameLabel. self.items is an NSArray of strings.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ItemCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ItemCell" forIndexPath:indexPath];
cell.nameLabel.text = [self.items objectAtIndex:indexPath.row];
return cell;
}
So: it's being created as an ItemCell, but it's not loading it from the storyboard. I've confirmed this by overriding initWithStyle:reuseIdentifier and initWithCoder, to see which was being called:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
NSLog(#"NOT STORYBOARD");
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
NSLog(#"Yay, it's working!");
}
return self;
}
Every time, it's initWithStyle that's being called. From everything I've read, this should be working. I can't find anything that indicates I need to somehow register the class differently when it's in a storyboard, but clearly the cell isn't aware that it's got a storyboard associated with it.
I'm sure I'm making a total newbie mistake, but I can't figure it out. Any help would be appreciated.
You don't have to (and should not) call registerClass for prototype cells defined in the storyboard. initWithCoder is called automatically if a prototype cell has to
be instantiated from the storyboard.
This is working fine for my plain style table views, but not for my grouped style. I'm trying to customize how the cell looks when it is selected.
Here is my code:
+ (void)customizeBackgroundForSelectedCell:(UITableViewCell *)cell {
UIImage *image = [UIImage imageNamed:#"ipad-list-item-selected.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
cell.selectedBackgroundView = imageView;
}
I have verified that the correct cell is indeed being passed into this function. What do I need to do differently to make this work?
It's not clear from your question whether or not you're aware that the tableViewCell automatically manages showing/hiding it's selectedBackgroundView based on its selection state. There are much better places to put that method other than in viewWillAppear. One would be at the time you initially create the tableViewCells, i.e.:
- (UITableViewCell *)tableView:(UITV*)tv cellForRowAtIP:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
cell = [tv dequeueCellWithIdentifier:#"SomeIdentifier"];
if (cell == nil) {
cell = /* alloc init the cell with the right reuse identifier*/;
[SomeClass customizeBackgroundForSelectedCell:cell];
}
return cell;
}
You only need to set the selectedBackgroundView property once in the lifetime of that cell. The cell will manage showing/hiding it when appropriate.
Another, cleaner, technique is to subclass UITableViewCell, and in the .m file for your subclass, override:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithBla....];
if (self) {
UIImageView *selectedBGImageView = /* create your selected image view */;
self.selectedBackgroundView = selectedBGImageView;
}
return self;
}
From then on out your cell should show it's custom selected background without any further modifications. It just works.
Furthermore, this method works better with the current recommended practice of registering table view cell classes with the table view in viewDidLoad: using the following UITableView method:
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier
You would use this method in your table view controller's viewDidLoad method, so that your table view cell dequeuing implementation is much shorter and easier to read:
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[SomeClass class]
forCellReuseIdentifier:#"Blah"];
}
- (UITableViewCell *)tableView:(UITV*)tv cellForRowAtIP:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:#"Blah"
forIndexPath:indexPath];
/* set your cell properties */
return cell;
}
This method is guaranteed to return a cell as long as you have registered a class with the #"Blah" identifier.