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"]
Related
I am using the code below and getting (Use of undeclared identifier "TableCell")
In the property attribute inspector TableCell is already defined under custom
Any idea?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"TableCell";
TableCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
int row = [indexPath row];
cell.TitleLabel.text = _Title[row];
cell.Desctiptionlabel.text = _Description[row];
cell.ThumbImage.image = [UIImage imageNamed:_Images[row]];
return cell;
}
If you use the dequeueReusableCellWithIdentifier:forIndexPath: method, you must remember to register the identifier with a class or a NIB first.
That code for your class will probably look something like the following in viewDidLoad, perhaps.
[self.tableView registerNib:[UINib nibWithNibName:"TableCell" bundle:nil] forCellReuseIdentifier:CellIdentifier];
Alternatively, you can use the older dequeueReusableCellWithIdentifier: method, but you will have to handle the case that cell will be nil after dequeue and you will need to load the cell from a nib or instantiate a new object manually.
As an aside, the convention in Objective-C development is to use lower camel case class member names and local variables.
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 :)
I am creating the cell programatically using the reuse identifier.
Note - I am not using storyboard for creating the cell
Whenever the cell is dequeued, the cell is nil, so the cell needs to be newly created using alloc, which is expensive.
EDIT (added 1 more question and corrected code)
Question
Why does this dequeue always return nil ? How can I correct it ?
Does dequeue work only when used along with storyboard / nib file ?
Code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell) //Every time cell is nil, dequeue not working
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
return cell;
}
You need to first set the CellIdentifier as Cell. Are you doing that? When you are creating a new cell you need to assign this identifier Cell to it. only then iOS will be able to dequeueReusableCellWithIdentifier with that identifier. Programatically you can do it like so -
UITableViewCell *cell = [[UItableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"Cell"];
You can set identifier from Interface Builder too -
I was making a couple of mistakes:
I was using a subclass of UITableViewController, but was creating the tableView outside of the subclass
There is a tableView created in the table view controller, which is self.tableView In the tableview controller while returning the cell for index path, I was using self.tableView instead of tableView.
Also, ensure that the cell identifier is declared as static
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
Since tableView and self.tableView were representing different tables, the cell was not being dequeued from the same table and hence was always nil
This code should be generating the warning "control reaches end of non-void function" because you aren't actually returning anything. Add return cell; to the end of the function. Additionally, you never add the reuse identifier to the newly created cells.
- (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];
}
return cell;
}
First declare cell identifier for a tableViewCell at the viewDidLoad method as:
[tableView registerClass:UITableViewCell.class forCellReuseIdentifier:#"MyCell"];
Now recall the instance of the UITableViewCell with the same identifier "MyCell" as:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyCell" forIndexPath:indexPath];
Further just fill up the cell .. Now logic executes that limited number of cells are able to show enormously large list efficiently (using dequeue concept).
But remember to assign value (even nil if required) to every UIView used in the cell, otherwise overwriting / overlapping of text / images will happen.
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
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.
}