I believe this question is my exact problem, but I was not able to fix the issue looking at the accepted answer.
UISearchBar: FilterContentForSearchText not working on a UITableView (results not shown on table)
I have a UITableViewController that allows searching. It was working perfectly via the default UITableViewCellStyle, then I decided to implement my own custom layout for the cells. I did not subclass UITableViewCell, but instead added two UILabels to the cell via Interface Builder and set up Auto Layout. I assigned them unique tags so that I can reference those labels in cellForRowAtIndexPath:. It works great - everything appears as expected, until you search. The search does work and it displays the sections and rows, but the labels on the rows have no text so the cell appears completely blank.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"List View Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
UILabel *leftLabel = (UILabel *)[cell viewWithTag:100];
UILabel *rightLabel = (UILabel *)[cell viewWithTag:101];
if (tableView == self.searchDisplayController.searchResultsTableView) {
leftLabel.text = ...;
rightLabel.text = ...;
}
else {
leftLabel.text = ...;
rightLabel.text = ...;
}
return cell;
}
I do know why the labels are blank. When you search, dequeueReusableCellWithIdentifier: returns nil so it inits a new cell with the reuse identifier, but that cell does not have a viewWithTag: and this results in left and right label becoming nil. So it obviously cannot add text to nil.
The answer provided in the link stated you should create the label in the if (!cell) statement and then call [cell.contentView addSubview:left{right}Label];. I did that, and then moved my label configuration code into that if statement as well. But when I do that, the main table's rows only has the default values of my left and right labels from Storyboard - it doesn't set the text of the labels. This is because dequeueReusableCellWithIdentifier: doesn't return nil and instead creates a new cell, so it doesn't ever set the text because that's in the if (!cell) statement.
I could not figure out how to take care of both situations: when cell is nil and when it is not. What do I need to do to fix this?
More comments: I've never used xib files before and I'd prefer to keep it that way. :) I wouldn't mind subclassing UITableViewCell if that's a solution. Of course, I would like to implement this the "proper" way - only create a cell when one is needed etc.
I think the easiest way to do this is to make your cell in a xib file if you want to use the same cell type for both the main table and the search results table. You can make a subclass if you want (you only need to put in IBOutlets to your two labels in the .h file), or do it the same way you already did using tags. In viewDidLoad of the table view controller, register the nib for both tables,
[self.searchDisplayController.searchResultsTableView registerNib:[UINib nibWithNibName:#"CommonCell" bundle:nil] forCellReuseIdentifier:#"CommonCell"];
[self.tableView registerNib:[UINib nibWithNibName:#"CommonCell" bundle:nil] forCellReuseIdentifier:#"CommonCell"];
Then in cellForRowAtIndexPath:, you only need to dequeue the cell with that same identifier, and populate the labels. There's no need to check for cell equals nil, because it never will be.
I modified one of my apps to show how you can implement cellForRowAtIndexPath. I subclassed the cell (CommonCell is the class), only adding IBOutlets to the leftLabel, and rightLabel,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CommonCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CommonCell" forIndexPath:indexPath];
cell.leftLabel.text = ([tableView isEqual:self.tableView])? self.theData[indexPath.row] : self.filteredData[indexPath.row];
cell.rightLabel.text = ([tableView isEqual:self.tableView])? self.theData[indexPath.row] : self.filteredData[indexPath.row];
return cell;
}
This is conceptually wrong: you are instantiate a NEW CELL from code that is not the cell from interface builder. If you want use that on interface builder you need to register the nib for your tableView, and associate it to an identifier (se the same identifier also in the cell on interface builder):
[self.searchDisplayController.searchResultsTableView registerNib:[UINib nibWithNibName:#"NameNib" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:#"Identifier"];
But the question is: why? The best thing to do is create a subclass and add your labels. It is really simple:
1) Create a new file CustomSearchCell object that extends UITableViewCell:
CustomSearchCell.h
#import <UIKit/UIKit.h>
#interface CustomSearchCell : UITableViewCell
#property (strong, nonatomic) UILabel *leftLabel;
#property (strong, nonatomic) UILabel *rightLabel;
#end
CustomSearchCell.m
#import "CustomSearchCell.h"
#implementation CustomSearchCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
_leftLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 10, 200, 45)];
[_leftLabel setFont:[UIFont systemFontOfSize:20.0]];
[_leftLabel setTextColor:[UIColor blackColor]];
_rightLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 45, 200, 25)];
[_rightLabel setFont:[UIFont systemFontOfSize:15.0]];
[_rightLabel setTextColor:[UIColor grayColor]];
[self.contentView addSubview: _leftLabel];
[self.contentView addSubview: _rightLabel];
}
return self;
}
#end
2) In your view controller:
#import "CustomSearchCell.h"
and:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ListCellIdentifier";
CustomSearchCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[CustomSearchCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if (tableView == self.searchDisplayController.searchResultsTableView) {
cell.leftLabel = ...;
cell.rightLabel = ...;
}
else {
cell.leftLabel = ...;
cell.rightLabel = ...;
}
return cell;
}
Related
I'm currently creating my UITableViewCells programmatically like so:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Home-Cell" forIndexPath:indexPath];
UILabel *newLabel = [[UILabel alloc] initwithframe:cell.frame];
[newLabel setText:self.data[indexPath.row]];
[cell addSubview:newLabel];
return cell;
}
This seems to create a new UILabel each time the cell is reused though, which I definitely don't want. I tried doing the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Home-Cell" forIndexPath:indexPath];
if (cell == nil) {
UILabel *newLabel = [[UILabel alloc] initwithframe:cell.frame];
[cell addSubview:newLabel];
}
[newLabel setText:self.data[indexPath.row]];
return cell;
}
but then the UILabel seems to never be created. Perhaps this is because I'm using prototype cells with Storyboard and thus the cells are never nil?
You have two solutions.
Create a custom table view cell that already has the label.
If you want to add the label in code, don't register a class for the cell. Then the dequeued cell can be nil and you can add the label at that time (like in your 2nd set of code). This also requires using the dequeueReusableCellWithIdentifier method that doesn't also take an indexPath.
You should create a UITableViewCell subclass and add "newLabel" as a property.
The cell is never nil because the method you use to dequeue the table view cell always returns a cell, creating one if it doesn't already exist in the reuse queue.
A better solution would be to create the label in the cell prototype in the storyboard.
This implementation is against MVC architecture where controller managers stuff and do not deal with view. Here, you are trying to add stuff in view from controller. It is suggested to subclass UITableViewCell as below and add your custom UI controls in there
MyTableViewCell.h
#interface MyTableViewCell : UITableViewCell {
}
#property (nonatomic, strong) UILabel *myLabel;
#end
Then you can implement layoutSubviews in your MyTableViewCell.m file to define the look and feel of your cell.
MyTableViewCell.m
- (id)initWithStyle:(UITableViewCellStyle)iStyle reuseIdentifier:(NSString *)iReuseIdentifier {
if ((self = [super initWithStyle:iStyle reuseIdentifier:iReuseIdentifier])) {
self.myLabel = [[UILabel alloc] initWithFrame:<Your_Frame>];
// Set more Label Properties
[self.contentView addSubview:self.myLabel];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
// Override run time properties
}
Finally use your custom cell like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"Home-Cell" forIndexPath:indexPath];
if (cell == nil) {
cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"Home-Cell"];
}
cell.myLabel.text = self.data[indexPath.row];
return cell;
}
As a side note, I hope you know that you get textLabel and detailTextLabel free from default UITableViewCell implementation.
I'm struggling with this basic thing for more than a day and it drives me crazy! Funny thing is, I have very similar thing on other screen and it works just fine! I have done this a thousand times, but never experienced something so odd. Maybe is this behavior in iOS 8 only?
On my very simple Prototype cell I have two labels with tags 102 and 103. But when I want to set text to them, they are always nil.
I have double checked that identifier is correct and that tag is the same as in Storyboard.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString * identifier = #"secondReusableIdentifier";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
for (UIView *subview in [cell subviews]) {
NSLog(#"subview: %lu", subview.tag); // prints 0
}
UILabel * label1 = (UILabel *)[cell viewWithTag:102]; // returns nil
UILabel * label2 = (UILabel *)[cell viewWithTag:103]; // returns nil
if (self.items.count) {
MyObject *obj = [self.items objectAtIndex:indexPath.row];
label1.text = obj.someProperty;
fuelPrice2.text = obj.someOtherProperty;
}
}
return cell;
Any suggestions would be appreciated.
In your code you are creating a new cell, that it's not the same than you have in Storyboard.
Change this: This is the old way, or the way you use when the cell is by code or nib, and you don't use storyboard. This code means.
NSString * identifier = #"secondReusableIdentifier";
// If I have available a cell with this identifier: secondReusableIdentifier, let's go to use it.
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil){
// If not, we create a new cell with this identifier. This methods is previous to storyboard, and this methods create a new cell, but does´t look in Storyboard if this identifier exist, or something like that.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
To this, in other hand when Apple launched storyboard the framework grow with this methods, that work in this way: If there is a cell free use it, if not it look in Storyboard for a cell with this identifier and create a new cell with this info. (You can use this methods also by code and with nib file, but you must register the class before...).
// Be sure than: "secondReusableIdentifier", it's its identifier in storyboard
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"secondReusableIdentifier" forIndexPath:indexPath];
Replacing this:
NSString * identifier = #"secondReusableIdentifier";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
BY
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"secondReusableIdentifier" forIndexPath:indexPath];
for creating a cell will solve your problem because, it always returns a cell. It either re uses existing cells or creates a new one and returns if there are no cells.
The most important difference is that the forIndexPath: version crashes if you didn't register a class or nib for the identifier. The older (non-forIndexPath:) version returns nil in that case.
You must register a class or nib for using this. But if you create your table view and your cell prototypes in a storyboard, the storyboard loader takes care of registering the cell prototypes that you defined in the storyboard.
Hope this helps.. :)
If
for (UIView *subview in [cell subviews]) {
NSLog(#"subview: %lu", subview.tag); // prints 0
}
prints 0, why do you do this??
UILabel * label1 = (UILabel *)[cell viewWithTag:102];
UILabel * label2 = (UILabel *)[cell viewWithTag:103];
So, if you created a custom cell with IB, you have to create a custom class and use it instead of a simple UITableViewCell
Here's my code:
- (UITableView *)table {
if (!_table) {
_table = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
[_table setDelegate:self];
[_table setDataSource:self];
[_table registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
}
return _table;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (!cell)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
else
[self configureCell:cell forRowAtIndexPath:indexPath];
return cell;
}
The problem is when I registerClass for my table, it assumes that my cell style is UITableViewCellStyleDefault. So that's why detailTextLabel doesn't show up. I tested it.
Comment out the registerClass line doesn't work because I don't have any CellIdentifier for dequeueReusableCell. So it will throw some exceptions.
If I'm not using dequeue, it works, but it not the best practice.
AFAIK, table cell couldn't change its style after init. So how can I make the detailTextLabel show up ?
The problem is when I registerClass for my table, it assumes that my cell style is UITableViewCellStyleDefault. So that's why detailTextLabel doesn't show up
That is correct. The solution is: do not register UITableViewCell as your class. Register a custom UITableViewCell subclass whose sole purpose in life is that it initializes itself to a different style.
For example, register the MyCell class, which you have defined like this:
#interface MyCell:UITableViewCell
#end
#implementation MyCell
-(id)initWithStyle:(UITableViewCellStyle)style
reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:UITableViewCellStyleValue2 // or whatever style you want
reuseIdentifier:reuseIdentifier];
return self;
}
#end
I created several cells with Interface Builder, and I'm using them to fill a UITableView. In other words, I have 3 classes for 3 different kinds of cell, and an other view which contains a UITableView.
- My UITableView containing different kinds of cells :
Here's my problem :
On the iPhone emulator, it looks great. But on the iPad emulator, the custom cells width is fixed. The UITableView width fits to the screen width, so it's good, but the UITableViewCells does not fit to the UITableView. I want to force the custom UITableViewCells to take the UITableView width.
Is there anything to do in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPathmethod, where I instanciate my custom cells ?
Or do I have to write a thing like self.fitToParent; in the custom cells header file ?
EDIT (schema) :
EDIT 2 (cellForRowAtIndexPath method) :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifierType1 = #"cellType1";
static NSString *cellIdentifierType2 = #"cellType2";
NSString *currentObjectId = [[myTab objectAtIndex:indexPath.row] type];
// Cell type 1
if ([currentObjectId isEqualToString:type1])
{
CelluleType1 *celluleType1 = (CelluleType1 *)[tableView dequeueReusableCellWithIdentifier:cellIdentifierType1];
if(celluleType1 == nil)
celluleType1 = [[CelluleType1 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifierType1];
celluleType1.lblAuteur.text = #"Type1";
return celluleType1;
}
// Cell type 2
else if ([currentObjectId isEqualToString:type2])
{
CelluleType2 *celluleType2 = (CelluleType2 *)[tableViewdequeueReusableCellWithIdentifier:cellIdentifierType2];
if(celluleType2 == nil)
celluleType2 = [[CelluleType2 alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifierType2];
celluleType2.lblAuteur.text = #"Type2";
return celluleType2;
}
else
return nil;
}
}
I think uitableviewcell's width is the same as the tableview's width.You can try to set cell's background color to test it. cell.backgroundColor = [UIColor redColor] ;
You should create a class which inherit from UITableViewCell and override it's method - (void)layoutSubviews , adjust your content's frame there.
I resolved my problem using the following code in each custom cell class. It's not very clean, but I can't spend one more day on this issue...
- (void)layoutSubviews
{
CGRect contentViewFrame = self.contentView.frame;
contentViewFrame.size.width = myTableView.bounds.size.width;
self.contentView.frame = contentViewFrame;
}
Thank you for your help KudoCC.
- (void)awakeFromNib {
[super awakeFromNib];
// anything you write in this section is taken with respect to default frame of width 320.
}
awakeFromNib is called when [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; is processed- anything you write in section is taken with respect to default frame of width 320.
You need to make another custom function and call it after cell gets initialized.
For eg:-
#implementation CheckinTableViewCell{
UILabel *NameLabel;
UILabel *rollLabel;
}
- (void)awakeFromNib {
[super awakeFromNib];
NameLabel = [[UILabel alloc] initWithFrame:CGRectZero];
rollLabel = [[UILabel alloc] initWithFrame:CGRectZero];
[self.contentView addSubview:NameLabel];
[self.contentView addSubview:rollLabel];
}
-(void) bindView{
NameLabel.frame = CGRectMake(10, 10, self.contentView.frame.size.width-20, 20);
rollLabel.frame = CGRectMake(10, 30, NameLabel.frame.size.width, 20);
}
and call this function in tableview cellForRowAtIndex:-
-(UITableViewCell*) tableView: (UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
CheckinTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if(cell ==nil){
cell = [[CheckinTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.name = #"Harry";
cell.rollno = #"123456";
[cell bindView];
return cell;
}
Where Do i add my custom tableview cell label in my UITableview when using NSCoder?
i already Created The UITableViewCell class and hooked everything up in interface builder.
I tried to replace cell.textLabel.text = oneName.name; with cell.label.text = oneName.name; and it just shows a black square.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (!cell) {
cell = [[CustomCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
NSUInteger row = [indexPath row];
DrillObject *oneName = [self.addDrill objectAtIndex:row];
cell.textLabel.text = oneName.name;
return cell;
}
Are you sure your label is of the right size? When using custom cells, I really recommend using the free Sensible TableView framework. The framework automatically loads your objects' data into the custom labels, and will also automatically resize the labels/cells whenever needed.
There are tow options to add label to your CustomCell class like you want here:
1- add a label onto your cell in xib file and the assign a tag for it and then create a getter like this:
-(UILabel*)label
{
id label = [self viewByTag:3];
if([label isKindOfClass:[UILabel class]])
{
return label;
}
return nil;
}
2- create the label in the init method and DONT forget to set the label background color to clear color like this:
UILabel *label = [UILabel alloc] initWithFrame:myFreame];
[label setBackgroundColor:[UIColor clearColor]];
// now you should have property on .h file like this
#property (nonatomic, weak) UILabel *label;
// so back to the init dont forget to do this
_label = label;
Thats it.