So I have a tableView that has sections and rows, and it uses a custom cell class. The custom cell has an image view and a few labels. The table view works fine, and the search works, except the search does not display any of the labels that are in my custom cell class, only the imageView with the correct image. I am quite confused as to why this is, especially since the image is still displayed, but not the labels. Here is some code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//TODO: problem with search view controller not displaying labels for the cell, needs fixing
JSBookCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if(cell == nil) {
cell = [[JSBookCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
JSBook *book = nil;
//uses the appropriate array to pull the data from if a search has been performed
if(tableView == self.searchDisplayController.searchResultsTableView) {
book = self.filteredTableData[(NSUInteger)indexPath.section][(NSUInteger)indexPath.row];
}
else {
book = self.books[(NSUInteger)indexPath.section][(NSUInteger)indexPath.row];
}
FFMetaData *data = [self.ff metaDataForObj:book];
cell.titleLabel.text = book.title;
cell.priceLabel.text = [NSString stringWithFormat:#"$%#", book.price];
cell.authorLabel.text = book.author;
cell.descriptionLabel.text = book.description;
cell.dateLabel.text = [self.formatter stringFromDate:data.createdAt];
if(book.thumbnail == nil) {
cell.imageView.image = [UIImage imageNamed:#"messages.png"];
[self setCellImage:cell withBook:book atIndex:indexPath withTableView:tableView];
}
else {
cell.imageView.image = [UIImage imageWithData:book.thumbnail];
}
return cell;
}
Before this problem, I had only one section in the tableView, and everything worked perfectly. Now that I have multiple sections and rows the search is broken as I described. Any ideas? Also, for [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; I used to have [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; But now if I use that I get a weird exception when I try to search:
NSInternalInconsistencyException', reason: 'request for rect at invalid index path ( 2 indexes [1, 1])'
So that is confusing me also. Thanks for the help!
[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
did not work because table view cells are registered to a specific table view. This will not work for your search results controller table view. You did find this out yourself and switched to:
[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
which is the right thing to do.
Also, designing your custom cell in storyboard will not really work for your search results controller because you are not able to design cells for search table view, only for the main table view.
Yes, you can register that class for your search table view, as you did here,
[self.searchDisplayController.searchResultsTableView registerClass:[JSBookCell class] forCellReuseIdentifier:CellIdentifier];
but that will not have any of the stuff you designed in your custom cell in storyboard. You would have to create all programmatically.
I got the same exception when I tried to search and some how this fixes it.
if (tableView == self.searchDisplayController.searchResultsTableView) {
cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}else{
cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
}
My summary for UITableView with Search Bar and Search Display using same custom cell designed in storyboard protype:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CellIdentifierAsYouDefinedInStoryboard";
CustomTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if (tableView == self.searchDisplayController.searchResultsTableView) {
/* searchResultsTableView code here */
} else {
/* Base tableView table code here */
}
/* more cell code here */
return cell;
}
and then add this line for searchResultsTableView to match your custom cell height:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.searchDisplayController.searchResultsTableView setRowHeight:self.tableView.rowHeight];
/* more of your viewDidLoad code */
}
If you are using a UITableView in a UIViewController and you want to reuse a Cell Identifier you created in your StoryBoard for your searchDisplayController, try this:
StoryBoard > UIViewController > Reference Outlets > link tableView to your UIViewController's .h file and call it something like tableView so you should have something like this:
#property (strong, nonatomic) IBOutlet UITableView *tableView;
So rather than doing it like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]
}
do this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]
}
If you create a cell in the storyboard you should not register the class (this actually screws things up). You register the class if you make the cell in code , and you register a nib if you make the cell in the nib. If you make it in the storyboard, you don't register anything, you use dequeueReusableCellWithIdentifier:forIndexPath:, and you don't need the if (cell == nil) clause at all.
Try with this, it's work for me
JSBookCell * cell = [yourtableview dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Related
When scrolling my UITableView (tends to be when I scroll it fast) the data for my cells gets mixed up, so labels might be repeated etc.
I understand that reusing the cells probably causes this, but what if the user scrolls down the list really quickly and all the cells get mixed up, how am I supposed to avoid this?
Thanks.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"VideoListCell";
VideoListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[VideoListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
if (isPlaylistView)
{
//Fill cell with data
}
else if (isPlaylistDetailView || isSearchResultsView)
{
//Fill cell with data
}
else
{
//Playlist button and uploads
if (indexPath.section == 0)
{
//Fill cell with data
}
else
{
//Fill cell with data
}
}
return cell;
}
You generally use this kind of code:
cell = dequeReusableCell;
if (cell == nil) {
create cell;
initialize cell;
}
fill cell with actual data from current row
return cell;
If you will move code "fill cell with actual data from current row" into "if" — you will get the kind of behavior you get right now.
So the answer will be "fill cell with data after you initialize it, outside of "if (cell == nil)" block.
UITableView will ever only dequeue a cell for reuse if the position that the cell was in is currently off-screen. So you don't have to worry about "mix-ups".
static NSString *cellIdentifier=#"cell";
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell==nil)
{
cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
}
I think it will be helpful to you.
set dequeueReusableCellWithIdentifier to nil for example..
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
UPDATE:
See this Example... i load many data in the cell with also my custom Gridview...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* cellIdentifier = #"gridCell";
UITableViewCell *gridCell = [tableView dequeueReusableCellWithIdentifier:nil];
if(gridCell == nil)
{
gridCell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
return gridCell;
}
hope this help you....
In your custom cell class override prepareForReuse method. In this method set text of your labels to nil and set imageview's image to nil also. This function is called everytime when a cell is reused so your problem will be solved by this. May be like this
- (void)prepareForReuse{
[super prepareForReuse];
self.titleLabel.text = nil;
self.unitImageView.image = nil;
}
I am using ios5 storyboards with a UITableViewController with a UITableViewCell subclass. I do not want to design the visual elements of the cell in the storyboard designer for the view, because I want to use a reusable subclass of UITableViewCell (specifically TDBadgedCell).
I have set my cell identifier in the storyboard designer, and all of the rows load correctly in the UITableView as long as I'm not setting any of the properties unique to TDBadgedCell. If I set the badgeString property though which is unique to TDBadgedCell, I get an exception. I narrowed down that dequeueReusableCellWithIdentifier: is not returning a cell of type TDBadgedCell.
I'm only running into this with a UITableViewController. I have a UIViewController with an embedded UITableView set up in the same fashion and it's not an issue. Any ideas?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString *CellIdentifier = #"PhoneNumberCell";
TDBadgedCell *cell = (TDBadgedCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[TDBadgedCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if ([cell isKindOfClass:[TDBadgedCell class]])
{
NSLog(#"It is TDBadgedCell");
}
else
NSLog(#"It is NOT TDBadgedCell");
I had a similar issue in that I am subclassing UITableViewCell but not using storyboard. Here is my solution to using different cell classes dependent on if the user had purchase the unlock feature of the app. Hope it helps someone.
In a nutshell, I had the cell with several objects including a UITextView object. I wanted to lock down the copy and paste feature of the UITextView object in the lite version but then release the feature once the user had purchased in in-app product.
I had two UITableViewCell classes, one with UITextView as it is and another with UITextView subclassed with canBecomeFirstresponder returning NO. That way the user could still scroll up and down the UITextview data but not copy and paste the data.
Here is the code and all I had to do was rename the reuse identifiers.
WHY? Because [self.tableview reloadData] would not rebuild the cells with the new class as the cell was still in existence. New cells off the screen would get the new class but existing ones would not. This solution rebuilds all cells once off after the purchase unlocking the added feature.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (your test if in-app was purchased is yes)
{
static NSString *MyIdentifier = #"MyCell";
FrontCell *cell = (FrontCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[FrontCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.shouldIndentWhileEditing = NO;
}
//....///
cell.trackDetails.text = [yourObject objectAtIndex:indexPath.row];
cell.trackDetails.delegate = self;
cell.trackDetails.tag = indexPath.row;
return cell;
}
else // inapp not purchased
{
static NSString *MyLockedIdentifier = #"MyLockedCell";
FrontCellLocked *cell = (FrontCellLocked *)[tableView dequeueReusableCellWithIdentifier:MyLockedIdentifier];
if (cell == nil)
{
cell = [[FrontCellLocked alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyLockedIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.shouldIndentWhileEditing = NO;
}
//....///
cell.trackDetails.text = [yourObject objectAtIndex:indexPath.row];
cell.trackDetails.delegate = self;
cell.trackDetails.tag = indexPath.row;
return cell; }
}
In storyboard,you can set the Custom Class property for a subclass of UITablviewCell.
Then dequeueReusableCellWithIdentifier method will return cell with the type of your subclass.
I think you are using wrong method for dequeuing the cells.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [self.tblProfileInfo dequeueReusableCellWithIdentifier:#"PostCell" forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
You forgot for indexPath at the end.
I've used Xcode 4.2 to create a storyboard-based iOS application.
One of my screens contains a UITableViewController, using dynamic custom cells.
So far - so good.
Now, I wanted to add a UISearchDisplayController to allow filtering my list.
For some reason, the UISearchDisplayController won't display my custom cells, and I can't find a way to force it...
This is what my cellForRowAtIndexPath method looks:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"QueueListCell";
QueueListTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[QueueListTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
}
assert(cell);
if ([tableView isEqual:self.searchDisplayController.searchResultsTableView]) {
indexPath = [_indexPathsForSearchResults objectAtIndex:indexPath.row];
}
// Set up the cell...
NSDictionary* itemDict = [_ListItems objectAtIndex:indexPath.row];
cell.labelQueueName.text = [itemDict objectForKey:kQueueName];
cell.labelQueueNumItems.text = [[itemDict objectForKey:kQueueNumItems] stringValue];
return cell;
}
Any thoughts on how to get this working? I mean, my UISearchDisplayController table DOES show the correct number of results (I know that since I can click on them, and I added an NSLog to let me know what I'm clicking on...)
This is my table view
This is how the search display table looks like...
My problem/question is how to make the UISearchDisplayController table view show my custom cells?
Any help appreciated...
Reuven
Answer specific to query
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}
For Complete example, have the sample code from apple's site.
Step by step illustration
I have been trying to figure out how to set the accessoryType to UITableViewCellAccessoryCheckmark when the cell is selected but am having trouble finding a decent example of this.
If you know how to do this or a good tutorial could you please let me know that would be great.
To restrict the user to just one selection, meaning to create an exclusive list of one choice only, you could follow these steps;
Firstly, have a global index path declared in your .h file to keep track of the already selected cell ->
NSIndexPath *oldIndexPath;
When you create the cells, be sure to set the accessory type to none, so that no cell is selected by default when the table is seen;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CallIdentifier"];
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Finally, in the didSelectRowAtIndexPath delegate method, add the following code which will remove the checkmark from the already selected cell, and add a checkmark to the newly selected one.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (oldIndexPath==nil) { // No selection made yet
oldIndexPath=indexPath;
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
else {
UITableViewCell *formerSelectedcell = [tableView cellForRowAtIndexPath:oldIndexPath]; // finding the already selected cell
[formerSelectedcell setAccessoryType:UITableViewCellAccessoryNone];
[cell setAccessoryType:UITableViewCellAccessoryCheckmark]; // 'select' the new cell
oldIndexPath=indexPath;
}
}
Hope this works out! :)
Something like this may work:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:myTableView cellForRowAtIndexPath:indexPath];
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
To answer the comment below, just push a viewController in the same method like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:myTableView cellForRowAtIndexPath:indexPath];
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
// Then push a new view
iPhoneCustomViewController *myVC = [[iPhoneCustomViewController alloc] initWithNibName:#"iPhoneCustomViewController" bundle:nil];
[self.navigationController pushViewController:myVC animated:YES];
[myVC release];
// And deselect the row (if desired)
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Did you know that:
1.) UITableView keeps track of the index paths for the rows that have been selected? It's in an array called indexPathsForSelectedRows
2.) UITableView has a flag you can set to make it either single or multiple selection. You can change it by calling the setter setAllowsMultipleSelection:(BOOL).
So, assuming that the table has been set to single selection, we can do the following in the tableView:CellForRowAtIndexPath method ...
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *simpleTableIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
[[cell textLabel] setText:#"Some Text"];
NSArray *selectedIndexPaths = [tableView indexPathsForSelectedRows];
if ([selectedIndexPaths containsObject:indexPath]) {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}else{
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
return cell;}
This implementation of CellForRowAtIndexPath will give you a clean checkmark with no gray background when a cell is selected. You will need to set the checkmark in the didSelectRowAtIndexPath delegate method to make sure a cell gets the checkmark the moment it gets selected.
No need to create separate ivars or anything else to keep track of what was or wasn't selected. It's all neatly contained in the UITableView as Apple intended.
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
newCell.accessoryType=UITableViewCellAccessoryCheckmark;
Implement this in didSelectRowAtIndexPath delegate method
From the docs:
The delegate handles selections in this method. One of the things it
can do is exclusively assign the check-mark image
(UITableViewCellAccessoryCheckmark) to one row in a section
(radio-list style). This method isn’t called when the editing property
of the table is set to YES (that is, the table view is in editing
mode). See "Managing Selections" in Table View Programming Guide for
iOS for further information (and code examples) related to this
method.
Here is an example:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]
cell.UITableViewAccessoryType = UITableViewCellAccessoryCheckmark;
}
I have a table into wich I'd like to mix some custom cells and standard cells.
I'm not really at ease with custom cells.
To do this, I wrote this code, I guess it's not optimal :
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.dataList count] + 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier;
UITableViewCell *cell;
if (indexPath.row+1 == [tableView numberOfRowsInSection:0]) {
CellIdentifier = #"Cell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
}
else {
CellIdentifier = #"ChooseSounds_cell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
UIViewController *c = [[UIViewController alloc] initWithNibName:CellIdentifier bundle:nil];
cell = (ChooseSoundsOneCell*)c.view;
[c release];
}
[((ChooseSoundsOneCell*)cell).playSoundBtn addTarget:self action:#selector(playSound:event:) forControlEvents:UIControlEventTouchUpInside];
}
return cell;
}
It's a mix of the default code from defaut TableViewController source, and a source I've found here to use a custom cell. There are some little difference beetween both, but as I don't understand really what is done, I separated the two blocks. I use the standard cell just for the last line.
I could use a custom cell to, but I don't like writing code or using xibs if there are already existing tools to do what I want to do.
So the question is : what would be the good code to do this ?
The usual pattern is to set the data into the cell in tableView:cellForRowAtIndexPath: not tableView:willDisplayCell.
For the latter the documentation states (in part):
This method gives the delegate a chance to override state-based properties set earlier by the table view, such as selection and background color.