Storing a grouped UITableView in an array - iphone

I am trying to store a grouped UITableView in an array (eg all the individual cells) so that i can retain which cells the user has tapped (and add the check accessory to it). Every time i try and restore the cells, i end up with the most recent cells to have been displayed (ie. the cells that have just appeared at the bottom of the screen appear at the top when scrolling back up.
i cant figure out hot to do this correctly so any help is very welcome
thanks
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyManager *dataStore = (MyManager* )[MyManager sharedManager];
NSNumber *dif = [[NSNumber alloc] initWithInt:-1];
if (indexPath.section == 0) {
dif = [NSNumber numberWithInt:0];
} else if (indexPath.section == 1) {
dif = [NSNumber numberWithInt:3];
} else if (indexPath.section == 2) {
dif = [NSNumber numberWithInt:5];
} else if (indexPath.section == 3) {
dif = [NSNumber numberWithInt:9];
}
if ([dataStore.masterArray objectAtIndex:(indexPath.row+[dif intValue])]==#"0") {
//array is initalised with 16 strings of #"0", just to fill it
static NSString *SimpleTableIdentifier = #"SimpleTableIdentifier";
NSArray *listData =[self.data objectForKey:
[self.sortedKeys objectAtIndex:[indexPath section]]];
UITableViewCell * cell = [tableView
dequeueReusableCellWithIdentifier: SimpleTableIdentifier];
if(cell == nil) {
cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:SimpleTableIdentifier] autorelease];
}
NSUInteger Row = [indexPath row];
cell.textLabel.text = [listData objectAtIndex:Row];
cell.accessoryType = UITableViewCellAccessoryNone;
[dataStore.masterArray replaceObjectAtIndex:(indexPath.row + [dif intValue]) withObject:cell];
cell.textLabel.text = [listData objectAtIndex:Row];
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
else {
UITableViewCell *cell = [dataStore.masterArray objectAtIndex:(indexPath.row+[dif intValue])];
return cell;
}
}
i would assume that this code should check to see if there is a cell at the index, if no then make and display one (this works fine). If the cell does exist, pull it out of the array an display it.

The problem is that cells are not all created at once. They are created and destroyed when necessary. If the cells go out of view, they may be destroyed to save memory. Instead of storing the cells themselves in an array, store a boolean value in an array. Then when the cell is created for a specific row, use the bool value out of the array to determine if it should be checked.

Related

Changing accessoryType for a row in a sectioned tableView after user selects the row

I have a sectioned tableView that I want my user to select one item from the table. When they select the item, a check should appear next to the item (using UITableViewCellAccessoryCheckmark). If they had made a previous selection, the check should be removed from the previously selected row. Here is the code I am using:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int newRow = [indexPath row];
int oldRow = [lastIndexPath row];
if (newRow != oldRow || newRow == 0)
{
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath: lastIndexPath];
oldCell.accessoryType = UITableViewCellAccessoryNone;
[lastIndexPath release];
lastIndexPath = indexPath;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
lastIndexPath is declared privately in the .h file.
This code works great for a small list that is not sectioned. But in a large table that is sectioned, it puts random check marks in rows in other sections. It is almost as if the cellForRowAtIndexPath is ignoring the section in indexPath.
The code also crashes if I select a row that is greater than the number of rows in the smallest section.
Here is the code for cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
NSString *key = [keys objectAtIndex:section];
NSArray *itemSection = [items objectForKey:key];
static NSString *SectionsTableIdentifier = #"SectionsTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SectionsTableIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:SectionsTableIdentifier] autorelease];
}
NSArray *rowLabel = [itemSection objectAtIndex:row];
cell.textLabel.text = [rowLabel objectAtIndex:1];
NSString *detText = [rowLabel objectAtIndex:0];
detText = [detText stringByAppendingString:#" $"];
detText = [detText stringByAppendingString:[rowLabel objectAtIndex:2]];
cell.detailTextLabel.text = detText;
return cell;
}
One problem you are having here is how you are saving the indexPath in lastIndexPath. You need to retain the indexPath you are saving in lastIndexPath. the indexPath passed into this method is autoreleased so it will likely get released out from under you if you don't retain it. This could be causing your crash.
This is probably exactly what you are looking for. I developed this for one of my apps. Enjoy ! (if so please mark as answered)
Also note that I'm using ARC, so no retain and release.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
/*
1. First we get the indexPath from the prior priorSelectedRowInteger and priorSelectedSectionInteger ivars. Note: we could use a single indexPath ivar, but we separate them into row and section here for clarity.
2. Then we reset the selectedRowInteger ivar to the currently selected row. This must be done before any rows are reloaded.
3. Then we reload only the two rows at the concerned index paths, as we have captured the indexPath of the prior selected row and the method gives us the new one. We could just simply reload the table here with [self.tableView reloadData], but it would not be animated and not as smooth.
*/
NSIndexPath *priorSelectedIndexPath = [NSIndexPath indexPathForRow:priorSelectedRowInteger inSection: priorSelectedSectionInteger];
// Now that we have the priorSelectedIndexPath, we save the new one for the next round.
self.priorSelectedRowInteger = indexPath.row;
self.priorSelectedSectionInteger = indexPath.section;
// For a changing tableView, check to make sure the priorIndexPath is still valid before trying to reload the prior row.
// NSLog(#"priorSelectedIndexPath %#", priorSelectedIndexPath);
if ((tableView.numberOfSections >= priorSelectedIndexPath.section+1) && ([tableView numberOfRowsInSection:priorSelectedIndexPath.section] >= priorSelectedIndexPath.row+1)) {
NSArray *thePriorIndexPathArray = [NSArray arrayWithObject:priorSelectedIndexPath];
[self.tableView reloadRowsAtIndexPaths:thePriorIndexPathArray withRowAnimation:UITableViewRowAnimationFade];
}
// Reload only the selected indexPath - necessary to update the text colors etc.
NSArray *theIndexPathArray = [NSArray arrayWithObject:indexPath];
[self.tableView reloadRowsAtIndexPaths:theIndexPathArray withRowAnimation:UITableViewRowAnimationFade];
}

