Assertion failure when using UISearchDisplayController in UITableViewController - ios

I've been trying to add simple Search functionality to a TableViewController in my app. I followed Ray Wenderlich's tutorial. I have a tableView with some data, I added the search bar + display controller in storyboard, and then I have this code:
#pragma mark - Table View
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"BreedCell" forIndexPath:indexPath];
//Create PetBreed Object and return corresponding breed from corresponding array
PetBreed *petBreed = nil;
if(tableView == self.searchDisplayController.searchResultsTableView)
petBreed = [_filteredBreedsArray objectAtIndex:indexPath.row];
else
petBreed = [_breedsArray objectAtIndex:indexPath.row];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = petBreed.name;
return cell;
}
#pragma mark - Search
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[_filteredBreedsArray removeAllObjects];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name contains[c] %#",searchString];
_filteredBreedsArray = [[_breedsArray filteredArrayUsingPredicate:predicate] mutableCopy];
return YES;
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption {
// Tells the table data source to reload when scope bar selection changes
[_filteredBreedsArray removeAllObjects];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name contains[c] %#",self.searchDisplayController.searchBar.text];
_filteredBreedsArray = [[_breedsArray filteredArrayUsingPredicate:predicate] mutableCopy];
return YES;
}
The standard stuff, but when I enter text in the search bar it crashes every time with this error:
2013-01-07 19:47:07.330 FindFeedo[3206:c07] *** Assertion failure in -[UISearchResultsTableView dequeueReusableCellWithIdentifier:forIndexPath:], /SourceCache/UIKit_Sim/UIKit-2372/UITableView.m:4460
2013-01-07 19:47:07.330 FindFeedo[3206:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier BreedCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
I understand that in iOS 6 the handling and dequeueing system for cells changed, and also that the search uses a different tableView, so I thought the problem was that the search tableView with the filtered results didn't know about the cell, so I put this in my viewDidLoad:
[self.searchDisplayController.searchResultsTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"BreedCell"];
And voila! It worked... Only the first time you search. If you go back to the original results and search again, the app crashes with the same error. I thought about maybe adding all the
if(!cell){//init cell here};
stuff to the cellForRow method, but doesn't that go against the whole purpose of having the dequeueReusableCellWithIdentifier:forIndexPath: method? Anyway, I'm lost. What am I missing? Help, please. Thank you in advance for all your time (:
Alex.

Try using self.tableView instead of tableView in dequeueReusableCellWithIdentifier:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"BreedCell"];
//Create PetBreed Object and return corresponding breed from corresponding array
PetBreed *petBreed = nil;
if(tableView == self.searchDisplayController.searchResultsTableView)
petBreed = [_filteredBreedsArray objectAtIndex:indexPath.row];
else
petBreed = [_breedsArray objectAtIndex:indexPath.row];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = petBreed.name;
return cell;
}
This code works pretty well
Note
If you have custom height cells, do not use
[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Use this instead
[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];

The reason why it worked great on first run but then crashed if you exited the results table and went back in for another search is because the Search Display Controller is loading a new UITableView each time you enter search mode.
By search mode I mean, you've tapped the textfield and you've began to type, at which point a table view is generated to display results, exiting this mode it achieved by hitting the cancel button. When you tap the textfield the second time and begin typing again - this is entering "search mode" for the second time.
So in order to avoid the crash you should register the cell class for the table view to use in the searchDisplayController:didLoadSearchResultsTableView: delegate method (from UISearchDisplayDelegate) of instead of in your controllers viewDidLoad method.
As follows:
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView
{
[tableView registerClass:[DPContentTableCell class] forCellReuseIdentifier:cellIdentifier];
[tableView registerClass:[DPEmptyContentTableCell class] forCellReuseIdentifier:emptyCellIdentifier];
}
This caught me by surprise because on iOS 7... the table view is being reused. So you can register the class in viewDidLoad if you prefer. For legacy sakes, I'll keep my registration in the delegate method I mentioned.

After searching, 'tableView' of cellForRowAtIndexPath method seems not an instance of the Table that you define. So, you can use an instance of a table that defines the cell. Instead of:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
Use:
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
(Do not use the tableView of cellForRowAtIndexPath method, use self.tableView.)

Dequeue the cell without using the 'indexPath' and in case of you obtain a nil element, you have to allocate it manually.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"YourCellId"];
if (!cell)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"YourCellId"];
// fill your cell object with useful stuff :)
return cell;
}
Trying to use self.tableView for dequeue the cell may cause crashes when you have a sectioned main list and a plain search list.
This code instead work in any situation.

When I had this problem, the solution was replacing tableView dequeueReusableCellWithIdentifier:#yourcell with self.tableView

