Reusing UITableViewCells with a UISearchDisplayController - objective-c

I came up with an issue that I managed to fix, but the thing is that I don't understand how it was fixed, lol. Can anyone help educate me?
I am making a simple table view app with an array of arrays with strings split into sections using UILocalizedIndexedCollation. I also have a UISearchBar setup in the header of the tableview that the user can use to find a particular string.
It got a little weird when I setup the UISearchDisplayController methods and added a bunch of if statements to pull the search results into the table view.
Here is my cellForRow:atIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"Simple Cell";
UITableViewCell *cell = nil;
// Get a cell from the table view.
if (tableView == self.searchDisplayController.searchResultsTableView) {
// Grab a cell for the search results
cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
} else {
// Grab a cell for the main table view
cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
}
// Setup and return the cell.
if (tableView == self.tableView) {
cell.textLabel.text = self.sectionsArray[indexPath.section][indexPath.row];
} else {
cell.textLabel.text = self.searchResults[indexPath.row];
}
return cell;
}
This works just fine, but when I change the section where I grab a cell from the self.tableView to:
// Get a cell from the table view.
cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
I get the following error when I run the simulator:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'request for rect at invalid index path ( {length = 2, path = 0 - 2})'
It isn't a huge deal if I don't understand why this happens, but it would be nice to know!
Thanks for reading through this :)

Related

dequeueReusableCellWithIdentifier cannot retrieve identified cell in storyboard

