How do you load custom UITableViewCells from Xib files? - ios

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
}

Related

How to set property of different custom UITableViewCell

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.

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...

iOS Swift - Custom UITableViewCell

I am sure the question itself has been asked and answered properly but in Objective C. I am using swift and was wondering how to customize a UITableViewCell properly. I followed this tutorial here http://www.appcoda.com/customize-table-view-cells-for-uitableview/ but I am stuck at properly initializing and using the custom class and XIB file I created. yes, I am a noob. Here is what I have for the standard cell without customization:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "myCell")
cell.text = postMgr.posts[indexPath.section].title
cell.detailTextLabel.text = postMgr.posts[indexPath.section].description
return cell
}
If someone can translate the Obj C in the tutorial to swift that would be great. Here it is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = #"SimpleTableCell";
SimpleTableCell *cell = (SimpleTableCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"SimpleTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.nameLabel.text = [tableData objectAtIndex:indexPath.row];
cell.thumbnailImageView.image = [UIImage imageNamed:[thumbnails objectAtIndex:indexPath.row]];
cell.prepTimeLabel.text = [prepTime objectAtIndex:indexPath.row];
return cell;
}
Not sure if that's even how it works with iOS7/8. If someone has a better and easier way of customizing the cell, let me know in Swift language.
I appreciate the help already. I am a beginner, please bear with me :)
KM
I just ported the tutorial sample app to Swift. I'am still using the same CustomTableViewCell written in objective-C (used bridging header to avail the class). My cellForRowIndexPath looks like
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!
{
var cell:SimpleTableCell! = tableView.dequeueReusableCellWithIdentifier(simpleTableIdentifier) as? SimpleTableCell
if (cell == nil)
{
let nib:Array = NSBundle.mainBundle().loadNibNamed("SimpleTableCell", owner: self, options: nil)
cell = nib[0] as? SimpleTableCell
}
cell.nameLabel.text = tableData[indexPath.row]
cell.thumbnailImageView.image = UIImage(named:thumbnails[indexPath.row])
cell.prepTimeLabel.text = prepTime[indexPath.row];
return cell;
}
find the complete source code here: TableViewApp-Swift

PFQueryTableViewController not showing custom cells

I have a subclass of PFQueryTableViewController that I am trying to show in a container view (as a subview). My problem is that I cannot get the custom cells to show in the tableview. I have verified the following via debugging:
The tableview is being added to the parent view
The tableview is a PFQueryTableView Controller as it includes the default pull to refresh
The PFQuery is returning the correct number of objects
The CellForRowAtIndexPath method is being called and iterating through the correct number of times
The correct data from Parse is being passed to the different labels in the cells
The labels are connected via IBOulets in my subclass of UITableViewCell. When I am trying to access the labels it is working correctly as it accesses the subclass and label
I have everything working here correctly except that the cell actually shows up! What am I missing?
This is my cellForRowAtIndexPath code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object{
static NSString *CellIdentifier = #"RoundCell";
RoundCell* cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier];
if (cell == nil)
{
cell = [[RoundCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// get string values from Parse
NSString * teeString =[object objectForKey:#"roundTee"];
NSString* courseString = [object objectForKey:#"roundCourse"];
NSString * courseString2 = [[courseString stringByAppendingString:#" - "]stringByAppendingString:teeString];
NSString * dateString = [object objectForKey:#"roundDate"];
NSString * scoreString = [object objectForKey:#"roundScore"];
NSString * differentialString = [object objectForKey:#"roundDifferential"];
cell.courseNameCell.text = courseString2;
cell.dateCell.text = dateString;
cell.scoreCell.text= scoreString;
cell.differentialCell.text=differentialString;
return cell;
}
The correct method is to call the custom cell in cellForRowAtIndexPath.
Check two basic things:
1. on the storyboard and click on the cell in the Attributes inspector checks that the cell has the correct identifier
2. set the cellForRowAtIndexPath in this way:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object{
CustomCell *cell = (CustomCell * )[self.tableView dequeueReusableCellWithIdentifier:#"YOUR CELL NAME" forIndexPath:indexPath];
So in your case try:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object{
CustomCell *cell = (CustomCell * )[self.tableView dequeueReusableCellWithIdentifier:#"YOUR CELL NAME" forIndexPath:indexPath];
NSString * teeString =[object objectForKey:#"roundTee"];
NSString* courseString = [object objectForKey:#"roundCourse"];
NSString * courseString2 = [[courseString stringByAppendingString:#" - "]stringByAppendingString:teeString];
NSString * dateString = [object objectForKey:#"roundDate"];
NSString * scoreString = [object objectForKey:#"roundScore"];
NSString * differentialString = [object objectForKey:#"roundDifferential"];
cell.courseNameCell.text = courseString2;
cell.dateCell.text = dateString;
cell.scoreCell.text= scoreString;
cell.differentialCell.text=differentialString;
return cell;
}
Do not forget to import the subclass of custom cell in your File.m
#import "YourCustomCell.h"
and set the cell in the identity inspector
If you designed your UITableView cell in a XIB (which it sounds like you did), then you can't use the alloc init paradigm to initialize your object. You have to use:
cell = [[[NSBundle mainBundle] loadNibNamed:#"MyCellXibFile"
owner:nil
options:nil] objectAtIndex:0]
Swift Version (prior to 1.2):
import UIKit
class JPUsersTableViewController: PFQueryTableViewController {
override init!(style: UITableViewStyle, className: String!) {
super.init(style: style, className: className)
textKey = "username"
pullToRefreshEnabled = true
paginationEnabled = true
objectsPerPage = 25
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Users"
tableView.registerClass(PFTableViewCell.self, forCellReuseIdentifier: kTableViewCellIdentifier)
tableView.separatorInset.right = tableView.separatorInset.left
tableView.tableFooterView = UIView(frame: CGRectZero)
view.backgroundColor = kbackgroundColor
let returnIcon = UIBarButtonItem(image: kNavBarReturnIcon, style: .Plain, target: navigationController, action: "popViewControllerAnimated:")
returnIcon.tintColor = kToolbarIconColor
navigationItem.leftBarButtonItem = returnIcon
tableView.reloadData()
addPullToRefresh()
}
override func queryForTable() -> PFQuery! {
let query = PFUser.query()
query.whereKey("username", notEqualTo: PFUser.currentUser().username)
query.orderByAscending("username")
//if network cannot find any data, go to cached (local disk data)
if (self.objects.count == 0){
query.cachePolicy = kPFCachePolicyCacheThenNetwork
}
return query
}
// MARK: - Navigation
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!, object: PFObject!) -> PFTableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as PFTableViewCell
cell.textLabel?.text = object["username"] as? String
if let profileImage = object["profileImage"] as? PFFile {
cell.imageView.file = profileImage
}
else {
cell.imageView.image = kProfileDefaultProfileImage
}
cell.textLabel?.font = UIFont(name: kStandardFontName, size: kStandardFontSize)
cell.textLabel?.textColor = UIColor.whiteColor()
cell.backgroundColor = kbackgroundColor
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 50
}
}

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 **/
}
}

Resources