In my tableView there is five rows that always exist in the table ,,, when I tap on one of them new three rows will appear under the tapped one.
If the first five rows is a default UITableViewCell ,,, I want the three cells also to be from another custom cell; so the main five cells will be different from the the secondary rows (three under the main cell).
Here is how I add the secondary rows :
In tableView delegate didSelectRowAtIndexPath :
NSInteger count = indexPath.row + 1;
NSMutableArray *newRows = [NSMutableArray array];//
self.expandedIndex = indexPath.row + 1;
self.tableExpanded = YES;
for(int i = 0; i < [self.numbers count]; i++)
{
NSIndexPath *path = [NSIndexPath indexPathForRow:count inSection:0];
[newRows addObject:path];
[self.myArray insertObject:[self.numbers objectAtIndex:i] atIndex:count];
count++;
}
[tableView insertRowsAtIndexPaths:newRows withRowAnimation:UITableViewRowAnimationTop];
The rows added but I want them to be from the custom cell that I want to create ,,, any ideas
This is what I would do. I would make newRows a property and then at the end of didSelectRowAt... I would call table refresh. Then in my cellForRowAtIndexPath check to see if the specific indexPath is in newRows, if it is use a different cell style:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell;
if ([self.newRows contains:indexPath]) {
// create custom cell
} else {
// create normal cell
}
return cell;
}
If this does not make sense let me know and I can elaborate.
Also note, you will have some interesting edge cases to handle. For example, if the user clicks on the 3rd cell and new cells are inserted into rows 4-6, and they later click on cell 2, adding new cells in rows 3-5, then the previous 4-6 cells will now be at rows 7-9. I am not sure how you are you are structuring your app so this may not be an issue (like maybe on one of the five cells can have subcells added at a time) but just realize that this is a possibility.
Related
I'm extremely confused. I have looked at several posts to try to figuere out how to do this. What i would like to do is detect when a UITableViewCell is tapped and then insert a row right below that row with a menu.
I implemented detecting the tapped item but I can't figure out how to insert the new row right below the row that was just tapped.
Here is the method that handles the tapped cell.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
indexPath = nil;
[tableView deselectRowAtIndexPath:indexPath animated:YES];
Locations *location = nil;
if (self.searchDisplayController.active) {
indexPath = [self.searchDisplayController.searchResultsTableView indexPathForSelectedRow];
location= [self.searchResults objectAtIndex:indexPath.row];
NSLog(#"location : %#" ,location.locationName);
[self.locations addObject:#"New Entry"];
[tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:YES];
//[self.tableViewForScreen insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
} else {
indexPath = [self.tableView indexPathForSelectedRow];
location = [self.locations objectAtIndex:indexPath.row];
NSLog(#"location : %#" ,location.locationName);
[tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:YES];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// set the tapped item pointer equals to the locations mutable array variable wich essentially is the
// data source of the table view.
//Locations *tappedItem =[self.locations objectAtIndex:indexPath.row];
//access the location name and print to log.
}
Obviously it fails at the part of insert
[tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:YES];
Can someone give me a hand?
The error is the following.
reason: 'Invalid update: invalid number of rows in section 0. The
number of rows contained in an existing section after the update (154)
must be equal to the number of rows contained in that section before
the update (154), plus or minus the number of rows inserted or deleted
from that section (1 inserted, 0 deleted) and plus or minus the number
of rows moved into or out of that section (0 moved in, 0 moved out).'
Here's a post that I looked at.
UITableView action row
http://adcdownload.apple.com//videos/wwdc_2011__sd/session_125__uitableview_changes_tips_tricks.m4v
The problems is that you call insertRowsAtIndexPath: but you don't first update the data source to include the data for the new row.
You seem to do this correctly when selecting a row in the search table but not the main table.
First of all do not use
indexPath = [self.searchDisplayController.searchResultsTableView indexPathForSelectedRow];
you can take indexPath from method parameters
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
It seems that you have problems because you are calling insertRowsAtIndexPaths but you are not updating data source.
I have an iPad app (XCode 4.6, ARC, Storyboards, iOS 6.2.3). I have a UIPopover with a UITableView that has 21 rows in it. I can set the accessoryType in all of the rows randomly, but only in the first 12 rows does the accessoryType setting (checkmark) persist so it can be examined in another method and processed. I don't see any difference between the first 12 rows and the last 9 rows. The UITableView is scrollable, so to get to the rows after the 11th row, you have to scroll to the bottom
Here is the code to set the accessoryType:
#pragma mark didSelectRowAtIndexPath
- (void) tableView:(UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath {
// get the cell that was selected
UITableViewCell *theCell = [tableView cellForRowAtIndexPath:indexPath];
if(theCell.accessoryType != UITableViewCellAccessoryCheckmark)
theCell.accessoryType = UITableViewCellAccessoryCheckmark;
else
theCell.accessoryType = UITableViewCellAccessoryNone;
}
Here is the code where I check the accessoryType and process it:
-(void) moveServices { // (moves checked tableViewRows to services tableview)
NSMutableString *result = [NSMutableString string];
for (int i = 0; i < [servicesArray count]; i++) {
NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
[tvServices scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
UITableViewCell *cell = [tvServices cellForRowAtIndexPath:path];
if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
[result appendFormat:#"%#, ",cell.textLabel.text];
NSLog(#"\n\ni: %d\ncell.accessoryType: %d\ncell.textLabel: %#",i,cell.accessoryType, cell.textLabel);
}
}
if (result.length > 2) { // move to text box in main menu
storeServices =[result substringToIndex:[result length] - 2];
}
}
It looks like you're mixing the notion of "data source" and the contents of the cells in the table. Don't do that -- keep your data source (whether or not a particular row in the table should display a checkmark based on your program logic) separate from the settings of particular cells (whether or not a particular cell displays a checkmark). Then in cellForRowAtIndexPath, you build the cell to match your data source's current settings. The reason is that UITableView reuses cell instances based on which rows are visible on-screen (and it's just good MVC design).
In your case you should keep an NSMutableArray property in your class that records the settings for the entire table, and use the value from that array in cellForRowAtIndexPath to set up that particular cell. Then the other "business logic" methods in your controller use the array property to query your model state instead of the cell settings (which are part of the view and should be independent from the data model).
First the UITableViewCell is registered for reuse like this
UINib *cellLoader=[UINib nibWithNibName:#"GroupCell_iPhone" bundle:nil];
[self.tableView registerNib:cellLoader forCellReuseIdentifier:#"GroupCell"];
then in the cellForRowAtIndexPath delegate
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
the cell is dequeued
GroupCell_iPhone *cell=(GroupCell_iPhone*)[self.tableView dequeueReusableCellWithIdentifier:#"GroupCell"];
then a series of UILabel and other objects are created dynamically based upon a series of criteria and added to the cell like this
[cell.contentView addSubview:noActivityLabel];
The problem arises when the second and subsequent cells are dequeued and appear to have the dynamically added objects from the first dequeued cell. In the end, every cell will be different. Is the dequeued cell a "pointer" to one instance of the UITableViewCell? Why do these subsequently dequeued cells have the content from the first?
If this is the case what is the best approach to creating cells w/ dynamic/changing content? Should a new instance of the cell be created each time? Can the dequeued cell be "cloned"?
Clarification:
All cells in the table start w/ a base layout but then unique content is added to each cell based upon some data associated w/ that cell. Therefore, every cell in the table is unique, i.e., there are different subviews (UILable, UIImageView, etc.) in the cells.
I'm not sure if it is the best way but I have done the following:
Created a CustomCellClass with several subclasses: CustomCellType1, CustomCellType2 Custom CellType3 etc. Where the CustomCellSubclasses all use different parts of the Same Model to Display information.
Then, I can add give CustomCellClass a prePareDataForCell function that sets some basic attributes. If the CustomCellSubclass needs them it has them, if not things move along just the same (I think, this is the part I am not sure if it is good practice or not).
Then I can do some pseudocode like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCellClass *returnCell = (CustomCellClass*)[tableView cellForRowAtIndexPath:indexPath];
MyObject *obj = [self.fetchedResultsController objectAtIndexPath:indexPath];
if ([returnCell.reuseIdentifier isEqualToString:CustomCellType1) {
CustomCellType1 *cell = (CustomCellType1*)[tableView dequeueReusableCellWithIdentifier:CustomCellType1];
if (!cell) {
cell = [[CustomCellType1 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CustomCellType1];
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomCellType1" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
// This is a basic cell doesn't need anything special done with the object model
returnCell = cell;
} else if ([[returnCell reuseIdentifier] isEqualToString:CustomCellType2]) {
CustomCellType2 *cell = (CustomCellType2 *)[tableView dequeueReusableCellWithIdentifier:CustomCellType2];
if (!cell) {
cell = [[CustomCellType2 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CustomCellType2];
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomCellType2" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
// Do stuff with obj data only needed for this type of cell, perhaps modify the cells subviews
[cell preConfigureCustomCellType2:obj];
returnCell = cell;
} else {
CustomCellType3 *cell = (CustomCellType3*)[tableView dequeueReusableCellWithIdentifier:StandardMailCell];
if (!cell) {
cell = [[CustomCellType3 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:StandardMailCell];
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomCellType3" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
// Do stuff with obj data only needed for this type of cell perhaps modify the same subviews in a different way for this type of cell
[cell preConfigureCustomCellType3:mail];
returnCell = cell;
}
if (![tableView isDragging] && !tableView.decelerating) {
// I used this to only run an expensive operation after the the customer flicks and the table comes to a rest.
}
return returnCell;
}
Why do these subsequently dequeued cells have the content from the
first?
Instead of re-creating each time new cells, it his far better to re-use your already created cells who are not longer displayed. This is what actually do your tableView when dequeueing your cells: it give you some non-displayed cells if it can.
Say you need to display a tableview with 1000 cells, and the tableView is actually showing only 6 cells at a time. Instead of creating 1000 cells, it will re-use the cells stacks so that it doesn't need to re-create new one.
Is the dequeued cell a "pointer" to one instance of the
UITableViewCell? Why do these subsequently dequeued cells have the
content from the first?
Because it's the same pointer object, so it will have the same content as it was at the first display. You need then to update its data.
Should a new instance of the cell be created each time? Can the
dequeued cell be "cloned"?
You only need to create new cell, when
[self.tableView dequeueReusableCellWithIdentifier:#"GroupCell"]
return nothing. Here's a simple example:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// get your cell from the stack
GroupCell_iPhone *cell=(GroupCell_iPhone*)[self.tableView dequeueReusableCellWithIdentifier:#"GroupCell"];
// or create new one if nil. Note: you must give it the same identifier than the
// one you are using for the dequeue. Or tableview will not be able to return you anything from the dequeing.
if(!cell)
cell = [GroupCell_iPhone newWithIdentifier: #"GroupCell"] + autorelease];
** update**
// here you are sure to have a valid cell, so you can display content according to the indexPath
// display: use a controller who will update your cell change
// note, the first method clean what was inside your cell. You can keep the subview and reuse them the same way as tableview do.
[myDisplayClassCellController emptyCellSubview: cell];
// here you add all the display logic you want into your cell view. Note, you need indexPath
// because you need to display depending of the stack.
[myDisplayClassCellController decorateCellWithCustomViews: cell atIndexPath: indexPath];
** end update**
// then you can return back the cell to the tableView
return cell;
}
I have a UITableView made of static cells, and each cell contains a UILabel which is populated with field data when the screen loads. There are more cells than can fit on one screen, so the table view scrolls. The UILabels are hidden at design-time and I want to set them visible once all the text properties have been set. I've been using the subviews property of the tableView to iterate through the labels to setHidden:NO but this only affects the labels within cells that are currently in view. How can I iterate through all the UILabels regardless of which ones are in view or not?
Thanks
Jonathan
You can address this issue inside your tableView:cellForRowAtIndexPath: method.
Assuming that you have implemented your static cells the way Apple's guide suggests, your tableView:cellForRowAtIndexPath: should look like a sequence of if-then-else statements returning the cells provided through IBOutlet objects:
if (indexPath.row == 0) {
return cell1;
} else if (indexPath.row == 1) {
return cell2;
} // .. and so on
Modify this code as follows:
UITableViewCell *res = nil;
if (indexPath.row == 0) {
res = cell1;
} else if (indexPath.row == 1) {
res = cell2;
} // .. and so on
// Call your custom code that makes the label visible
if (allTextPropertiesHaveBeenLoaded) {
[res setMyLabelVisible];
}
return res;
When all text properties have been set, call reloadData to force all cells to go through your tableView:cellForRowAtIndexPath: and get reconfigured.
just call upon the cellForRowAtIndexPath: method
for(NSUInteger i = 0; i < numberOfCells; i++) {
UITableView* cell = [tableView cellForRowAtIndexPath: [NSIndexPath indexPathForRow:i inSection:0];
}
it's my first question in this awesome site. I've really searched a lot and yet find an answer to the next problem.
I have a table view with 9 cells. One of the cells has a switch button in it. When the switched button value changes, i want to delete 2 rows of the table view. I have written the next code:
- (IBAction)switchValueChanged:(id)sender {
NSLog(#"%#", ([switch isOn]) ? #"ON" : #"OFF");
NSArray *deleteIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:1 inSection:0],
[NSIndexPath indexPathForRow:2 inSection:0],
nil];
UITableView *tableView = (UITableView *)self.view;
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationFade];
// I Have to change the number of rows in the section here.
[tableView endUpdates];
}
When i run this, I'm getting a problem relating to the number of rows in the section - this number has been changed so i need to change it. I really can't find how i change it, but i know where i have to change it (see code).
How can i do it? how can i call the method numberOfRowsInSection:NSInteger and set also the rows?
Thank you very much :)
The number of rows in the table is in your
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
You need this to match the new number of rows in the table
---- EDIT
This means that you will need to keep track of the number of rows in your table.
Start with this set to 9 and delete 2 every time you change the switch
- (IBAction)switchValueChanged:(id)sender {
NSLog(#"%#", ([switch isOn]) ? #"ON" : #"OFF");
// We are going to delete 2 rows
numberOfRows -= 2;
...
and in your numberOfRowsInSection instead of returning 9 each time, return the new number of rows
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return numberOfRows;
}
You should delete the data from the datasource not the table cells and then just to refresh the table view. At least this is how i would do the design.
You have to also delete the data from the datasource of the table view. The data source is any container that holds the actual data which is being displayed on the table view. Just deleting the row only deletes the visual item of the row, when tableview is reloaded - it will want to draw 9-2 rows....but since number of rows is still returning 9, it will throw an inconsistency exception and tell you why. See the log carefully.