A grouped cell is showing up in my plain tableView and I don't know why

So I don't know how this is happening, or how to fix it, but I am having an issue of my UISearchDisplayController's plain tableView displaying search results with a grouped cell.
I have a dataSource array with a few names, a tableData array that iterates through the dataSource array and adds any entries that fit the searchText, and then depending on which tableView it is, I reload the tableView with the appropriate dataSource...
Ideas anyone?
Code:
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
int count = 0;
if(aTableView == self.tableView){
count = [userTableData count]==0?1:[userTableData count];
}else{
count = [tableData count];
}
return count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return #"Users";
}
- (UITableViewCell *)tableView:(UITableView *)tView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CellIdentifier";
// Dequeue or create a cell of the appropriate type.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryNone;
}
if(tView == self.tableView){
if([userTableData count] == 0){
cell.textLabel.text = #"Please select a user";
}else{
cell.textLabel.text = [userTableData objectAtIndex:indexPath.row];
}
}else{
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
}
return cell;
}
And then as the search is entered, matching strings are added to the tableData array
assign tags to the UITableView and hide the grouuped tableView while searching
In the documentation for UISearchDisplayController about the searchResultsTableView property, it says
Discussion:
This method creates a new table view if one does not already exist.
So what you could try is create your own UITableViewwhen you set up the UISDC, explicitly making it plain:
UITableView *searchTableView = [[UITableView alloc] initWithFrame:CGRectZero
style:UITableViewStylePlain];
searchDisplayController.searchResultsTableView = searchTableView;

How to copy TableView cell data to a NSMutable Array?