I am working on that tutorial also. The default TableViewController has "forIndexPath" and in his example it doesn't exist. Once I removed it the search works.
//Default code
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//Replace with
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

for swift 3 you just need to add self:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = self.tableView.dequeueReusableCell(withIdentifier: "yourCell", for: indexPath) as! YourCell
...
}

Related

Is cellForRowAtIndexPath called when you use a custom cell?

I am using a custom cell class in a tableview controller.
When I include a statement in the tableviewcontroller in cellForRowAtIndexPath NSLog(#"method called"): it does not seem to get called.
Is it possible that this method is not called when you have a custom cell?
Edit:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"cell for row at index path called");
NSDictionary *item= [self.getItems objectAtIndex:indexPath.row];
//This sets place in storyboard VC
IDTVCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"Cell"];
cell.item = item;
if (cell == nil) {
cell = [[IDTVCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"Cell"];
}
return cell;
}
cellForRowAtIndexPath is not called if no rows are returned.
-tableView:cellForRowAtIndexPath: is not getting called
That is what happened in my case.
It can also not get returned if you reload table on wrong thread and in certain other scenarios.
cellForRowAtIndexPath: not called
However, a custom cell per se does not cause this..
To answer your question - Yes, it is.
There could be n-number of reasons why cellForRowAtIndexPath: is not getting called. This may be because delegate / dataSource is not set or UITableView frame is not set... etc. etc.
You should easily find a solution with more online research and closure look at your code.

UITableViewCell and label not displaying text

I have a UITableView with a cell that has two labels in it. The table view is linked to the dataSource and delegate, the labels in the cell are linked to an IBOutlet, what not. It looks to me like this should work, but this code below is not running so the Cell or labels are not populated with text. Any ideas? Or anything I'm missing?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"theLabelCell";
CustomClassCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
CustomClassText *customText = _arrayThatHasText[indexPath.row];
if (![self isSelectionMode]) {
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
cell.TitleLabel.text = customText.firstLabel;
cell.TextLabel.text = customText.secondLabel;
return cell;
}
Did you remember to also register the cell identifier for reuse?
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"CustomClassCell" bundle:nil] forCellReuseIdentifier:#"theLabelCell"];
}
If you are not seeing any cells, then check whether numberofSections and numberOfRowsInSection delegate methods are not returning 0
When does your _arrayThatHasText get populated? The issue might very well be that the data source (_arrayThatHasText) is getting instantiated before the numberofSections and numberOfRowsInSection delegate methods are being called and then after these methods are called the data source is being populated with actual data -> which would result in the 0 values in the delegate methods as you are experiencing.
You might want to try putting a [self.tableView reloadData] call at the end of ViewWillAppear or in ViewDidAppear method and see if that helps.

Unable to dequeue a cell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard?

