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.
}
Related
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"]
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 am using a UISearchDisplayController to be able to display a table with custom cells based on some data I am retrieving from a server.
First I set the UISearchDisplayController inside my UIViewController.
self.searchController = [[UISearchDisplayController alloc]
initWithSearchBar:self.mySearchBar contentsController:self];
self.searchController.delegate = self;
self.searchController.searchResultsDataSource = self;
self.searchController.searchResultsDelegate = self;
My UIViewController also implements the UISearchBarDelegate, so I can determine when a search starts. I set up a block so when my api call returns it gets called and a dictionary of results is saved in the self.searchResults property:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
// here we make the api call
[api getSomeInfo:searchBar.text complete:^(NSDictionary *json) {
self.searchResults = json;
[self.searchController.searchResultsTableView reloadData];
}];
}
Now, the problem I have is that in my UITableViewDataSource method, where I return the custom cell. My cell is instantiated, but it's IBOutlets never get initialized, so I cannot set their content (text, images, etc) properly:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.searchController.searchResultsTableView) {
cell = [tableView dequeueReusableCellWithIdentifier:#"SearchResultsCellIndentifier"];
if (cell == nil) {
cell = [[SearchResultsCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.customLabel.text = [self.searchResults objectForKey:#"customText"]; // cell exists but cell.customLabel is nil!!
}
}
Why is the content nil? Is there somewhere in my Custom Cell class where I should be setting the content up?
Thanks!
I think your problem is that you used the variable cellIdentifier when creating the cell, but a string constant when dequeuing.
Simply always recreating a cell will work, but is not efficient at all and leads to major memory leaks.
You should first set the cellIdentifier according to which table view you are in, and which kind of cell you need, then dequeue with that cellIdentifier, and then create a new one if needed.
I am trying to populate a TableView situated within a UIViewController with a collection of objects when a button is clicked.
The problem is that cellForRowAtIndexPath seems to be expecting 'votes' to be an instantiated object, which it isn't until the button is pressed.
I'm not sure I'm going about this the correct way and would appreciate any assistance anybody could give me.
I have specified the delegate and datasource as follows:
#interface MyViewController : UIViewController<UITableViewDelegate, UITableViewDataSource>
I have completed my implementation of numberOfRowsInSection as follows:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.votes count];
}
I have completed my implementation of cellForRowAtIndexPath as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Votes";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the region cell
CandidatePhrase *phrase = [self.votes objectAtIndex:indexPath.row];
cell.textLabel.text = phrase.phrase;
return cell;
}
On button press I'm loading an array with a list of objects
_votes = [[NSArray alloc] initWithObjects:myCandidatePhrase.votes, nil];
I'm just now clear on how the table will bind each time I press the button.
Here's the error I'm currently getting, presumably because the votes array hasn't been instantiated?
2011-12-09 22:34:48.979 MyApp[3809:fb03] -[NSObject tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x6b49b20
The tableView instance (not the view controller) is going to call its delegate methods as soon as it is instantiated and whenever it is informed of a change to the table view. So it doesn't matter if votes is instantiated or not for the delegate method to be called.
However, if you want it to know that there are no rows when votes has not been instantiated, try this
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (votes != nil)
return [self.votes count];
else
return 0;
}
Once you have instantiated votes, you want to call reloadData on your tableView.
Don't forget to add your view controller as the delegate for the tableView, if it is not already.
I am using a navigation controller to get to the UITableView. In this UItableView, there is a search bar and 50 cells. When i don't scroll and then hit back, the application acts normally but when i scroll down like 10 cells and then hit back, my application crashes with EXC_BAD_ACCESS Error. Any idea wat may be the reason of this crash?
In dealloc, I am releasing all the objects I created in the header file:
- (void)dealloc
{
[listContent release];
[filteredListContent release];
[tmpCell release];
[cellNib release];
[super dealloc];
}
and for the function creating the cells, it is as follows: ( Note I am doing an alternate UItableView with a searchBar)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *kCellID = #"cellID";
ApplicationCell *cell = (ApplicationCell *)[tableView dequeueReusableCellWithIdentifier:kCellID];
if (cell == nil)
{
[self.cellNib instantiateWithOwner:self options:nil];
cell = tmpCell;
self.tmpCell = nil;
}
/*
If the requesting table view is the search display controller's table view, configure the cell using the filtered content, otherwise use the main list.
*/
NSDictionary *dataItem;
if (tableView == self.searchDisplayController.searchResultsTableView)
{
dataItem = [self.filteredListContent objectAtIndex:indexPath.row];
}
else
{
dataItem = [self.listContent objectAtIndex:indexPath.row];
}
// Display dark and light background in alternate rows -- see tableView:willDisplayCell:forRowAtIndexPath:.
cell.useDarkBackground = (indexPath.row % 2 == 0);
// Configure the data for the cell.
cell.icon = [UIImage imageNamed:#"iTaxi.jpeg"];
cell.publisher = [dataItem objectForKey:#"Number"];
cell.name = [dataItem objectForKey:#"Name"];
cell.price = [UIImage imageNamed:#"order-taxi.png"];
return cell;
}
ViewDidUnload has the same code as dealloc
That error occurs because somewhere in your code you're setting scrollEnabled to "NO" (probably when you activate the searchbar):
self.tableView.scrollEnabled = NO;
I mean, if your searchText length is equals to 0 (you just entered on the search mode), you cannot disable the tableview scroll.
Hope this helped you.
Good luck, good coding!
Fábio Demarchi
When you press back while the tableview is being scrolled, the app will get crashed since the deallocated tableview instance's datasource(rarely delegate) protocol's method being called. So we could get the crash since we're accessing deallocated instance.
To avoid this just add the dealloc method in the particular view controller class, and set the corresponding protocol's to nil.
-(void)dealloc {
self.yourTableView.delegate = nil;
self.yourTableView.dataSource = nil;
}
Happy Coding :)
This is because the cells are recreated for visible rows. That is, cellForRowAtIndexPath is called for visible rows when you scroll the tableView. Remove that condition if(cell==nil) in cellForRowAtIndexPath.