Merged with How to copy TableView cell data to a NSMutable Array?.
I'm quite new to iphone development. I created To-Do List app using coredata. so, my table view dynamically updating.
I want to add table view row textlabels to a NSMutable array (that means row1 text label to 1st index position of MutableArray , row2 text label to 2nd index position of Array)
this is my table view cellfor indexpath method
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *HeroTableViewCell = #"HeroTableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:HeroTableViewCell];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:HeroTableViewCell] autorelease];
}
NSManagedObject *oneHero = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSInteger tab = [tabBar.items indexOfObject:tabBar.selectedItem];
switch (tab) {
case kByName:
cell.textLabel.text = [oneHero valueForKey:#"name"];
cell.detailTextLabel.text = [oneHero valueForKey:#"secretIdentity"];
break;
case kBySecretIdentity:
cell.detailTextLabel.text = [oneHero valueForKey:#"name"];
cell.textLabel.text = [oneHero valueForKey:#"secretIdentity"];
default:
break;
}
//listData = [[[NSMutableArray alloc] init]autorelease];
//if(indexPath.row==1){
//[listData addObject: cell.textLabel.text];
return cell;
}
commented lines are how I'm try to do it, but I struggling to do it as I want
please help me . . .

Hiding UITableViewCell

Is there a way to hide a UITableView cell? I'm looking for some property or method I can invoke on the UITableViewCell returned by a synchronous cellForRowAtIndexPath() to hide it and make it unselectable by the user.
For me using mapping is not easy way, so I decided to use SAS method. But it doesn't work with my custom cell. So, I correct it:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == 7 && hide7Row){
UITableViewCell* cell = [cells objectAtIndex:indexPath.row];
cell.hidden = YES;
return 0.0;
}
else if(indexPath.row == 8 && hide8Row){
UITableViewCell* cell = [cells objectAtIndex:indexPath.row];
cell.hidden = YES;
return 0.0;
}
else {
return 44.0;
}
}
Works Fine.
You mean to leave a gap in the table where the cell should be, or just to progress from the one before it straight to the one after it? In the former case, I guess you might try getting the cell's contentView and set its hidden property to YES; otherwise, you'll just have to do a little logic in your -tableView:numberOfRowsInSection: and -tableView:cellForRowAtIndexPath: methods, returning (the number of cells you'd otherwise return - 1) from the first, and, depending on whether the row index is less than or greater than the row you're not including, either (the cell you'd otherwise return) or (the cell at (the row index + 1)), respectively.
(edit, because the explanation was convoluted:)
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
{
if(section == theSectionWithoutARow)
{
if(shouldRemoveTheRow)
return [theArrayWithTheSectionContents count] - 1;
else
return [theArrayWithTheSectionContents count];
}
// other sections, whatever
}
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// blah blah standard table cell creation
id theCellObject;
if(indexPath.section == theSectionWithoutARow)
{
NSInteger theActualRowToDisplay = indexPath.row;
if(shouldRemoveTheRow && indexPath.row >= theRowIndexToRemove)
{
theActualRowToDisplay = indexPath.row + 1;
}
theCellObject = [theArrayWithTheSectionContents objectAtIndex:theActualRowToDisplay];
}
// now set up the cell with theCellObject
return cell;
}
There is no method to do that on the cellForRowAtIndexPath as far as I am aware.
Noah Witherspoon's method seems to be more or less workable, although it will need to be modified if you want multiple rows to be hidden.
Another way to approach it is to create a "cell map", I don't know if this is more efficient or not, but I've used it and it worked.
Let us say you have an NSArray (or mutable version thereof) of data which is to be shown in your TableView. The array's count property is used as the return value for your numberOfRowsInSection delegate method. This is a somewhat typical approach to my knowledge.
To make it so that only some of the rows are shown, I created a "mapping array", which is an NSMutableArray that contains "pointers" to your actual data array. The map contains integers wrapped in NSNumbers. In its virgin state the map's index 0 has the integer 0 (wrapped in NSNumber), index 1 has integer 1, etc.
The UITableView delegate methods are built so that the map's index count is used for numberOfRowsInSection. In the cellForRowAtIndexPath method, it looks at the appropriate index of the map array, retrieves whatever is wrapped in the NSNumber, and then looks in that index of your actual data array.
The benefit of this dereference is that it becomes extremely easy to add and remove cells from the table. Just add/remove the NSNumber objects from your mapping array. Make sense? Sorry, not at my Mac or I could just put up some code samples.
Oh, and don't forget that you have to call the update method (the exact name escapes me) on your TableView so that it refreshes and the cells hide/unhide.
Had the same problem, and as I wanted to avoid some mapping as mentioned, I just set the cell-size to 0:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger row=[indexPath row];
float ret=0.0;
if( row==3) {
ret=0.0;
}
else {
ret=40.0;
}
return ret;
}
I used Matt's technique to create a mapping to cell data. Here is some code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return computeNumberOfRowsAndMapCellData;
}
// Compute mapToCellData to map the index of the cell to the cell data for the
// cell based on TaskConfig show/hide.
// Return the number of rows in section 1.
- (NSInteger)computeNumberOfRowsAndMapCellData {
if (mapToCellData) {
[mapToCellData release];
}
mapToCellData =[[NSMutableArray alloc] init];
if (cellData) {
[cellData release];
}
cellData = [[NSMutableArray alloc] init];
NSInteger numberOfRows = 12; // maximum number of rows
NSNumber *index = [NSNumber numberWithInt:0];
// If the data is not configured to show, decrement the number of rows in the table.
if ( ! [configManager isShowDateForType:task.case_type severity:task.severity]) {
numberOfRows--;
} else {
// Add a map to the cell data with the row number.
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[df stringFromDate:task.respond_due_date], #"Respond Due", nil];
[cellData addObject:dict];
[mapToCellData addObject:index];
int value = [index intValue];
index = [NSNumber numberWithInt:value + 1];
}
// Check the configuration for the rest of the rows of cell data.
....
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
cell.textLabel.numberOfLines = 0;
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.font = [UIFont fontWithName:#"Helvetica-Bold" size:15];
cell.detailTextLabel.font = [UIFont fontWithName:#"Helvetica-Bold" size:14];
}
NSUInteger mapIndex = 0;
// Use the mapToCellData to find cell data based on show/hide in ConfigManager for the data type.
mapIndex = [indexPath row];
NSNumber *cellDataIndex = [mapToCellData objectAtIndex:mapIndex];
NSDictionary *cellDataDict = [cellData objectAtIndex:[cellDataIndex unsignedIntegerValue]];
cell.textLabel.text = = [[cellDataDict allValues] objectAtIndex:0];
cell.detailTextLabel.text = [[cellDataDict allKeys] objectAtIndex:0];

Last indexed cell in UITableView is taking on wrong font

I have the following code which is trivial at first sight. I simply set want to set the font type to Georgia with a size of 14 if the cell is from the result of a search or if there is a count of zero in my students array.
However, with this particular code cell that's last in my tableView is taking on the font of Georgia with size 14. All other cells are working proper. Where in my code is the logic wrong?
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger row = [indexPath row];
NSInteger section = [indexPath section];
static NSString *CellIdentifier = #"Student";
cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell
if([studentsSearch count] > 0) {
cell.text = (NSString *)[[[studentsSearch objectAtIndex:section] objectAtIndex:row] valueForKey:#"name"];
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
} else {
if(isSearching == YES)
cell.text = #"No students available.";
else
cell.text = #"No students have been added for this school.";
cell.font = [UIFont fontWithName:#"Georgia" size:14];
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
EDIT
What appears to be happening is when the view controller gets instantiated and pushed on top of the navigation controller's stack, my studentsSearch array is nil. I populate it within that controller.
So upon initialization, the cell has its font set to Georgia with a size of 14 because the count is < 0. However, once I populate the studentsSearch array and reload the tableView's data, the font seems to be sticking from when the view first got initialized.
I suppose now I need to find how to set the font back to that cell to what the default is.
I'm not quite sure what you're asking, but I do note that you're only setting the font to Georgia 14 when you have a search result; otherwise, you're ignoring it. If you have a cell with it's font set in the second if/then branch, and then retrieve that cell (using dequeueReusableCellWithIdentifier:), it will already have it's font set.
The simplest solution is to add
cell.font = [UIFont systemFontOfSize: 14];
after
cell.text = (NSString *)[[[...
cell.accessoryType = ...
in the first branch.
Keep in mind that table cells are recycled. Let's say your table has 15 visible rows. That means you have approximately 15 cells (or a few more) that get created and, as I said, recyled. Even if your table has hundreds of rows, it will still use the same 15 cells.
In this case, you're never resetting the font size, so once you set that font size on a cell, it will be used on any row after it that re-uses the cell.
So, if your studentsSearch count > 0, you need to make sure to set the font size to whatever your baseline is (17?).
I'd suggest that you identify the 'special' cell by giving it a different cell identifier.
In this case, you'd request the special cell with cell reuse identifier, e.g. #"None", and if cell has not yet been created, then create one and set its font.
This way, you create an extra cell with a special identifier, and it is kept separate from the other regular cells in your table.
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger row = [indexPath row];
NSInteger section = [indexPath section];
static NSString *StudentCellIdentifier = #"Student";
static NSString *NoneCellIdentifier = #"None";
// did we find students?
BOOL found = [studentsSearch count] > 0;
// get/create correct cell type
cell = [tv dequeueReusableCellWithIdentifier:(found ? StudentCellIdentifier : NoneCellIdentifier)];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:(found ? StudentCellIdentifier : NoneCellIdentifier)];
}
// return a student, or None cell if no studnts found
if( found )
{
cell.text = (NSString *)[[[studentsSearch objectAtIndex:section] objectAtIndex:row] valueForKey:#"name"];
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
}
else
{
if(isSearching == YES)
cell.text = #"No students available.";
else
cell.text = #"No students have been added for this school.";
cell.font = [UIFont fontWithName:#"Georgia" size:14];
cell.accessoryType = UITableViewCellAccessoryNone;
}
return [cell autorelease];
}