I'm trying to display a TableView of a list of songs in a user's library. I used the code from this tutorial (which uses a storyboard, but I would like to try it a different way and with just a subclass of UITableView).
I get the error:
*** Assertion failure in -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:], /SourceCache/UIKit/UIKit-2903.23/UITableView.m:5261
2014-05-07 20:28:55.722 Music App[9629:60b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
and an error Thread 1: SIGABRT on the line:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
This is my code:
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
NSArray *songs = [songsQuery items];
return [songs count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
NSArray *songs = [songsQuery items];
MPMediaItem *rowItem = [songs objectAtIndex:indexPath.row];
cell.textLabel.text = [rowItem valueForProperty:MPMediaItemPropertyTitle];
cell.detailTextLabel.text = [rowItem valueForProperty:MPMediaItemPropertyArtist];
cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Table-view-background.png"]];
cell.textLabel.textColor = [UIColor colorWithRed:0.278 green:0.278 blue:0.278 alpha:1.0];
cell.selectedBackgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Table-view-selected-background.png"]];
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
return cell;
}
The app loads and works fine, showing a blank table when I run it in the iPhone simulator on my Mac. it comes up with this error when I run it on my iPhone.
Any help would be much appreciated, thanks!
If you create the table view programmatically, and you're just using the default UITableViewCell, then you should register the class (in viewDidLoad is a good place). You can also do this for a custom class, but only if you create the cell (and its subviews) in code (use registerNib:forCellWithReuseIdentifier: if the cell is made in a xib file).
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
However, this will only give you a "Basic" table view cell with no detailTextLabel. To get that type of cell, you should use the shorter dequeue method, dequeueReusableCellWithIdentifier:, which doesn't throw an exception if it doesn't find a cell with that identifier, and then have an if (cell == nil) clause to create the cell,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//Configure cell
return cell;
}
Please, if you're using a custom class for your cell check that you've already registered the nib for the cell to use in your table view, as follow:
[self.yourTableViewName registerNib:[UINib nibWithNibName:#"YourNibName" bundle:nil]
forCellWithReuseIdentifier:#"YourIdentifierForCell"];
Apart from that please check that your custom UITableViewCell subclass has the appropiate reuse identifier.
If you're not using a custom class, please follow this instructions from Apple Docs:
The data source, in its implementation of the tableView:cellForRowAtIndexPath: method, returns a configured cell object that the table view can use to draw a row. For performance reasons, the data source tries to reuse cells as much as possible. It first asks the table view for a specific reusable cell object by sending it a dequeueReusableCellWithIdentifier:
For complete information, please go to this link: Creating a Table View Programmatically.
I hope this helps!
The reason for the crash is that you are using
[tableView dequeueReusableCellWithIdentifier: forIndexPath:]
which will cause a crash unless you specify a cell or nib file to be used for the cell.
To avoid the crash, use the following line of code instead
[tableView dequeueReusableCellWithIdentifier:];
This line of code will return nil if there is not a cell that can be used.
You can see the difference within the docs which can be found here.
There is also a good answer to a Q&A that can be found here
I know this is a bit late to answer this question but here is what I was missing.
I was doing:
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
But I should be doing this:
let cell = self.tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
Notice: the self. added next to tableView.
I hope this saves you 24 hours.
if you use default UITableViewCell (not customize one) [tableView dequeueReusableCellWithIdentifier: forIndexPath:], you must registerClass or registerNib; (dequeReusableCellIdentifier:forIndexPath won’t return nil cell, if the cell is not there, then it will automately create a new tableViewCell with the default UITableViewCellStyelDefault!)
otherwise, you can use [tableView dequeueReusableCellWithIdentifier] without registerClass or registerNib
If you are using StoryBoard then select that tableviewcontroller->table->Cell, give that Cell a 'Identifier' in attribute inspector.
Make sure you use the same 'Identifier' in 'cellforrowatindexpath' method.

how to not return a UITableviewCell

I have a NSArray of NSDictionaries, in this array there are several values which I do not want to show in the UITableView, I would like to know how to avoid returning these cells in the tableView:cellForRowAtIndexPath:
I have tried to return nil; but this has caused me errors.
This is what my code looks like
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
CustomInstallCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[CustomInstallCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
cell.selectionStyle = UITableViewCellSelectionStyleGray;
currentInstallDictionary = [sortedItemsArray objectAtIndex:indexPath.row];
NSNumber *tempDP = [currentInstallDictionary objectForKey:#"dp"];
NSInteger myInteger = [tempDP integerValue];
if (myInteger == 0) {
return cell;
}
return nil; // gives error
}
any help would be appreciated.
This method must return a cell. It cannot return nil. The best thing to do is filter your list before you load your table and use the filtered array when dequeueing cells.
The UITableView is only asking for a cell because you told it to ask. Your implementation of the UITableViewDataSource protocol implements the two methods:
numberOfSectionsInTableView:
tableView:numberOfRowsInSection:
In those methods you determine how many cells should appear on screen. As Brian Shamblen answered, if you don't want that data to appear in the table view, some how ignore (filter, delete, whatever) that data when you calculate the number of sections and rows. If you do so, no "extra" cells will be requested.

dequeueReusableCellWithIdentifier does not work

I want to load around 6000 - 8000 rows in a UITableview. I get the data from the server using a async call and when I get the data I call
[tableView reloadData]
This is to refresh the table view . But because of some reason my app gets stuck and freezes .
When I debug , I found that cellforrowatindexpath is called 6000 times (on main thread) and
dequeueReusableCellWithIdentifier always returns null .
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath{
CDTableRowCell *cell = nil;
// Create and Resue Custom ViewCell
static NSString *CellIdentifier = #"CellIdentifier";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// got into render/theme objec
if(cell == nil){
cell = [[CDTableRowCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// MODIFYING CELL PROPERTIES HERE FROM AN ARRAY
// NO HTTP CALLS
}
Also, tableview starts reusing cell once I start scrolling but before that I never always create a new one.
Any clue why this strange behavior ???
try like this,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier =#"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
return cell;
}
The method in your question is not a table view datasource method. The datasource method has the table view as an argument. The method you have written is one that can be used to obtain a cell from the tableView itself, not to obtain a new cell from the datasource.
I don't know how often that method is called but overriding it is almost certainly not what you want to do.
I'm guessing you have subclassed a uitableview to be its own datasource? If so, you need to have the code in your question in the datasource method tableView:cellForRowAtIndexPath:, and not override the method as you have now.

Resources