I created a UITableViewController of dynamic prototype cells in the Storyboard that can show 3 different custom cells
But I am getting *** Assertion failure in -[UITableView _configureCellForDisplay:forIndexPath:], / which theoretically means that the reuseidentifier I am trying to use in my cellForRowAtIndexPath does not exist but as I am sure I have defined it in my storyboard.
This is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
HomeOverviewItem *item = [_items objectAtIndex:indexPath.row];
NSLog(#"cell at %#",indexPath);
if([item propCellType] == HomeOverviewItem_HEADER){
CellHomeHeader *cell = (CellHomeHeader *)[tableView dequeueReusableCellWithIdentifier:#"cell_header"];
cell.propLabelName.text = item.propName;
cell.propLabelCntReqs.text = item.propRequestCnt;
cell.propLabelCntDays.text = item.propDaysCnt;
NSLog(#"returning header cell %#",cell);
return cell;
}else if([item propCellType] == HomeOverviewItem_ITEM1){
CellHomeItem1 *cell = (CellHomeItem1 *)[tableView dequeueReusableCellWithIdentifier:#"cell_item1"];
cell.propLabelName.text = item.propName;
cell.propLabelCntReqs.text = item.propRequestCnt;
cell.propLabelCntDays.text = item.propDaysCnt;
NSLog(#"returning item1 cell %#",cell);
return cell;
}else{
CellHomeItem2 *cell = (CellHomeItem2 *)[tableView dequeueReusableCellWithIdentifier:#"cell_item2"];
cell.propLabelName.text = item.propName;
cell.propLabelCntReqs.text = item.propRequestCnt;
cell.propLabelCntDays.text = item.propDaysCnt;
NSLog(#"returning item2 cell %#",cell);
return cell;
}
}
EDIT:
I will add this image below to make sure that identifier is the same to what I am using on my code
Error:
Where could I possibly go wrong?
I bet your call to dequeueReusableCellWithIdentifier: is returning nil because initially there are no cells to be dequeued (from the docs: A UITableViewCell object with the associated identifier or nil if no such object exists in the reusable-cell queue.). Use
- dequeueReusableCellWithIdentifier:forIndexPath: as this will always return a valid cell.
I found the cause of my problem many thanks to the hint given by Bob....
I want to post this answers for future devs who might also experience this problem.
The one causing this problem is how I instantiate the viewcontroller.
for this case, NEVER instantiate the viewcontrollerv via alloc then init
instantiate the view using [storyboard instantiateViewControllerWithIdentifier:#"yourViewControllerID"]

UITableView Data Inconsistency

I have a UITableView backed by a NSArray.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
id item = [self.data objectAtIndex:indexPath.row];
cell.item = item;
return cell;
}
Very standard. Now the problem is that reloadData will ask for numberOfSections and numberOfRows synchronously, but will call cellForRow asynchronously. So sometimes, by the time cellForRowAtIndexPath gets called, the data array has changed, and so [self.data objectAtIndex:indexPath.row] gets an out of bounds exception and crashes the app. How do I avoid this?
Note that every time I set the data array, I also call [self.tableView reloadData].
cellForRowAtIndexPath gets called frequently, (on scroll etc), you could just add a simple line of code to check if the size of the data array is smaller than the cell being requested. Although this means you might end up with blank cells.
I'd set breakpoints on both methods, right click the breakpoints -> "edit breakpoint" and tick "automatically continue after evaluating". Then click "add action" -> "debugger command" and then type "po data" or "po [data count]".
This will print information about the array in the debug console every time the breakpoint is hit (without stopping). You should then be able to look through the debug output and see where it is falling out of sync. Add some NSLog statements to to tell you which method is is being called etc and work from there.
I think the best way to avoid such a situation is to avoid user interaction while data is updated.May be you can show a screen to user that "updating.." and an activity indicator.
Another way is that to have another array to populate new data, handling can be done in separate thread and at times only it is assigned back to the datasource array with reloading call after that.There also a screen with same can be used while datasource array gets changed
Quick hack I used, try this and see if it works for you:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
// -----------------------------------------------------------------
// the magical line that prevents the table from fetching the data
// -----------------------------------------------------------------
if([indexPath row] < [self.data count])
{
id item = [self.data objectAtIndex:indexPath.row];
cell.item = item;
}
return cell;
}
:D
You should store a local array, that doesnt get modified. then, when your base array changes, you can update your storred array safely. Look into adding/removing cells from a table view using the built in api Add rows to existing UITableView section

Cells become empty after scrolling. (Xcode)

I have a problem with my tableview. When I scroll and a cell disappears from the screen it becomes blank. I have built a prototype cell in the storyboard with two labels and one imageview, it have the same identifier that I use in my code. I also have built a custom class for the customcell. Here is the code in cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
Scientist *currentScientist = [[xmlParser data] objectAtIndex:indexPath.row];
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.self.cellName.text = currentScientist.self.name;
cell.self.cellSubject.text = currentScientist.self.subject;
cell.self.cellImage.image = currentScientist.self.image;
return cell;
}
I don't know if you need more code to help me.
In my case, creating a different cell identifier for each one worked just fine. I have something like:
NSString *cellIdentifier = [NSString stringWithFormat:#"identifier%i%i", indexPath.section, indexPath.row];
The rest should remain the same.
I found an article that details the problem you are encountering. I would also recommend printing out the Scientist data to ensure an object is being fetched correctly with the objectAtIndex:indexPath.row call.
From the article I linked below, I would be willing to bet that your dequeueReusableCellWithIdentifier is the problem. One quick way you could solve this is by giving each cell its' own unique cell identifier (Just to test out the theory). However, it looks the appropriate way to solve this is by changing up your cellForRowAtIndex method.
Source

UITableViewDataSource method

Here is the CellForRowAtIndexPath method of UITableViewDataSource protocol. I saw that code on a website.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *TableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TableIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TableIdentifier];
}
cell.textLabel.text = [playersReady objectAtIndex:indexPath.row];
return cell;
}
My questions are:
Why when here defined cell wrote = [tableView dequeueReusableCellWithIdentifier:TableIdentifier]; ? What that means? If I commented that code everything going OK. For what that code? Hmmm...
How cell in if statement can be equals to nil if cell equals to TableIdentifier (SimpleTableItem)? For what wrote that code?
Why TableIdentifier equals to SimpleTableItem? For What?
An iPhone doesn't have a lot of memory. But even on modern computers you wouldn't want to initialize a new cell for every cell in your table. That's just wasting memory. So instead Apple came up with this idea of reusable cells. You only have to initialize a few cells that fill your screen (table view). Then, when the user scrolls down some new cells will appear at the bottom of the screen, but at the same time other cells will disappear at the top of the screen. So you can simply take those cells and reuse them.
Luckily UITableView manages this for you. All you have to do when you need to setup a new cell in that method is ask the table view, if it has any cells available that can be reused. If there are reusable cells, dequeueReusableCellWithIdentifier: will return one of them. But if there are non available yet (typically when you first fill up your table view with the initial cells) it will return nil. So you have to test if cell is nil and create a new cell from scratch if that's the case.
On iOS 6.0 there is a new method dequeueReusableCellWithIdentifier:forIndexPath: that always returns a valid cell (it creates the cell for you if there is no reusable cell yet).
Table view Create only those cell which can Display at one time on screen.After this system reuse cell for save memory .
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 20;
}
-(UITableViewCell *)tableView:(UITableView *)tableViewL cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableViewL dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
NSLog(#"Cell == nil so create a new cell....");
}else {
NSLog(#"Reuse Cell ");
}
return cell;
}
CellIdentifier use for identify cell for example if you add label on first ten table on 12 cell you add a button it give you a problem when you reuse cell.so that we need create a different cell For add button on cell and give it a Identifier string.

UITableView "cellForRowAtIndexPath" method gets called twice on a swipe abruptly

I think many of us has faced this problem on UITableView delegate method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath which gets called twice.
In my application I transforming the tableView. The code is:
CGAffineTransform transform = CGAffineTransformMakeRotation(-M_PI/2);
theTableView.transform = transform;
theTableView.rowHeight = self.bounds.size.width;
theTableView.frame = self.bounds;
Now inside the delegate method I am doing a couple of things:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
modelRef.currentCellAtIndexPathRow = indexPath.row;
static NSString *CellIdentifier = #"Cell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier frame:self.bounds] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
modelRef.currentPageIndex = (indexPath.row + 1);
[cell showPage];
NSLog(#" visible cell %i ",[[tableView visibleCells] count]);
return cell;
}
At a time 1 cell is visible, but first time when the application launches. The log shows visible cells 0.
Many a times this particular delegate method gets called twice abruptly.
How can I solve this?
I think an immediate fix is just to set a flag which changes the first time it is hit, so then you ignore the second call. It's probably not the perfect solution, and I can't tell you why it gets hit twice - but this will work. (I have experienced exactly the same behavior when I implemented an Apple delegate from the UIWebView class)
EDIT:
Create a BOOL member in the class header, then in the init set the value to be YES. So if the BOOL is called mbIsFirstCall for example, in your delegate method, do the following:
if (mbIsFirstCall)
{
// do your processing, then the line below
mbIsFirstCall = NO;
}
else
{
// you don't need this else, but just for clarity it is here.
// you should only end up inside here when this method is hit the second time, so we ignore it.
}