I'm adding a row to a UITableView section when the user switches the table to editing mode with this code:
- (void) setEditing:(BOOL) editing animated:(BOOL) animated {
[super setEditing:editing animated:animated];
if (editing) {
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:0 inSection:SECTION_SUPPLIERS]] withRowAnimation:UITableViewRowAnimationFade];
}
}
The object being to create a "Add new supplier ..." row. This works, however when the table wants to get the editing style so it can add a plus or minus icon, I have a problem.
- (UITableViewCellEditingStyle) tableView:(UITableView *) tableView editingStyleForRowAtIndexPath:(NSIndexPath *) indexPath {
return ([self.tableView isEditing] && indexPath.row == 0) ? UITableViewCellEditingStyleInsert : UITableViewCellEditingStyleDelete;
}
The sequence seems to go like this:
The table switches to editing. There is only one supplier row at this time so it queries for a editing style with row == 0.
The insert of the new row is done which then triggers another query for an editing style again with a row == 0.
So if I use this sequence and use the row number and tableView isEditing to decide on the editing style icon I end up with both the Add new and current supplier rows with plus icons on them.
What is the best way to insert a row and assign the appropriate editing style - plus for the add new row, and minus for any other row?
Have you tried putting your "Add new..." row at the bottom instead of at the top? Instead of checking for [indexPath row] == 0 you instead check for [indexPath row] == [items count], and your tableView:numberOfRowsInSection: implementation looks like this:
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
int numberOfRows = [items count];
if ([self isEditing]) {
numberOfRows++;
}
return numberOfRows;
}
This is how the example in iPhone Programming: The Big Nerd Ranch Guide works.
Related
I intended to add items to a UITableView when this method was called. New items are successfully added (but there's a problem, mentioned below), but I think its odd because I thought "begin updates" to "end updates" was supposed to handle this (Inserting a new row). The initial if condition I had put in never ran so the whole area never got executed. I only realized this recently and updated the condition to what it is now. Now the if block get executed and it crashes the app.
When it is commented out like it is now... New items are added but the newNameOfItem replaces any existing cell labels.
I would like this to add x(newNumberOfItems) new items preferably into a new section each time its called. How can I achieve this?
- (void)addNew:(NSString *)newNumberOfItems :(NSString *)newNameOfItem
{
if(!self.numberOfRows){
NSLog(#"Initially no of rows = %d", self.numberOfRows);
self.numberOfRows = [self.numberOfItems intValue];
NSLog(#"Then no of rows = %d", self.numberOfRows);
}
else
{
self.numberOfRows = self.numberOfRows + [newNumberOfItems intValue];
NSLog(#"New no rows = %d", self.numberOfRows);
}
NSLog(#"run = %d", self.run);
Begin updates if statement ...
/*if(self.secondRun){
NSLog(#"run = %d it it", self.run);
[self.tableView beginUpdates];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.numberOfRows-[newnumberOfItems intValue] inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath]withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView endUpdates];
}
*/
[self.tableView reloadData];
}
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell" forIndexPath:indexPath];
cell.textLabel.text = self.nameOfItem;
return cell;
}
...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.numberOfRows;
}
If you want to add new items to a new section every time you call -addNew…, you should create an NSMutableArray of sections where each member is an array of objects representing row data (in this case seems like you'd want NSStrings that represent the item's name. The structure would look something like:
mySections = #[ #[#"section 0 row 0 name", #"section 0 row 1 name", …], #[#"section 1 row 0 name", #"section 1 row 1 name", …], …]
Then in numberOfRowsInSection: return mySection[indexPath.section].count.
Each label has the same value because you're setting every single label for every cell that you dequeue to self.nameOfItem. It's doing exactly what you told it to do. If your intent is to set a different text for every section/row, you have to fetch that text from somewhere. If you created a mySections array as above, you could:
cell.textLabel.text = mySections[indexPath.section][indexPath.row] ;
A note about -addNew:…: simply adding new items to your mySections array will not cause the tableview to update. As you know above, [self.tableView reloadData] will do this for you. However, it will reload the entire table instead of just updating the rows that you added. To do this more efficiently (and with a nice animation), you instead use [self.tableView beginUpdates/endUpdates]. In the case above, where you're adding entire sections and not just rows, you should use insertSections:withRowAnimation:.
I have 2 sections on my UITableview controller. One of my section has a switch and my requirement is when I set the switch to ON, the second section should be set to hidden along with all its rows. I am calling the following method/code to hide the section when the status of the switch is changed:
- (void)setState
{
myTableViewCell *myCell = [[myTableViewCell alloc] init];
if ([myCell.mySwitch isOn])
{
NSIndexPath *indexPath = [NSIndexPath indexPathWithIndex:1];
[self.tableView cellForRowAtIndexPath:indexPath].hidden = YES;
}
}
I am getting the following exception for this code which I understand is perfectly true.
Name = NSInternalInconsistencyException;
Reason = "Invalid index path for use with UITableView. Index paths passed to table view must contain exactly two indices specifying the section and row. Please use the category on NSIndexPath in UITableView.h if possible.";
But how can I hide the complete section along with all its rows. If I try to get the index path using NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:1]; this would just hide the 1st row in the section.
If you wanna hide a whole section when switch is on, just reloadData when you click the switch, and return 0 in numberOfRowsInSection for that section and return totalNumberOfSections - howManySwitchesIsOn in numberOfSectionsInTableView, like this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if(howManySwitchesIsOn) {
return totalNumberOfSections - howManySwitchesIsOn;
}
return totalNumberOfSections;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(section should be hide) {
return 0;
}
return howManyRowsForThatSection;
}
Section deletion and insertion in a grouped UITableView are accomplished via:
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
Check out SectionHidingDemo, a demo app that illustrates section deletion and insertion in a grouped UITableView using those methods.
I have a UITableViewController that has 5 different sections, & each section contains an individual cell. In section 1 I have a UISegmentedControlthat offers two options by default segment 1 is picked so the rest of the sections (the other 4) correspond with segment 1. However, the problem that I am having is: Having the ability to switch to another UITableView that has other sections & cells that would correspond to segment 2, when of course segment 2 is selected in the UISegmentedControl
I am using storyboards for this project.
I know that the question of how to achieve this has been asked a thousand times, and I know how to do this if I was working with regular view controllers, I am new to working with UITableViewController and this is really giving me a hard time.
Things that I have tried:
No.1: I tried deleting the UITableViewController all together and replacing with a normal ViewController and sub a UITableView to the main view. The problem with that is that I was given an error by xcode because static cells require a UITableViewController to work, (unfortunately I am working with static cells).
No.2: I have tried to hide the cells with a conditional from the UISegmentedControl to only display the appropriate cells with the appropriate selection, but this did not work because I was not able to add my other sections & cells on top (as I would have normally done with regular UIViewController.
This is the code that I used to try method # 2 :
- (IBAction)segmentedControl:(id)sender {
UISegmentedControl *segmentedControl = (UISegmentedControl *) sender;
NSInteger selectedSegment = segmentedControl.selectedSegmentIndex;
if (selectedSegment == 0)
{
upick= 0;
self.lengthCell.hidden=NO;
self.widthCell.hidden=NO;
self.depthCell.hidden=NO;
self.flowCell.hidden=YES;
}
else if (selectedSegment == 1)
{
upick =1;
self.lengthCell.hidden=YES;
self.widthCell.hidden=YES;
self.depthCell.hidden=YES;
self.flowCell.hidden=NO;
}
}
No.3: Countless hours of research on the net but can't seem to find the exact results I am looking for, most show how to do this with a UIViewController and the steps are different for this particular case.
I would really appreciate some advice & overall direction to take from here because I am stuck. Thanks guys!
Have all the cells you need available in your table view controller.
In your table view's datasource methods (i.e. numberOfSections, numberOfRowsInSection, cellForRowAtIndexPath) you can set all the correct numbers and data for the two states.
When the segmented control changes, you can do two things. Either you simply reloadData, as was suggested before, but then you do not get any animations. Or you use insertRowsAtIndexPaths:withRowAnimation: and deleteRowsAtIndexPaths:withRowAnimation: to get the animation effect.
E.g. showing sections 1-2 in case 1 and sections 3-6 in case 2 (section 0 being the top section); each section has 2 rows
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return _control.selectedSegmentIndex == 0 ? 3 : 4;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return section == 0 ? 1 : 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// .. the usual boilerplate
if (_control.selectedSegmentIndex == 0) { /* configure cell */ }
else { /*do the same for the other case */ }
return cell;
}
-(void)segmentedControlDidChange:(UISegmentedControl*)sender {
NSIndexSet *indexes;
[_tableView beginUpdates];
if (sender.selectedSegmentIndex == 0) {
indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1,4)];
[_tableView deleteSections:indexes
withRowAnimation: UITableViewRowAnimationFade];
indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1,2)];
[_tableView insertSections:indexes
withRowAnimation: UITableViewRowAnimationFade];
[_tableView reloadSections:indexes
withRowAnimation: UITableViewRowAnimationNone];
}
else {
// do the reverse
}
[_tableView endUpdates];
}
If I am correct, you need to switch between 2 table views using UISegmentedControl. You can use UIViewController and add 2 UITableViews to it and keep your UISegmentedControl outside the table views. Then may be you can manage it in the following method:
- (IBAction)segmentedControl:(id)sender {
UISegmentedControl *segmentedControl = (UISegmentedControl *) sender;
NSInteger selectedSegment = segmentedControl.selectedSegmentIndex;
if (selectedSegment == 0)
{
//show Table1 and hide Table2
// Reload Table1
}
else if (selectedSegment == 1)
{
//show Table2 and hide Table1
// Reload Table2
}
}
Hope this helps.
I´m quite new to iOS development and I´m having a terrible time by trying something that should be easy; to add an extra row in a TableView everytime the user clicks on one of the existing rows. There is no real purpose on that action, I´m just wanting to understand the behaviour of TableView.
So I did the following:
I used a Split View-based template and changed the number of rows to 30 in the RootViewController.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 30;
}
The method tableView:didSelectRowAtIndexPath looks in the following manner:
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
/*
When a row is selected, set the detail view controller's detail item to the item associated with the selected row.
*/
NSMutableArray* paths = [[NSMutableArray alloc] init];
NSIndexPath *indice = [NSIndexPath indexPathForRow:30 inSection:0];
[paths addObject:indice];
detailViewController.detailItem = [NSString stringWithFormat:#"Second Story Element %d with all its information and bla bla bla", indexPath.row];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
When I execute the program and click on one of the elements, I receive the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (30) must be equal to the number of rows contained in that section before the update (30), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted).'
I did not change any other part of the code that the template provides.
I read quite extensively the documentation from Apple and the responses to the following questions:
Add a row dynamically in TableView of iphone
and
how to properly use insertRowsAtIndexPaths?
The second question seems to address the same problem, but I´m not capable to understand what is happening. What do they mean with dataSource? The response that I understand better says the following:
It's a two step process:
First update your data source so numberOfRowsInSection and cellForRowAtIndexPath will return the correct values for your post-insert data. You must do this before you insert or delete rows or you will see the "invalid number of rows" error that you're getting.
What does this update of the data source implies?
Sample code would be HIGHLY appreciated, because I´m totally frustrated.
By the way, all that I´m trying has nothing to do with entering the editing mode, has it?
You need to keep the count returned by tableView:numberOfRowsInSection: in sync!
So when you have 30 rows and then tell the tableview to insert a new row you need to make sure tableView:numberOfRowsInSection: will now return 31.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
{
return self.rowCount;
}
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.rowCount++;
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
In practice you would probably use an array to track your rows return [self.rows count]; etc
The answer is quite simple. When you want to modify a table view you need to perform two simple steps:
Deal with the model
Deal with table animation
You already perform the second step. But you have missed the first one. Usually when you deal with a table you pass it a data source. In other words some data to display within it.
A simple example is using a NSMutableArray (it's dynamic as the name suggests) that contains dummy data.
For example, create a property like the following in .h
#property (nonatomic, strong) NSMutableArray* myDataSource;
and in .m synthesize it as:
#synthesize myDataSource;
Now, you can alloc-init that array and populate it as the following (for example in viewDidLoad method of your controller).
self.myDataSource = [[NSMutableArray alloc] init];
[self.myDataSource addObject:#"First"];
[self.myDataSource addObject:#"Second"];
Then, instead of hardcoding the number of rows you will display (30 in your case), you can do the following:
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
return [self.myDataSource count];
}
Now, in you didSelectRowAtIndexPath delegate you can add a third element.
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.myDataSource addObject:#"Third"];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
It looks like one big problem is with tableView:numberOfRowsInSection:. You need to return the correct number of rows in that method.
To do that, it's usually best to maintain an NSArray or NSMutableArray of items for the table view so in that function, you can say: return [arrayOfValues count];. Keep the array as a property of your view controller class so that it's readily accessible in all methods.
The array can also be used in cellForRowAtIndexPath:. If you have an array of NSString, you can say cell.text = [arrayOfValues objectAtRow:indexPath.row];.
Then, when you want to add an item to the table view, you can just add it to the array and reload the table, e.g. [tableView reloadData];.
Try implementing this concept and let me know how it goes.
You can Also do that for dayanamic table cell
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [arrayStationStore count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIndentyfire;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndentyfire];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentyfire];
}
cell.textLabel.text = [arrayStationStore objectAtIndex:indexPath.row];
return cell;
}
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Check if current row is selected
BOOL isSelected = NO;
if([tblStationName cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryCheckmark)
{
isSelected = YES;
}
if(isSelected)
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
[arrayReplace removeObject:indexPath];
NSLog(#"array replace remove is %# ",arrayReplace);
}
else
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
[arrayReplace addObject:indexPath];
NSLog(#"array replace add is %# ",arrayReplace);
}
return indexPath;
}
I'm trying to make my table add a cell at the bottom of the table for adding cells, my table is on delete mode, so people can delete, but then then i need it to add a cell with insert that will insert a cell. i get errors saying my app isn't consistent with the nsarray. how could i make this work?
Step 1 override setEditing on your tableViewController to insert or delete the add row
- (void)setEditing:(BOOL)editing animated:(BOOL)animate
{
BOOL prevEditing = self.editing;
[super setEditing:editing animated:animate];
[tableView setEditing:editing animated:animate];
if (editing && !prevEditing) {
// started editing
[self.tableView insertRowsAtIndexPaths:....] withRowAnimation:UITableViewRowAnimationFade];
} else if (!editing && prevEditing) {
// stopped editing
[self.tableView deleteRowsAtIndexPaths:....] withRowAnimation:UITableViewRowAnimationFade];
}
}
Then ensure you are returning the right number of rows
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger numberOfRows = xxxxxx;
if (self.editing) {
numberOfRows++;
}
return numberOfRows;
}