How to set property of different custom UITableViewCell - ios

First of all, this is how I dequeue my cells:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
switch (indexPath.row) {
case 0:
{
UINib *nib = [UINib nibWithNibName:#"TableViewCellLabel" bundle:nil];
[tableView registerNib:nib forCellReuseIdentifier:#"CellLabel"];
cell = (TableViewCellLabel *)[tableView dequeueReusableCellWithIdentifier:#"CellLabel"];
}
break;
case 1:
{
UINib *nib = [UINib nibWithNibName:#"TableViewCellTextfield" bundle:nil];
[tableView registerNib:nib forCellReuseIdentifier:#"CellTextfield"];
cell = (TableViewCellTextfield *)[tableView dequeueReusableCellWithIdentifier:#"CellTextfield"];
}
break;
default:
break;
}
And then, I would set their property in willDisplayCell delegate method:
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
// set transparent cell bg
[cell setBackgroundColor:[UIColor clearColor]];
// init cell view
switch (indexPath.row) {
case 0:
{
((TableViewCellLabel *)cell).tableViewCellType = TableViewCellTypeLabel;
((TableViewCellLabel *)cell).delegate = self;
}
break;
case 1:
{
((TableViewCellTextfield *)cell).tableViewCellType = TableViewCellTypeTextfield;
((TableViewCellTextfield *)cell).delegate = self;
}
break;
default:
break;
}
}
My expected result was two different tableViewCellType property value, but it ended up two cells holding the same type TableViewCellTypeLabel. I can't figure out where have I done wrong so I need some help. Thank you.

At first register nibs in tableView at viewDidLoad() of controller.
Then, if you separate your table view for two sections, in first you want to display cells of CellLabel type, and in second section cells of CellTextfield type, you should switch case not row of IndexPath in cellForRowAtIndexPath method, but indexPath.section (you must return 2 sections count in dataSource method) and not to register anything in there. If you need just 2 rows - don't change your switch indexPath.row code.
Next you should setup your cell (set delegate and tableViewCellType) in cellForRowAtIndexPath method right after dequeue. This will allow you not to lead type implicitly second time.
And at the end of cellForRowAtIndexPath method you return you cell instance configured.
Example in swift (in obj-c same, structure is important here):
override func viewDidLoad() {
super.viewDidLoad()
var nib = UINib.init(nibName: "TableViewCellLabel", bundle: nil)
self.tableView.register(nib, forCellReuseIdentifier: "CellLabel")
nib = UINib.init(nibName: "TableViewCellTextfield", bundle: nil)
self.tableView.register(nib, forCellReuseIdentifier: "CellTextfield")
}
CellForRow:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
switch indexPath.section {
case 0:
guard let cellLabel = tableView.dequeueReusableCell(withIdentifier: "CellLabel") as? TableViewCellLabel else {
return cell
}
cellLabel.tableViewCellType = TableViewCellTypeLabel
cellLabel.delegate = self
return cellLabel
case 1:
guard let cellField = tableView.dequeueReusableCell(withIdentifier: "CellTextfield") as? TableViewCellTextfield else {
return cell
}
cellField.tableViewCellType = TableViewCellTypeTextfield
cellField.delegate = self
return cellField
default:
return cell
}
}
And clear for me, please: you return 1 section in numberOfSections and 2 in numberOfRowsInSection? If you have static number on cells in project use static cells: initialize them separately, keep lazy properties, return when you exactly want it to be returned. And this case you will not use dequeueReusableCell.

Related

Reuse error tableView with cell design programmatically

I have a problem with my tableView who I managed specially, i need to delete and add row really often. My cell are designed programmatically. I update my array who depend my cells and called self.tableView.reloadData() but this don't remove the cells I need and update the tableView like my array.
Cause to the reuse and my design of cell (programmatically) I need to check if the cell is always design or not. And the problem come from here.
When I called tableView.reloadData() my data are not properly reload, so I need to delete All view in the cells: indicate that the cells are not design, to design the new cell ... Of course I can just update the visible cells (with tableView.visibleCells), so this work but how can I update my other not-visible cells ?
Maybe I have an architecture problem? If so, what is the best way to delete and insert a row in the TableView with a indexPath defined? Or, how programmatically design the cell only one time?
Code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return user.lobbySurvey.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:"Celll") as! CardCell
for survey in user.lobbySurvey{
let index = user.lobbySurvey.index(where: {
//get the current index is nedeed else the cells reuse lazy
$0 === survey
})
if indexPath.row == index{
var surveyState : UserSurvey.state
surveyState = survey.state
switch surveyState{
case .selectSurvey:
cell.drawCard(statutOfCard: .selectSurvey)
case .goSurvey:
cell.drawCard(statutOfCard: .goSurvey(picture: survey.picture))
case .surveyEnded:
print("survey Ended")
case .surveyWork:
print("survey in progress to vote")
case .surveyWaiting:
cell.drawCard(statutOfCard: .surveyWaiting(selfSurveyId: survey.id, timeLeft: survey.timeLeft, picture: survey.picture))
case .buyStack:
cell.drawCard(statutOfCard: .buyStack(supView : self.view))
}
}
}
cell.delegate = self
cell.delegateCard = self
cell.layer.backgroundColor = UIColor.clear.cgColor
cell.backgroundColor = .clear
tableView.backgroundColor = .clear
tableView.layer.backgroundColor = UIColor.clear.cgColor
return cell
}
You can have an array which is the model of your table:
NSMutableArray *model; (model can have identifier).
You can change this model whenever you want.
With this you can make your table dynamic just calling tableView.reloadData() and make whatever in cellForRow & heightForRow
- (CGFloat) tableView: (UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height;
YourModel *model = self.model[indexPath.row];
if ([model isKindOfClass:[SomeClassTableViewCellModel class]]) {
height = 50;
} else if([model.identifier isEqualToString:#"Whatever"]){
height = 0.0f;
else{
height = kHeightForNormalCells;
}
return height;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
YourModel *model = self.model[indexPath.row];
cell = [tableView dequeueReusableCellWithIdentifier:model.identifier forIndexPath:indexPath];
if ([cell isKindOfClass:[SomeClass class]]) {
NSLog(#"Configure Cell");
[cell setModel:model];
}
return cell;
}
Moreover, your cell should have a method setModel:
- (void)setModel:(SomeTableViewCellModel *)model {
_model = model;
self.label1.text = [NSString stringWithFormat:#"%#",model.value1];
self.label2.text = [NSString stringWithFormat:#"%#",model.value2];
}
Hope this helps you.

Dynamically add subview to UITableViewCell

I'm trying to add some subviews to my UITableViewCell. The number of subviews is based on my data. When I scroll down the subviews disappears and does not show any more. Adding them to the NIB is no option because I only now the number of subviews at runtime and they are different for each cell.
What is the right way to add an unknown number of subviews to a UITableViewCell at runtime?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"DetailCell";
DetailCellTableViewCell *cell = (DetailCellTableViewCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"DetailCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
NSInteger count = [self getMaxSubviews];
NSInteger y=100;
for (int i=0; i<count;i++)
{
UITextField *dataS = [[UITextField alloc] init];
dataS.frame=CGRectMake(277, y, 60, 17);
y=y+17;
dataS.tag=i+1337;
dataS.backgroundColor=[UIColor redColor];
[cell addSubview:dataS];
}
}
if (!useOrigCellFromNib) // Here I can use the original Nib created by IB
{
NSString *data = #"Some String";
[cell.data setText:data];
}
else // Use added subviews!
{
for (int i=0;i<arrS.count;i++)
{
NSManagedObject *s = [arrS objectAtIndex:i];
UITextView *dataS =[cell viewWithTag:i+1337];
dataS.text=[NSString stringWithFormat:#"%ld foo", (long)i];
[cell.data setHidden:YES];
}
}
return cell;
}
Like Igor mentioned when reusing cell you have to remove waht ever you add previousely and re-create subviews.
May be you can not use "loadFromNib" and Subclass 'UITableViewCell' class and create your cell there.
This is a example in swift but logic is same for ObjC too
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let stuffArray = array[indexPath.row]
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")
if cell == nil {
cell = MyCustomCell(initWithDaraArray:stuffArray) // create cell based on array data dynamically
} else { // even if you have cell you need to refresh it for new data
cell.refreshDataForDataInArray(stuffArray) // here remove all subviews and create new ones
}
return cell
}
and cell heights can be adjusted by
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let stuffArray = array[indexPath.row]
return calculatedHeight(stuffArray)
}
1 you should reuse cells, call tableView.dequeueReusableCellWithIdentifier("CellId")
2 after you get the reused cell, you should delete all previously added custom subviews
3 after that you can add new subviews
about " I scroll down the subviews disappears and does not show any more"
I don't see any "cell" variable before
if (cell == nil)
So Probably you do not paste the reuse code here, in this case cells after scrolling will not be nil and the code under the if (cell == nil) will not be called...

How can I get the class (custom) of cell selected in UITableView?

Usually I get my selected cell this way:
- (void)tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = (CustomCell*) [table cellForRowAtIndexPath:indexPath];
}
But in the code I'm working with, I may have many kind of cells in my table view. How can I get the class of my selected cell (if it's for example CustomCell or CustomCell2) ?
You can check the type of cell returned
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell isKindOfClass:[CustomCell class]]) {
//do specific code
}else if([cell isKindOfClass:[CustomCell2 class]]){
//Another custom cell
}else{
//General cell
}
SWIFT 4
Just in case, if someone needed it. Get the instance of selected cell and then check it for required tableViewCell type.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = myCustomCell.cellForRow(at: indexPath)
else
{
return
}
/** MyCustomCell is your tableViewCell class for which you want to check. **/
if cell.isKind(of: MyCustomCell.self)
{
/** Do your stuff here **/
}
}

