I've been doing a lot of research, but cannot find an answer or a guide to help.
I would like to have more than one custom cell in a Table View. For example, the first cell may display an image, while the 2nd cell displays some information, and then the third displaying something else (All cells different sizes). I've tried a lot of code but can't get it right, I've also been watching tutorials but am still unable to do this. Would it be easier to do this in a blank UIView with no cells?
Thanks, help and advice is greatly appreciated.
Let's say you have one table view with one section. Here's how I usually do this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
id cell = nil;
switch (indexPath.row) {
case 0:
cell = [self dequeCellIdentifier:#"FirstTypeOfCell"];
break;
case 1:
cell = [self dequeCellIdentifier:#"SecondTypeOfCell"];
break;
case 2:
cell = [self dequeCellIdentifier:#"ThirdTypeOfCell"];
break;
default:
cell = [self dequeCellIdentifier:#"JustThrowThisInThere"];
break;
}
return cell;
}
To dequeue the cell, make sure you specify its identifier in XIB interface and add this method to dequeue:
- (id)dequeCellIdentifier:(NSString *)cellIdentifier {
id cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[NSBundle mainBundle] loadNibNamed:cellIdentifier owner:self options:nil][0];
}
return cell;
}
To get different heights for cells, use the - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.row) {
case 0:
return 40.0;
case 1:
return 100.0;
default:
return 50.0;
}
}
Create a custom class for each cell that inherits from UITableViewCell. Make sure to also crete a XIB when you create the class. This give you the flexibility to design your cell in the interface builder.
Check out this answer: How do you load custom UITableViewCells from Xib files?
PS. Don't forget to change the height of the cells in
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath
You can use prototype cells in storyboard. basically what you do is create one prototype cell for each type and create a UITableViewCell subclass for each of them. also give each a unique ID. all these steps are explained in detail here (follow upto Modifying the CarTableViewCell Class section).
Now is the tricky part. The above tutorial only explains how to create one custom cell. if you want multiple types of custom cells, create all those cells with unique reuse identifier and subclasses. now i'm not sure if this is the right approach but here's what I usually do :
in CellForRowAtIndexPath:
if([cellType isEqualToString:#"DefaultCell"])
{
MyTableDefaultCell *cell = [self.tableViewOutlet dequeueReusableCellWithIdentifier:#"DefaultCell" forIndexPath:indexPath];
if(cell == nil)
{
cell = [[MyTableDefaultCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"DefaultCell"];
}
//Do your thing here.
return cell;
}
else if([cellType isEqualToString:#"SegmentedControlCell"])
{
MyTableSegmentControlCell *cell = [self.tableViewOutlet dequeueReusableCellWithIdentifier:#"SegmentedControlCell" forIndexPath:indexPath];
if(cell == nil)
{
cell = [[MyTableSegmentControlCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"SegmentedControlCell"];
}
// Setup cell here.
return cell;
}
and dont forget to return the appropriate heights for each cell in heightForRowAtIndexPath:. cheers.
Related
About UITableView reuse, when there are multiple different Cell, use a different identifier to distinguish good or use an identifier and the Cell subViews remove, add content again good, if the Cell is very many cases, these reusable, what kind of specific access rules, when an identifier in the queue on the position is how to remove the master answer, thank you
_testTableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_testTableView.dataSource = self;
_testTableView.delegate = self;
[_testTableView setRowHeight:80.];
[self.view addSubview:_testTableView];
[_testTableView registerClass:[TestTableViewCell class] forCellReuseIdentifier:testKeyOne];
[_testTableView registerClass:[TestTwoTableViewCell class] forCellReuseIdentifier:testKeyTwo];
//one way
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//one
if(indexPath.row < 15){
TestTableViewCell * oneCell = [tableView dequeueReusableCellWithIdentifier:testKeyOne forIndexPath:indexPath];
return oneCell;
}else{
TestTwoTableViewCell * oneCell = [tableView dequeueReusableCellWithIdentifier:testKeyTwo forIndexPath:indexPath];
return oneCell;
}
return nil;
}
two way:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:testKeyOne forIndexPath:indexPath];
for(UIView * view in cell.subviews){
[view removeFromSuperview];
}
//[cell addSubview:];
return cell;
}
one way or two,or Other better way,and Reuse of specific originally, enter the reuse and take out the order of the queue order
You want to go with option one. The table view data source should not be adding or removing views from table view cells. That just gets too messy.
Another option is to have just one cell subclass, but code the subclass to hide and show views as needed. I wouldn't have it add and remove views. That's way more complex code and way more expensive time-wise, which isn't great when you're trying to get a high frame rate when scrolling.
I have a UITableView which contains so many rows.In my screen there can be UISwitch added for few rows in UITableView.Please tell me how can i do this?
Should i create a custom cell with a UISwitch & show or hide the UISwitch.
Should i add the UISwitch directly from the code in cellForRowAtIndexPath:.
Any suggestions please?
EDIT:
I have tried this code but this does not work.
SettingsCell *cell;
static NSString *CellIdentifier = #"SettingCell";
if (cell == nil)
{
cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier
forIndexPath:indexPath];
[cell setSelectionStyle:UITableViewCellSelectionStyleDefault];
[tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
}
if(indexPath.row==2 || indexPath.row==3)
{
cell.switch_value.hidden=false;
}
else
{
cell.switch_value.hidden=true;
}
cell.label_text.text = [VALUES objectAtIndex:indexPath.row];
return cell;
If you are creating a new cell then you should use that cell for only required rows. For rest of the rows you should not take pain of hiding the switch.
If you create UISwitch directly from the code in CellForRowAtIndexPath, then you should take care while re-using the cell. If you use different identifier for both cell types, the show/hide problem will get resolved.
Both solutions work here : you can add an UISwitch in a UITableViewCell in cellForRowAtIndexPath: : be just careful to check that no previously created UISwitch exists before creating a new one.
The second solution consists in using a custom UITableViewCell subclass.
I prefer this solution because you don't have to cast your subviews or use viewWithTag: method, or even to check if your custom subview was already created.
You should create some simples subclasses, such as PickerCell, SwitchCell, TextFieldcell : thus you will have your own set of custom UITableViewCell subclasses ready to be used within all your projects.
Assuming you have registered your reuse identifier, try this :=
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SettingCellIdentifier = #"SettingCell";
static NSString *StandardCellIdentifier = #"StandardCell";
if (indexPath.row == 2 || indexPath.row == 3) {
SettingsCell *cell = (SettingsCell *)[tableView dequeueReusableCellWithIdentifier:SettingCellIdentifier forIndexPath:indexPath];
[cell setSelectionStyle:UITableViewCellSelectionStyleDefault];
[tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
cell.label_text.text = [VALUES objectAtIndex:indexPath.row];
// Do whatever you want with the switch here
return cell;
}
else
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:StandardCellIdentifier forIndexPath:indexPath];
// instantiate a regular UITableViewCell
cell.label_text.text = [VALUES objectAtIndex:indexPath.row];
return cell;
}
}
I would like to customize a simple UITableViewCell so that I run the customization only once and add values (e.g., cell title) later. My app's cell is more complex - it has subviews and uses auto layout; however, a simple example, I believe, will help in focusing on the objective.
I am using iOS 8, Xcode 6.X, Objective-C and Nibs (no storyboard) to keep it simple. I have not created a custom class for UITableViewCell. Instead, I have the following code:
- (void)viewDidLoad {
[super viewDidLoad];
//[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1; //FIXED VALUE FOR EXAMPLE'S SAKE
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 3; //FIXED VALUE FOR EXAMPLE'S SAKE
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tableView:cellForRowAtIndexPath:");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
NSLog(#"cell == nil");
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
//CUSTOMIZING CELL THAT I WANT TO RUN ONLY ONCE
cell.backgroundColor = [UIColor redColor];
}
NSArray *numbersArray = #[#1,#2,#3];
cell.textLabel.text = [NSString stringWithFormat:#"%#", numbersArray[indexPath.row]];
return cell;
}
Which outputs:
tableView:cellForRowAtIndexPath:
cell == nil
tableView:cellForRowAtIndexPath:
cell == nil
tableView:cellForRowAtIndexPath:
cell == nil
FIRST QUESTION: Why is cell == nil run 3 times? It seems wasteful to run the customization code cell.backgroundColor = [UIColor redColor]; 3 times.
Now, when I enable:
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
And use:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Instead of:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
I get the output:
tableView:cellForRowAtIndexPath:
tableView:cellForRowAtIndexPath:
tableView:cellForRowAtIndexPath:
SECOND QUESTION: Why isn't cell == nil run at all?
FINAL QUESTIONS: How can I make cell == nil run only once so that I format the UITableViewCell only once? Is there a better way to customize a simple cell, running the customization code only once?
Why is cell == nil run 3 times? It seems wasteful to run the customization code cell.backgroundColor = [UIColor redColor]; 3 times.
The table view most likely displays three cells at once, hence requiring three distinct cell objects.
Why isn't cell == nil run at all?
The documentation states that -dequeueReusableCellWithIdentifier:forIndexPath: always returns a valid cell if you registered the identifier previously. It basically takes care of checking if a new cell is required for you.
How can I make cell == nil run only once so that I format the UITableViewCell only once?
You don't. You will have to customize every single instance. I would recommend to use a custom subclass though, rather then messing with UITableViewCell from the outside.
The best way to do this, is to create a custom class for your cell, and do any customization that isn't dependent on the indexPath there. Usually, I do this in initWithCoder or awakeFromNib. You should register the nib in viewDidLoad; I don't see anything wrong with the code you mention in your comment to Christian's answer, unless the name of the file is wrong. It really isn't the view controller's business to be adding subviews or customizing your cell; that code belongs in the cell's class.
BTW, this doesn't keep the customization code from running multiple times. It needs to run once for each cell instance that you create, just like it does in your original code. The number of cells created will be equal to the number that fit on the screen at one time (plus one maybe).
I have created a tableview with multiple prototype (4) cells in order to display different content in each cell but being a newb - I am not clear on how to then code this in the tableviewcontroller to pull data into the multiple cells (just using multiple simple arrays with one data point for each label for now to test it) but I can only get the data to populate in the first cell - and not clear how to code to get the data into the remaining cells. I created 4 separate customtableviewcell files as well. Can someone point me in the right direction on how to code so I can get data into the four separate prototype cells? I need it to be able scroll as it won;t all fit on the the screen which is why I chose table view to do this - but there will only ever be these four sections in the view (with different data depending on what you pushed to get here) should I not be using tableview? if I should be using something different like a view controller with 4 views instead? will it scroll so the user can see all sections? Thanks in advance for any help and suggestions.
You should assign unique Identifier to the each cell in the Storyboard. Then, you can populate appropriate cells like this:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.section)
{
case 0:
{
MyCustomCell1 *cell = [tableView dequeueReusableCellWithIdentifier:#"cell_1"];
// Configure cell
return cell;
}
case 1:
...
default: return nil;
}
}
Consider creating custom subclasses of UITableViewCell to provide handy IBOutlets.
(1) You need 4 cells. So, prepare 4 custom cells by creating subclass of UITableViewCell. You will know how to create custom cell by search on google.
(2) Set number of sections to 1.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
(3) Set desired height for each cell
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch(indexPath.row)
{
case 0:
{
return 40;
}
case 1:
{
return 50;
}
case 2:
{
return 30;
}
case 3:
{
return 45;
}
default:
{
return 0; // Default case
}
}
}
(4) Set contents of each cell. The data will come from your data source i.e. Array or Dictionary.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = [NSString stringWithFormat:#"CellIdentifier%d%d",indexPath.section,indexPath.row];
if(indexPath.row == 0)
{
CustomCell1 *objCustomCell1 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(objCustomCell1 == nil)
{
objCustomCell1 = [[CustomCell1 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
objCustomCell1.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Set row specific data here...
NSDictionary *dicObj = [arrYourDataSource objectAtIndex:indexPath.row];
objCustomCell1.myLabel.text = [dicObj objectForKey:#"your key"];
return objCustomCell1;
}
else if(indexPath.row == 1)
{
CustomCell2 *objCustomCell2 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(objCustomCell2 == nil)
{
objCustomCell2 = [[CustomCell2 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
objCustomCell2.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Set row specific data here...
NSDictionary *dicObj = [arrYourDataSource objectAtIndex:indexPath.row];
UIImage *theImage = [UIImage imageNamed:[dicObj objectForKey:#"your key"]];
objCustomCell2.myImageView.image = theImage;
return objCustomCell2;
}
// Do same for remaining 2 rows.
return nil;
}
First do this and then add comment. We will move ahead.
I have two kind of cell, one a standard title-subtitle cell, and another custom cell with two UITextField. And they have different identifier, VIEW and EDIT.
In my code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
I need to create two kind of cell. At any time, I have only one cell in edit, so I create a NSUInteger to keep track of which cell is being edited.
My code looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell;
if ( rowInEdit == indexPath.row )
{
cell = [tableView dequeueReusableCellWithIdentifier:#"EDIT"];
if ( !cell )
{
cell = [[UITableViewCell alloc]init];
}
RLSite* site = [[RLSettings settings]siteAtIndex:indexPath.row];
[[cell textLabel]setText:site.name];
[[cell detailTextLabel]setText:site.url];
}
else
{
cell = [tableView dequeueReusableCellWithIdentifier:#"VIEW"];
if ( !cell )
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"VIEW"];
}
RLSite* site = [[RLSettings settings]siteAtIndex:indexPath.row];
[[cell textLabel]setText:site.name];
[[cell detailTextLabel]setText:site.url];
}
return cell;
}
Notice that my first part is definitely flawed. I don't know how to create a cell with prototype, and how to assign reuseIdentifier to it. Furthermore, I don't know how to access those UITextField in that cell once it's created.
Can anyone help me?
e. how to create a cell with prototype you can follow these tutorials as I also learned it from one of these tutorials
Tutorials
First one Which I referred.
Second one
And to excess the added text views you need to assign them tag values (obviously distinct).
Then use the below code to excess them in side your tableView:cellForRowAtIndexPath: method.
UITextField *txtField = (UITextField *)[cell viewWithTag:8];
Here replace the 8 with your tag value :)