I am new in iOS development and currently working on UITableView. I want to find last visible cells on the screen of device and cells that are at the bottom of the screen must be of blue color, which should fade to green as the cell is scrolled to the top of the screen.
I have gone through these links
Link1
Link2
But could not get success. Can anyone please provide idea how to detect last cells & cell fade animation?
Get last visible cell:
if let lastCell = tableView.visibleCells.last {
// do something with lastCell
}
In Swift 3.0, you can used this tableview method.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
let intTotalrow = tableView.numberOfRows(inSection:indexPath.section)//first get total rows in that section by current indexPath.
//get last last row of tablview
if indexPath.row == intTotalrow - 1{
// call for last display
}
}
#shallowThought solution will only work if cells are already presented.
But, if you want to know the last cell when cells aren't presented yet and are going to be presented, we can create an extension for UITableView as follow:
func isLastVisibleCell(at indexPath: IndexPath) -> Bool {
guard let lastIndexPath = indexPathsForVisibleRows?.last else {
return false
}
return lastIndexPath == indexPath
}
This way, you can check tableView.isLastVisibleCell(...) multiple times until you have reached actual visible cell.
Try this code, It will work
Initially Declare
int firstIndexRow;
int lastIndexRow;
Write below code inside of ViewDidLoad()
[myTable reloadData]; //Reload because get visible last cell index row
firstIndexRow = 0;
lastIndexRow = (int)[self.myTable.indexPathsForVisibleRows lastObject].row;
NSLog(#"first : %d",firstIndexRow);
NSLog(#"Bottom : %d",lastIndexRow);
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
NSIndexPath *firstVisibleIndexPath = [[self.myTable indexPathsForVisibleRows] objectAtIndex:0];
NSIndexPath *lastObject = [self.myTable.indexPathsForVisibleRows lastObject];
firstIndexRow = (int)firstVisibleIndexPath.row;
lastIndexRow = (int)lastObject.row;
NSLog(#"first : %d",firstIndexRow);
NSLog(#"Bottom : %d",lastIndexRow);
[myTable reloadData];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [myTable dequeueReusableCellWithIdentifier:#"cell"];
if (indexPath.row == firstIndexRow) {
cell.textLabel.textColor = [UIColor blueColor];
}else if (indexPath.row == lastIndexRow) {
cell.textLabel.textColor = [UIColor greenColor];
}else{
cell.textLabel.textColor = [UIColor grayColor];
}
cell.textLabel.text =[namesArray objectAtIndex:indexPath.row];
return cell;
}
I am trying to populate a Tableview with data from an array. I am trying to make a custom table view cell that includes a big square image, same width as the screen and the height should also be the same size as the screen's width. And under the image I want to add a text label. (You can think of it as a very bad copy of instagram)
However, the tableview only shows empty standard cells.
This is my custom cell code:
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
customImageView.contentMode = UIViewContentModeScaleAspectFit;
CGRect frame;
frame.origin.x=0;
frame.origin.y=0;
frame.size.width=self.frame.size.width;
frame.size.height=self.frame.size.width;
customImageView.frame=frame;
[customTextLabel sizeToFit];
frame.origin = CGPointMake(0, (self.frame.size.width+1));
customTextLabel.frame = frame;
customTextLabel.numberOfLines = 0;
customTextLabel.lineBreakMode = NSLineBreakByCharWrapping;
[self.contentView addSubview:customImageView];
[self.contentView addSubview:customTextLabel];
[self.contentView sizeToFit]; } return self;}
and the code from the tableview:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyCellIdentifier = #"MyCellIdentifier";
//UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:MyCellIdentifier];
FeedTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:MyCellIdentifier];
if(cell == nil) {
cell = [[FeedTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyCellIdentifier];
}
if(!emptyTable){
Log *log = [self.feedPosts objectAtIndex:indexPath.section];
cell.customImageView.image = [self loadImage:(int)log.logID];
cell.customTextLabel.text = log.logDescription;
}
return cell;}
Better solution is to use constraints
create constraints like on image below
Create outlets for your height width ( control drag your constraint to cell class)
Change it to screen size/ height on your
cell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell
cell.heightConstraint.constant = UIScreen .mainScreen().bounds.height;
cell.widthConstraint.constant = UIScreen.mainScreen().bounds.width;
return cell
}
Don't forget to set height for row
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UIScreen .mainScreen().bounds.height;
}
Also, you can recreate it on your code programmatically, if you want
Hope this helps
Where are you allocate memory for the cell.customImageView and cell.customTextLabel? This problem should be caused by not generating customeImageView and customeTextLabel. you should according to the following code to modify:
//Setter/Getter
//allocate memory and init customImageView/customTextLable
- (UIImageView *)customeImageView{
if(!_ customImageView)_customImageView = [UIImageView new];
return _customImageView;
}
- (UILable *)customTextLabel{
if(!_customTextLable) _customTextLabel = [UIlable new];
return _cusomTextLable;
}
Then invoke them when initWithStyle:reuseIdentifier:.The other thing should be noticed is that tableView should custome cell's height by implement tableView:heightForRowAtIndexPath:
I almost did what DarkHorse said.
I forgot to do:
customImageView = [[UIImageView alloc] init];
customTextLabel = [[UILabel alloc] init];
and then set the height in
tableView:heightForRowAtIndexPath
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)
}
I have n sections (known amount) and X rows in each section (unknown amount. Each row has a UITextField. When the user taps the "Done" button I want to iterate through each cell and do some conditional tests with the UITextField. If the tests pass data from each cell is written to a database. If not, then a UIAlert is shown. What is the best way to loop through the rows and if there is a more elegant solution to this please do advise.
If you only want to iterate through the visible cells, then use
NSArray *cells = [tableView visibleCells];
If you want all cells of the table view, then use this:
NSMutableArray *cells = [[NSMutableArray alloc] init];
for (NSInteger j = 0; j < [tableView numberOfSections]; ++j)
{
for (NSInteger i = 0; i < [tableView numberOfRowsInSection:j]; ++i)
{
[cells addObject:[tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]]];
}
}
Now you can iterate through all cells:
(CustomTableViewCell is a class, which contains the property textField of the type UITextField)
for (CustomTableViewCell *cell in cells)
{
UITextField *textField = [cell textField];
NSLog(#"%#"; [textField text]);
}
Here is a nice swift implementation that works for me.
func animateCells() {
for cell in tableView.visibleCells() as! [UITableViewCell] {
//do someting with the cell here.
}
}
Accepted answer in swift for people who do not know ObjC (like me).
for section in 0 ..< sectionCount {
let rowCount = tableView.numberOfRowsInSection(section)
var list = [TableViewCell]()
for row in 0 ..< rowCount {
let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: row, inSection: section)) as! YourCell
list.append(cell)
}
}
for xcode 9 use this - (similar to #2ank3th but the code is changed for swift 4):
let totalSection = tableView.numberOfSections
for section in 0..<totalSection
{
print("section \(section)")
let totalRows = tableView.numberOfRows(inSection: section)
for row in 0..<totalRows
{
print("row \(row)")
let cell = tableView.cellForRow(at: IndexPath(row: row, section: section))
if let label = cell?.viewWithTag(2) as? UILabel
{
label.text = "Section = \(section), Row = \(row)"
}
}
}
for (UIView *view in TableView.subviews) {
for (tableviewCell *cell in view.subviews) {
//do
}
}
Since iOS may recycle tableView cells which are off-screen, you have to handle tableView one cell at a time:
NSIndexPath *indexPath;
CustomTableViewCell *cell;
NSInteger sectionCount = [tableView numberOfSections];
for (NSInteger section = 0; section < sectionCount; section++) {
NSInteger rowCount = [tableView numberOfRowsInSection:section];
for (NSInteger row = 0; row < rowCount; row++) {
indexPath = [NSIndexPath indexPathForRow:row inSection:section];
cell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"Section %# row %#: %#", #(section), #(row), cell.textField.text);
}
}
You can collect an NSArray of all cells beforehands ONLY, when the whole list is visible. In such case, use [tableView visibleCells] to be safe.
quick and dirty:
for (UIView *view in self.tableView.subviews){
for (id subview in view.subviews){
if ([subview isKindOfClass:[UITableViewCell class]]){
UITableViewCell *cell = subview;
// do something with your cell
}
}
}
Here's a completely different way of thinking about looping through UITableView rows...here's an example of changing the text that might populate your UITextView by looping through your array, essentially meaning your tableView cell data.
All cells are populated with data from some kind of model. A very common model would be using an NSObject and NSMutableArray of those objects. If you were in didSelectRowAtIndexPath, you would then want to do something like this to affect the row you're selecting after modifying the array above:
for(YourObject *cellRow in yourArray)
{
if(![cellRow.someString isEqualToString:#""])
{
cellRow.someString = #"";
}
//...tons of options for conditions related to your data
}
YourObject *obj = [yourArray objectAtIndex:indexPath.row];
obj.someString = #"selected";
[yourArray insertObject:views atIndex:indexPath.row];
[yourArray removeObjectAtIndex:indexPath.row];
[yourTable reloadData];
This code would remove all the UITextField's text in every row except the one you selected, leaving the text "selected" in the tapped cell's UITextField as long as you're using obj.someString to populate the field's text in cellForRowAtIndexPath or willDisplayRowAtIndexPath using YourObject and yourArray.
This type of "looping" doesn't require any conditions of visible cells vs non visible cells. If you have multiple sections populated by an array of dictionaries, you could use the same logic by using a condition on a key value. Maybe you want to toggle a cells imageView, you could change the string representing the image name. Tons of options to loop through the data in your tableView without using any delegated UITableView properties.
swift 5:
guard let cells = self.creditCardTableView.visibleCells as? [CreditCardLoanCell] else {
return
}
cells.forEach { cell in
cell.delegate = self
}
I would like to add my two cents to the matter even though this post is old. I created an array of type UITableViewCell and appended each new cell to it before returning it in cellForRowAt. See code below:
var cellArray = [UITableViewCell]()
//UITableView code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
//set up cell information
cellArray.append(cell)
return cell
}
Then if you need any information from each cell (i.e., UITextFields) in your Done button, you can iterate through the array like so in the desired context:
for cell in cellArray {
let myCell = cell as! Cell
//do stuff
}
Hope this helps anyone in the future
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
}