How to let user to modify the text in UITableView cells

I have a question regarding uitable view.
I am implementing an app which is similar to the address book app.I am able to present the table view in editing mode. I want to let the user to edit the text in the cells in editing mode. I know that in order to edit the text in the cells, I need a textfield. I have created a textfield.
My question is:
what should I do in order to present that textfield in the cells.
what are the methods I need to implement in order to present that text field in the table view in editing mode.
Once I am done with editing ,How can I update the data which is in my contacts view controller(contains all the contacts).The saving should persist in the address book. For this question I know that I need to implement some delegate method,But I am not sure how to do that.
Please have a look at the following code,so that you will have an idea about my problem.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
[tableView setSeparatorColor:[UIColor clearColor]];
//[self.tableView setEditing: YES animated: YES];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
if(isEditingOn) {
if(cell == nil)
cell = [self getCellContentView:CellIdentifier];
UILabel *lblTemp1 = (UILabel *)[cell viewWithTag:1];
UITextField *textfield1=(UITextField*)[cell viewWithTag:2];
if(indexPath.row == 0) {
lblTemp1.text = #"Name";
textfield1.text = myContact.name;
}
else if(indexPath.row == 1) {
lblTemp1.text = #"Phone";
textfield1.text = myContact.phone;
}
else if(indexPath.row == 2) {
lblTemp1.text = #"Email";
textfield1.text = myContact.email;
}
}
else {
if(indexPath.row == 0) {
cell.textLabel.text = myContact.name;
}
else if(indexPath.row == 1) {
cell.textLabel.text = myContact.phone;
}
else if(indexPath.row == 2) {
cell.textLabel.text = myContact.email;
}
}
return cell;
}
- (UITableViewCell *) getCellContentView:(NSString *)cellIdentifier {
CGRect CellFrame = CGRectMake(0, 0, 60, 20);
CGRect Label1Frame = CGRectMake(10, 10, 180, 25);
UILabel *lblTemp;
UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CellFrame reuseIdentifier:cellIdentifier] autorelease];
lblTemp = [[UILabel alloc] initWithFrame:Label1Frame];
lblTemp.tag = 1;
[cell.contentView addSubview:lblTemp];
[lblTemp release];
CGRect TextFieldFrame=CGRectMake(240, 10, 60, 25);
UITextField *textfield;
textfield=[[UITextField alloc]initWithFrame:TextFieldFrame];
textfield.tag=2;
textfield.placeholder = #"";
[cell.contentView addSubview:textfield];
}
This is a really complex question to answer this fully and in-depth with code examples, but I'll try to point you in the right direction.
1) Add a UITextField as a subview of your table cell when you create the cell in the tableView:cellForRowAtIndexPath: method (I assume that's what your getCellContentView: method is for). Set a tag on your UITextField that matches the row index of the cell and make your tableviewcontroller the delegate for the cell. Set the textfield to hidden. (remember to set the tag each time the cell is requested, not just the first time you create it).
2) In the tableView:didSelectRowAtIndexPath: method, grab the cell using tableViewCellForRowAtIndexPath and then show the textfield inside it (you may have to do some view traversal to get it) and call becomeFirstResponder on the textfield.
3) When the user has typed something, your textfielddelegate methods will be fired. You can look at the tag on the textfield to work out which row the field belongs to and then update the dat source with the new text. Then just reload the table to hide the textfield and update the cell content.
If you know how to use custom table cell subclasses then you can make your life a bit easier by creating a custom cell that already contains a textfield and has an property for accessing it, but otherwise the technique will be mostly the same.
Also, tableView:didSelectRowAtIndexPath: won't normally fire when a tableview is in edit mode unless you set tableView.allowsSelectionDuringEditing = YES;
It's better to use 2 UITableViewCells, The first one for view and the last for edit mode.
Also we will depend on the variable rowToEdit which refers to the current editing row. (in my case one cell is allowed to be edited at the same time)
let us begin:
First I depend on accessoryButtonTap action to edit the row:
var rowToEdit: IndexPath? = nil
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
// End edit mode if one cell being in edit mode other than this one
if let row = self.rowToEdit {
// return current edit cell to view mode
self.rowToEdit = nil
self.tableView.reloadRows(at: [row], with: .automatic)
}
self.rowToEdit = indexPath
self.tableView.reloadRows(at: [self.rowToEdit!], with: .automatic)
}
Differentiate between the 2 modes when you will load the cell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath == self.rowToEdit {
let cellId = "ContactEditTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath as IndexPath) as! ContactEditTableViewCell
cell.accessoryType = .none
self.configEditCell(cell: cell, indexPath: indexPath)
return cell
} else {
let cellId = "ContactTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath as IndexPath) as! ContactTableViewCell
self.configCell(cell: cell, indexPath: indexPath)
return cell
}
}
Additional option if you want to change the height based on mode:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == self.rowToEdit {
return 120
} else {
return 70
}
}
Last option to add Save and Cancel buttons:
I added them to each cell, So I pass a reference to the ContactTable to each cell.
#IBAction func btnSave_click(_ sender: UIButton) {
// save the record
btnCancel_click(sender)
}
#IBAction func btnCancel_click(_ sender: UIButton) {
let tmp = self.tbl.rowToEdit
self.tbl.rowToEdit = nil
self.tbl.tableView.reloadRows(at: [tmp!], with: .automatic)
}

