I have a UICollectionView inside a UITableViewCell. You may refer the image at here
I would like to reload the collectionView if any update happen.
I have done some research and found this :
how to reload a collectionview that is inside a tableviewcell
Reloading collection view inside a table view cell happens after all cells in table view have been loaded
UICollectionView not updating inside UITableViewCell
I called the #IBOutlet weak var collectionView: UICollectionView! from UITableViewCell to UITableViewController at cellForRowAt.
Here is the code:
var refreshNow: Bool = false
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.allCardCell, for: indexPath) as! AllCardTableViewCell
if refreshNow {
cell.collectionView.reloadData()
refreshNow = false
}
cell.collectionView.collectionViewLayout.invalidateLayout()
return cell
}
If the user click Ok on UIAlertAction :
let alert = UIAlertController(title: "Success", message: "Card successfully added", preferredStyle: .alert)
let action = UIAlertAction(title: "Ok", style: .default) { (action) in
self.refreshNow = true
self.tableView.reloadData()
}
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
The reason why I put the refreshNow is to prevent the apps from lagging and slow. But still did not update if any changes happen.
The problem is the collectionView did not refresh. But when I debug, it was went through the cell.collectionView.reloadData().
The update/changes only happen when I restart the apps. I want it to be so called real-times update.
Any help is really appreciated and many thanks.
Image credit: How to use StoryBoard quick build a collectionView inside UITableViewCell
At end of your update add:
DispatchQueue.main.async() { () -> Void in
self.tableView.reloadData()
}
In your case, you should assign tag to your collection view in order to get access outside the cellForRowAt function.
This is how your function should look like:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.allCardCell, for: indexPath) as! AllCardTableViewCell
cell.collectionView.tag = 1234
return cell
}
and the action will reload it will access the collectionView by using the tag
let action = UIAlertAction(title: "Ok", style: .default) { (action) in
let collectionView = self.tableView.viewWithTag(1234) as! UICollectionView
collectionView.reloadData()
}
Also take note that cellForRowAt will keep reload the content based what you added inside it every time the cell appear. So, keep updating your data outside the cellForRowAt function.
Because you reused UITableViewCell so you must alway reload your UICollectionView. If you use refreshNow to reload UICollectionView, at the cell have refreshNow = false, UICollectionView will display like cell that it 's reused => wrong
Udate rep:
See , in picture uitableviewcell 1 will reuse at index 6. If you not reload content of cell (reload collectionview) it will display like uitableviewcell 1 at index 0
#import "AddPhotoViewController.h"
#import "PhotoTableViewCell.h"
#import "ShareTableViewCell.h"
#interface AddPhotoViewController ()
#property (weak, nonatomic) IBOutlet UITableView *tblView;
#property (strong,nonatomic)NSMutableArray *arrImages,*arrIndexPath,*selectImages;
#end
#pragma mark - TableViewDelegate&DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *returnCell;
static NSString *cellIdentifier = #"CellOne";
static NSString *cellIdentifierTwo = #"CellTwo";
static NSString *cellIdentifierThree = #"CellThree";
if (indexPath.row == 0) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
returnCell = cell;
} else if (indexPath.row == 1){
ShareTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifierTwo forIndexPath:indexPath];
cell.viewMood.layer.cornerRadius = 5;
cell.viewPeople.layer.cornerRadius = 5;
[cell.viewMood layer].borderWidth = 1;
[cell.viewMood layer].borderColor = [UIColor colorWithRed:241.0/255.0 green:143.0/255.0 blue:48.0/255.0 alpha:1].CGColor;
[cell.viewPeople layer].borderWidth = 1;
[cell.viewPeople layer].borderColor = [UIColor colorWithRed:241.0/255.0 green:143.0/255.0 blue:48.0/255.0 alpha:1].CGColor;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
returnCell = cell;
}else if (indexPath.row == 2){
PhotoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifierThree forIndexPath:indexPath];
cell.collView.dataSource = self;
cell.collView.delegate = self;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
returnCell = cell;
}
return returnCell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewAutomaticDimension;
}
#pragma mark- UIImagePickerControllerDelegate
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *chosenImage = info[UIImagePickerControllerOriginalImage];
[_arrImages addObject:chosenImage];
PhotoTableViewCell *cell = [self.tblView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]];
[cell.collView reloadData];
[picker dismissViewControllerAnimated:YES completion:^{
}];
}
#pragma mark - CollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [_arrImages count];
}
- ( UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"CellCollection";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
UIImageView *imgView = [(UIImageView*)[cell contentView] viewWithTag:100];
UIImageView *imgViewTick = [(UIImageView*)[cell contentView] viewWithTag:200];
UIView *view = [(UIView*)[cell contentView] viewWithTag:300];
if (indexPath.row == 0){
imgViewTick.hidden = YES;
view.hidden = YES;
}
if ([_arrIndexPath containsObject:indexPath]) {
[_selectImages removeAllObjects];
view.hidden = NO;
view.alpha = 0.4;
imgViewTick.hidden = NO;
imgView.image = [_arrImages objectAtIndex:indexPath.row];
[_selectImages addObject:[_arrImages objectAtIndex:indexPath.row]];
NSLog(#"Pick images:%#",_selectImages);
}else{
view.hidden = YES;
imgViewTick.hidden = YES;
imgView.image = [_arrImages objectAtIndex:indexPath.row];
}
return cell;
}
I am using tableView to display some information which is just four line of information. And i want to assign respective information to each row.
Like how shown in the below image there are four rows, same as in the image so i am using tableView for that. Here my problem is that i have created four cells but don't know how should i use label in specific cell and show the info.
and also if the value is null that row should not be there means if two values among four are null then only two rows having values should be there in tableView. How can i achieve this. Till now i am only able to show one row information only.
- (NSArray *)myTableViewCells
{
if (!_myTableViewCells)
{
_myTableViewCells = #[
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]
];
}
return _myTableViewCells;
}
if([managedObject valueForKey:#"personality_company_master_values"] != nil)
{
[_displayValues addObject:[NSString stringWithFormat:#"Personality %#",[managedObject valueForKey:#"personality_company_master_values"]]];
}
if([managedObject valueForKey:#"video_tag"] != nil)
{
[_displayValues addObject:[NSString stringWithFormat:#"Tag %#",[managedObject valueForKey:#"video_tag"]]];
}
if([managedObject valueForKey:#"industry_master_values"] != nil)
{
[_displayValues addObject:[NSString stringWithFormat:#"Industry %#",[managedObject valueForKey:#"industry_master_values"]]];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.myTableViewCells.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = self.myTableViewCells[indexPath.row];
// NSManagedObject *managedObject = [self.devices lastObject];
cell.backgroundColor = [self colorFromHexString:#"#014455"];
cell.textLabel.text = _displayValues[indexPath.row];
cell.textLabel.backgroundColor = [self colorFromHexString:#"#014455"];
cell.textLabel.textColor = [UIColor whiteColor];
cell.textLabel.font=[UIFont systemFontOfSize:14.0];
// UILabel *lbl=(UILabel*)[cell viewWithTag:900];
// [lbl setText:[managedObject valueForKey:#"personality_company_master_values"]];
// lbl.textColor=[UIColor blackColor];
return cell;
}
Get the value you want display in an array.
Something like this
#property (monatomic, strong)NSMuatableArray *displayValues;
-(void)viewDidLoad
{
self.displayValues = [[NSMutableArray alloc]init];
NSManagedObject *managedObject = [self.devices lastObject];
if([managedObject valueForKey:#"personality_company_master_values"] != nil)
{
[self.displayValues addObject:[managedObject valueForKey:#"personality_company_master_values"]];
}
if([managedObject valueForKey:#"company_master_values"] != nil)
{
[self.displayValues addObject:[managedObject valueForKey:#"company_master_values"]];
}
if([managedObject valueForKey:#"tag_master_values"] != nil)
{
[self.displayValues addObject:[managedObject valueForKey:#"tag_master_values"]];
}
}
- (NSArray *)myTableViewCells
{
if (!_myTableViewCells)
{
_myTableViewCells = #[
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]
];
}
return _myTableViewCells;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.displayValues.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = self.myTableViewCells[indexPath.row];
// NSManagedObject *managedObject = [self.devices lastObject];
//cell.textLabel.text = [NSString stringWithFormat:#"%#",[managedObject valueForKey:#"personality_company_master_values"]];
cell.textLabel.text = self.displayValues[indexPath.row];
//not getting have to do this way or any other way please help
// secondLabel.text = [NSString stringWithFormat:#"%#",[managedObject valueForKey:#"company_master_values"]];
// thirdLabel.text = [NSString stringWithFormat:#"%#",[managedObject valueForKey:#"tag_master_values"]];
return cell;
}
I'm afraid you're doing several things wrong, starting with preallocating an array of cells. Tableviews don't work like that, you provide cells on demand and populate them with values from your data model. When you want to remove a cell update your data model then call reloadData(). Here's a simple example:
import UIKit
class MyCell: UITableViewCell {
var row: Int = -1 // serves no purpose but to show how you might subclass a UITableViewCell
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var dataModel = [
"Hello", "World,", "this", "is", "a", "tableview"
]
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
var frame = view.bounds
let statusBarHeight = UIApplication.sharedApplication().statusBarFrame.height
frame.origin.y += statusBarHeight
frame.size.height -= statusBarHeight
tableView = UITableView(frame: frame, style: .Plain)
tableView.delegate = self
tableView.dataSource = self
tableView.registerClass(MyCell.self, forCellReuseIdentifier: "mycell")
view.addSubview(tableView)
}
// MARK: - UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataModel.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("mycell") as! MyCell
let row = indexPath.row
cell.row = row // there is no point in doing this other than to show it as an example
cell.textLabel!.text = dataModel[row]
return cell
}
// MARK: - UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
dataModel.removeAtIndex(indexPath.row)
tableView.reloadData()
}
}
EDIT: Here's an objective c version
////////////////////////////
/// Objective C Version //
////////////////////////////
#import "ViewController.h"
#interface MyCell: UITableViewCell
#property(assign) NSInteger row; // serves no purpose but to show how you might subclass a UITableViewCell
#end
#implementation MyCell #end
#interface ViewController() <UITableViewDataSource, UITableViewDelegate>
#property NSMutableArray *dataModel;
#property UITableView *tableView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_dataModel = [NSMutableArray arrayWithArray: #[#"Hello", #"World,", #"this", #"is", #"a", #"tableview"]];
CGRect frame = self.view.bounds;
CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
frame.origin.y += statusBarHeight;
frame.size.height -= statusBarHeight;
_tableView = [[UITableView alloc] initWithFrame: frame style: UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
[_tableView registerClass: [MyCell class] forCellReuseIdentifier: #"mycell"];
[self.view addSubview: _tableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _dataModel.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = (MyCell *) [tableView dequeueReusableCellWithIdentifier: #"mycell"];
NSInteger row = indexPath.row;
cell.row = row; // there is no point in doing this other than to show it as an example
cell.textLabel.text = _dataModel[row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[_dataModel removeObjectAtIndex: indexPath.row];
[_tableView reloadData];
}
#end
You dont need to create a fixed amount of cells this is not an efficient solution to the problem. You should create NSMutableDictionary and save the data like this:
NSMutableArray *data = [NSMutableDictionary dictionary];
[data setValue:#"Vijayakanth" forKey:#"Personality"];
Now in the table view delegate you can return the keys count for noOfRowsInSection and in cellForRowAtIndexPath you can get the key from dictionary get the value w.r.t that key and assign the values to your cell. In your case:
Key: Personality (which is shown on the left side)
Value: Vijayakanth (which is shown on the right side)
Hope you understand the point.
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...
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
}