How do you load custom UITableViewCells from Xib files?
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 filestableView.register(UINib(nibName: "MyCell", bundle: Bundle.main), forCellReuseIdentifier: "cell")// For registering classestableView.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 ofUITableViewCell
, 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 (withoutindexPath:
) you get the old behavior, in which you need to check fornil
and instance it yourself, notice theUITableViewCell?
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.