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
Related
So I'm trying to add a UITableView on the lower half of my ipad app which will be used to display a search result. This is how I did it.
I added a UIView
I added a UItableView onto the UIView
I then dragged the UITableView to the ViewController so it can connect to it for delegate and datasource.
This is what it currently looks like:
(It's at that middle top row)
So I added the following onto the viewcontroller class to generate the data
# pragma mark TableView properties
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"SearchResultCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = #"test";
}
The debugger would go through all these but would get stuck after the "cellForRowAtIndexPath" method:
It would just go through that and would not end until I stop the whole debugging. Not really sure what's going on.
Thoughts? Maybe you guys can point me to the right direction as to how I should generate my search results.
Thanks!
I usually find it much more faster and easier to use the free Sensible TableView framework to do automatic table view searches, instead of using the regular datasource/delegate system which I could never get right.
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.
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 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.
}