How do you load custom UITableViewCells from Xib files?

The question is simple: How do you load custom UITableViewCell from Xib files? Doing so allows you to use Interface Builder to design your cells. The answer apparently is not simple due to memory managment issues. This thread mentions the issue and suggests a solution, but is pre NDA-release and lacks code. Here's a long thread that discusses the issue without providing a definitive answer.
Here's some code I've used:
static NSString *CellIdentifier = #"MyCellIdentifier";
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil];
cell = (MyCell *)[nib objectAtIndex:0];
}
To use this code, create MyCell.m/.h, a new subclass of UITableViewCell and add IBOutlets for the components you want. Then create a new "Empty XIB" file. Open the Xib file in IB, add a UITableViewCell object, set its identifier to "MyCellIdentifier", and set its class to MyCell and add your components. Finally, connect the IBOutlets to the components. Note that we did not set the File's Owner in IB.
Other methods advocate setting the File's Owner and warn of memory leaks if the Xib is not loaded via an additional factory class. I tested the above under Instruments/Leaks and saw no memory leaks.
So what's the canonical way to load cells from Xibs? Do we set File's Owner? Do we need a factory? If so, what's the code for the factory look like? If there are multiple solutions, let's clarify the pros and cons of each of them...
The right solution is this:
- (void)viewDidLoad
{
[super viewDidLoad];
UINib *nib = [UINib nibWithNibName:#"ItemCell" bundle:nil];
[[self tableView] registerNib:nib forCellReuseIdentifier:#"ItemCell"];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create an instance of ItemCell
PointsItemCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ItemCell"];
return cell;
}
Here are two methods which the original author states was recommended by an IB engineer.
See the actual post for more details. I prefer method #2 as it seems simpler.
Method #1:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"BDCustomCell"];
if (cell == nil) {
// Create a temporary UIViewController to instantiate the custom cell.
UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:#"BDCustomCell" bundle:nil];
// Grab a pointer to the custom cell.
cell = (BDCustomCell *)temporaryController.view;
[[cell retain] autorelease];
// Release the temporary UIViewController.
[temporaryController release];
}
return cell;
}
Method #2:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"BDCustomCell"];
if (cell == nil) {
// Load the top-level objects from the custom cell XIB.
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"BDCustomCell" owner:self options:nil];
// Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
cell = [topLevelObjects objectAtIndex:0];
}
return cell;
}
Update (2014):
Method #2 is still valid but there is no documentation for it anymore. It used to be in the official docs but is now removed in favor of storyboards.
I posted a working example on Github:
https://github.com/bentford/NibTableCellExample
edit for Swift 4.2
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.tblContacts.register(UINib(nibName: CellNames.ContactsCell, bundle: nil), forCellReuseIdentifier: MyIdentifier)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier, for: indexPath) as! ContactsCell
return cell
}
Register
After iOS 7, this process has been simplified down to (swift 3.0):
// For registering nib files
tableView.register(UINib(nibName: "MyCell", bundle: Bundle.main), forCellReuseIdentifier: "cell")
// For registering classes
tableView.register(MyCellClass.self, forCellReuseIdentifier: "cell")
(Note) This is also achievable by creating the cells in the .xib or .stroyboard files, as prototype cells.
If you need to attach a class to them, you can select the cell prototype and add the corresponding class (must be a descendant of UITableViewCell, of course).
Dequeue
And later on, dequeued using (swift 3.0):
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Hello"
return cell
}
The difference being that this new method not only dequeues the cell, it also creates if non-existant (that means that you don't have to do if (cell == nil) shenanigans), and the cell is ready to use just as in the example above.
(Warning) tableView.dequeueReusableCell(withIdentifier:for:) has the new behavior, if you call the other one (without indexPath:) you get the old behavior, in which you need to check for nil and instance it yourself, notice the UITableViewCell? return value.
if let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? MyCellClass
{
// Cell be casted properly
cell.myCustomProperty = true
}
else
{
// Wrong type? Wrong identifier?
}
And of course, the type of the associated class of the cell is the one you defined in the .xib file for the UITableViewCell subclass, or alternatively, using the other register method.
Configuration
Ideally, your cells have been already configured in terms of appearance and content positioning (like labels and image views) by the time you registered them, and on the cellForRowAtIndexPath method you simply fill them in.
All together
class MyCell : UITableViewCell
{
// Can be either created manually, or loaded from a nib with prototypes
#IBOutlet weak var labelSomething : UILabel? = nil
}
class MasterViewController: UITableViewController
{
var data = ["Hello", "World", "Kinda", "Cliche", "Though"]
// Register
override func viewDidLoad()
{
super.viewDidLoad()
tableView.register(MyCell.self, forCellReuseIdentifier: "mycell")
// or the nib alternative
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return data.count
}
// Dequeue
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "mycell", for: indexPath) as! MyCell
cell.labelSomething?.text = data[indexPath.row]
return cell
}
}
And of course, this is all available in ObjC with the same names.
Took Shawn Craver's answer and cleaned it up a bit.
BBCell.h:
#import <UIKit/UIKit.h>
#interface BBCell : UITableViewCell {
}
+ (BBCell *)cellFromNibNamed:(NSString *)nibName;
#end
BBCell.m:
#import "BBCell.h"
#implementation BBCell
+ (BBCell *)cellFromNibNamed:(NSString *)nibName {
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:NULL];
NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
BBCell *customCell = nil;
NSObject* nibItem = nil;
while ((nibItem = [nibEnumerator nextObject]) != nil) {
if ([nibItem isKindOfClass:[BBCell class]]) {
customCell = (BBCell *)nibItem;
break; // we have a winner
}
}
return customCell;
}
#end
I make all my UITableViewCell's subclasses of BBCell, and then replace the standard
cell = [[[BBDetailCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"BBDetailCell"] autorelease];
with:
cell = (BBDetailCell *)[BBDetailCell cellFromNibNamed:#"BBDetailCell"];
I used bentford's Method #2:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"BDCustomCell"];
if (cell == nil) {
// Load the top-level objects from the custom cell XIB.
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"BDCustomCell" owner:self options:nil];
// Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
cell = [topLevelObjects objectAtIndex:0];
}
return cell;
}
It works, but watch out for connections to File's Owner in your custom UITableViewCell .xib file.
By passing owner:self in your loadNibNamed statement, you set the UITableViewController as File's Owner of your UITableViewCell.
If you drag and drop to the header file in IB to set up actions and outlets, it will set them up as File's Owner by default.
In loadNibNamed:owner:options, Apple's code will try to set properties on your UITableViewController, since that's the owner. But you don't have those properties defined there, so you get an error about being key value coding-compliant:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<MyUITableViewController 0x6a383b0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key myLabel.'
If an Event gets triggered instead, you'll get an NSInvalidArgumentException:
-[MyUITableViewController switchValueDidChange:]: unrecognized selector sent to instance 0x8e9acd0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyUITableViewController switchValueDidChange:]: unrecognized selector sent to instance 0x8e9acd0'
*** First throw call stack:
(0x1903052 0x15eed0a 0x1904ced 0x1869f00 0x1869ce2 0x1904ec9 0x5885c2 0x58855a 0x62db76 0x62e03f 0x77fa6c 0x24e86d 0x18d7966 0x18d7407 0x183a7c0 0x1839db4 0x1839ccb 0x1f8b879 0x1f8b93e 0x585a9b 0xb904d 0x2c75)
terminate called throwing an exceptionCurrent language: auto; currently objective-c
An easy workaround is to point your Interface Builder connections at the UITableViewCell instead of File's Owner:
Right click on File's Owner to pull up the list of connections
Take a screen capture with Command-Shift-4 (drag to select the area to be captured)
x out the connections from File's Owner
Right click on the UITableCell in the Object hierarchy and re-add the connections.
I've decided to post since I don't like any of these answers -- things can always be more simple and this is by far the most concise way I've found.
1. Build your Xib in Interface Builder as you like it
Set File's Owner to class NSObject
Add a UITableViewCell and set its class to MyTableViewCellSubclass -- if your IB crashes (happens in Xcode > 4 as of this writing), just use a UIView of do the interface in Xcode 4 if you still have it laying around
Layout your subviews inside this cell and attach your IBOutlet connections to your #interface in the .h or .m (.m is my preference)
2. In your UIViewController or UITableViewController subclass
#implementation ViewController
static NSString *cellIdentifier = #"MyCellIdentier";
- (void) viewDidLoad {
...
[self.tableView registerNib:[UINib nibWithNibName:#"MyTableViewCellSubclass" bundle:nil] forCellReuseIdentifier:cellIdentifier];
}
- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewCellSubclass *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
...
return cell;
}
3. In your MyTableViewCellSubclass
- (id) initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
...
}
return self;
}
If you're using Interface Builder to make cells, check that you've set the Identifier in the Inspector. Then check that it's the same when calling dequeueReusableCellWithIdentifier.
I accidentally forgot to set some identifiers in a table-heavy project, and the performance change was like night and day.
Loading UITableViewCells from XIBs saves a lot of code, but usually results in horrible scrolling speed (actually, it's not the XIB but the excessive use of UIViews that cause this).
I suggest you take a look at this: Link reference
Here's the class method that I've been using for creating custom cells out of XIBs:
+ (CustomCell*) createNewCustomCellFromNib {
NSArray* nibContents = [[NSBundle mainBundle]
loadNibNamed:#"CustomCell" owner:self options:NULL];
NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
CustomCell *customCell= nil;
NSObject* nibItem = nil;
while ( (nibItem = [nibEnumerator nextObject]) != nil) {
if ( [nibItem isKindOfClass: [CustomCell class]]) {
customCell = (CustomCell*) nibItem;
if ([customCell.reuseIdentifier isEqualToString: #"CustomCell"]) {
break; // we have a winner
}
else
fuelEntryCell = nil;
}
}
return customCell;
}
Then, in the XIB, I set the class name, and reuse identifier. After that, I can just call that method in my view controller instead of the
[[UITableViewCell] alloc] initWithFrame:]
It's plenty fast enough, and being used in two of my shipping applications. It's more reliable than calling [nib objectAtIndex:0], and in my mind at least, more reliable than Stephan Burlot's example because you're guaranteed to only grab a view out of a XIB that is the right type.
Correct Solution is this
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"CustomCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:#"CustomCell"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
return cell;
}
Reloading the NIB is expensive. Better to load it once, then instantiate the objects when you need a cell. Note that you can add UIImageViews etc to the nib, even multiple cells, using this method (Apple's "registerNIB" iOS5 allows only one top level object - Bug 10580062
"iOS5 tableView registerNib: overly restrictive"
So my code is below - you read in the NIB once (in initialize like I did or in viewDidload - whatever. From then on, you instantiate the nib into objects then pick the one you need. This is much more efficient than loading the nib over and over.
static UINib *cellNib;
+ (void)initialize
{
if(self == [ImageManager class]) {
cellNib = [UINib nibWithNibName:#"ImageManagerCell" bundle:nil];
assert(cellNib);
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellID = #"TheCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if(cell == nil) {
NSArray *topLevelItems = [cellNib instantiateWithOwner:nil options:nil];
NSUInteger idx = [topLevelItems indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop)
{
UITableViewCell *cell = (UITableViewCell *)obj;
return [cell isKindOfClass:[UITableViewCell class]] && [cell.reuseIdentifier isEqualToString:cellID];
} ];
assert(idx != NSNotFound);
cell = [topLevelItems objectAtIndex:idx];
}
cell.textLabel.text = [NSString stringWithFormat:#"Howdie %d", indexPath.row];
return cell;
}
Check this - http://eppz.eu/blog/custom-uitableview-cell/ - really convenient way using a tiny class that ends up one line in controller implementation:
-(UITableViewCell*)tableView:(UITableView*) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath
{
return [TCItemCell cellForTableView:tableView
atIndexPath:indexPath
withModelSource:self];
}
The correct way to do it is to create a UITableViewCell subclass implementation, header, and XIB. In the XIB remove any views and just add a table cell. Set the class as the name of the UITableViewCell subclass. For file owner, make it the UITableViewController subclass class name. Connect the file owner to the cell using the tableViewCell outlet.
In the header file:
UITableViewCell *_tableViewCell;
#property (assign) IBOutlet UITableViewCell *tableViewCell;
In the implementation file:
#synthesize tableViewCell = _tableViewCell;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *kCellIdentifier = #"reusableCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:kCellIdentifier owner:self options:nil];
cell = _tableViewCell;
self.tableViewCell = nil;
}
return cell;
}
What I do for this is declare an IBOutlet UITableViewCell *cell in your controller class.
Then invoke the NSBundle loadNibNamed class method, which will feed the UITableViewCell to the cell declared above.
For the xib I will create an empty xib and add the UITableViewCell object in IB where it can be setup as needed. This view is then connected to the cell IBOutlet in the controller class.
- (UITableViewCell *)tableView:(UITableView *)table
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%# loading RTEditableCell.xib", [self description] );
static NSString *MyIdentifier = #"editableCellIdentifier";
cell = [table dequeueReusableCellWithIdentifier:MyIdentifier];
if(cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"RTEditableCell"
owner:self
options:nil];
}
return cell;
}
NSBundle additions loadNibNamed (ADC login)
cocoawithlove.com article I sourced the concept from (get the phone numbers sample app)
Create your own customized class AbcViewCell subclass from UITableViewCell (Make sure your class file name and nib file name are the same)
Create this extension class method.
extension UITableViewCell {
class func fromNib<T : UITableViewCell>() -> T {
return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)?[0] as! T
}
}
Use it.
let cell: AbcViewCell = UITableViewCell.fromNib()
First import your custom cell file #import "CustomCell.h" and then change the delegate method as below mentioned:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = #"CustomCell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
return cell;
}
In Swift 4.2 and Xcode 10
I have three XIB cell files
in ViewDidLoad register your XIB files like this...
This is first approach
tableView.register(UINib.init(nibName: "XIBCell", bundle: nil), forCellReuseIdentifier: "cell1")
tableView.register(UINib.init(nibName: "XIBCell2", bundle: nil), forCellReuseIdentifier: "cell2")
//tableView.register(UINib.init(nibName: "XIBCell3", bundle: nil), forCellReuseIdentifier: "cell3")
Second approach directly register XIB files in cellForRowAt indexPath:
This is my tableview delegate functions
//MARK: - Tableview delegates
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 6
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//This is first approach
if indexPath.row == 0 {//Load first XIB cell
let placeCell = tableView.dequeueReusableCell(withIdentifier: "cell1") as! XIBCell
return placeCell
//Second approach
} else if indexPath.row == 5 {//Load XIB cell3
var cell = tableView.dequeueReusableCell(withIdentifier:"cell3") as? XIBCell3
if cell == nil{
let arrNib:Array = Bundle.main.loadNibNamed("XIBCell3",owner: self, options: nil)!
cell = arrNib.first as? XIBCell3
}
//ADD action to XIB cell button
cell?.btn.tag = indexPath.row//Add tag to button
cell?.btn.addTarget(self, action: #selector(self.bookbtn1(_:)), for: .touchUpInside);//selector
return cell!
//This is first approach
} else {//Load XIB cell2
let placeCell = tableView.dequeueReusableCell(withIdentifier: "cell2") as! XIBCell2
return placeCell
}
}
Here is my method for that: Loading Custom UITableViewCells from XIB Files… Yet Another Method
The idea is to create a SampleCell subclass of the UITableViewCell with a IBOutlet UIView *content property and a property for each custom subview you need to configure from the code. Then to create a SampleCell.xib file. In this nib file, change the file owner to SampleCell. Add a content UIView sized to fit your needs. Add and configure all the subviews (label, image views, buttons, etc) you want. Finally, link the content view and the subviews to the file owner.
Here is a universal approach for registering cells in UITableView:
protocol Reusable {
static var reuseID: String { get }
}
extension Reusable {
static var reuseID: String {
return String(describing: self)
}
}
extension UITableViewCell: Reusable { }
extension UITableView {
func register<T: UITableViewCell>(cellClass: T.Type = T.self) {
let bundle = Bundle(for: cellClass.self)
if bundle.path(forResource: cellClass.reuseID, ofType: "nib") != nil {
let nib = UINib(nibName: cellClass.reuseID, bundle: bundle)
register(nib, forCellReuseIdentifier: cellClass.reuseID)
} else {
register(cellClass.self, forCellReuseIdentifier: cellClass.reuseID)
}
}
Explanation:
Reusable protocol generates cell ID from its class name. Make sure you follow the convention: cell ID == class name == nib name.
UITableViewCell conforms to Reusable protocol.
UITableView extension abstracts away the difference in registering cells via nib or class.
Usage example:
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView()
let cellClasses: [UITableViewCell.Type] = [PostCell.self, ProfileCell.self, CommentCell.self]
cellClasses.forEach(tableView.register)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: PostCell.self.reuseID) as? PostCell
...
return cell
}
I dont know if there is a canonical way, but here's my method:
Create a xib for a ViewController
Set the File Owner class to UIViewController
Delete the view and add an UITableViewCell
Set the Class of your UITableViewCell to your custom class
Set the Identifier of your UITableViewCell
Set the outlet of your view controller view to your UITableViewCell
And use this code:
MyCustomViewCell *cell = (MyCustomViewCell *)[_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
UIViewController* c = [[UIViewController alloc] initWithNibName:CellIdentifier bundle:nil];
cell = (MyCustomViewCell *)c.view;
[c release];
}
In your example, using
[nib objectAtIndex:0]
may break if Apple changes the order of items in the xib.
NSString *CellIdentifier = [NSString stringWithFormat:#"cell %ld %ld",(long)indexPath.row,(long)indexPath.section];
NewsFeedCell *cell = (NewsFeedCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell=nil;
if (cell == nil)
{
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"NewsFeedCell" owner:nil options:nil];
for(id currentObject in topLevelObjects)
{
if([currentObject isKindOfClass:[NewsFeedCell class]])
{
cell = (NewsFeedCell *)currentObject;
break;
}
}
}
return cell;
This extension requires Xcode7 beta6
extension NSBundle {
enum LoadViewError: ErrorType {
case ExpectedXibToExistButGotNil
case ExpectedXibToContainJustOneButGotDifferentNumberOfObjects
case XibReturnedWrongType
}
func loadView<T>(name: String) throws -> T {
let topLevelObjects: [AnyObject]! = loadNibNamed(name, owner: self, options: nil)
if topLevelObjects == nil {
throw LoadViewError.ExpectedXibToExistButGotNil
}
if topLevelObjects.count != 1 {
throw LoadViewError.ExpectedXibToContainJustOneButGotDifferentNumberOfObjects
}
let firstObject: AnyObject! = topLevelObjects.first
guard let result = firstObject as? T else {
throw LoadViewError.XibReturnedWrongType
}
return result
}
}
Create an Xib file that contains just 1 custom UITableViewCell.
Load it.
let cell: BacteriaCell = try NSBundle.mainBundle().loadView("BacteriaCell")
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellReuseIdentifier = "collabCell"
var cell:collabCell! = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as? collabCell
if cell == nil {
tableView.register(UINib(nibName: "collabCell", bundle: nil), forCellReuseIdentifier: cellReuseIdentifier)
cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! collabCell!
}
return cell
}

Resources