How to select only two rows in tableview in iOS? - ios

I am trying to allow just two rows to be selected at a time in a table view, but it seems that I can't get a good result!
The idea is that you select one row and this sends an object to core data, then when you select a second row that sends a different thing to core data. But if you select more than two rows, it displays an alert view that says you can only select two rows.
I have tried this:
NSArray *indexPathArray = [[NSArray alloc]init];
indexPathArray = [self.mainTableView indexPathsForSelectedRows];
if ( indexPathArray.count == 1) {
NSLog(#"%#",#"we have 1 cell selected");
}
if ( indexPathArray.count == 2) {
NSLog(#"%#",#"We have 2 cells selected!");
}
if (indexPathArray.count > 2) {
NSLog(#"%#",#"ERROR ERROR!!!");
}
and I have tried many other google and stack overflow suggestions but didn't get to an end!
So how can I achieve this?
This is the method in which I have the code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath(NSIndexPath*)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[self setSelection:indexPath];
_profile = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSInteger rowNumber = 0;
for (NSInteger i = 0; i < indexPath.section; i++) {
rowNumber += [self tableView:tableView numberOfRowsInSection:i];
}
rowNumber += indexPath.row;
NSLog(#"%ld",(long)rowNumber);
NSArray *indexPathArray = [[NSArray alloc]init];
indexPathArray = [self.mainTableView indexPathsForSelectedRows];
if ( indexPathArray.count == 1) {
NSLog(#"%#",#"we have 1 cell selected");
}
if ( indexPathArray.count == 2) {
NSLog(#"%#",#"We have 2 cells selected!");
}
if (indexPathArray.count > 2) {
NSLog(#"%#",#"ERROR ERROR!!!");
}
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade]; }

Delete this line:
[tableView deselectRowAtIndexPath:indexPath animated:YES];
from your didSelectRowAtIndexPath: method.

Comment/Delete out the following lines in your code in method
- (void)tableView:(UITableView )tableView didSelectRowAtIndexPath(NSIndexPath)indexPath
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
And check. Your logical part should then work fine.
Explanation: TableView method indexPathsForSelectedRows will return the number of rows selected. And your are deselecting the selected row using this line of code:
[tableView deselectRowAtIndexPath:indexPath animated:YES];
Commenting/deleting only this single line of code will give you indexPathArray count as 1, because you still reload the row at the end of your method using:
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
The above piece of code also resets the row to be unselected as you are reloading it. So you need to comment or delete it out too.

Related

Call something based on UITableView rows selected, in an IBAction

I just saw a post about selecting multiple UITableView rows and based on which row/rows are selected, something will happen when you press a button linked to an IBAction.
It's an interesting question and I've played around with it, but now I'm stuck a bit.
As you can see I have a NSMutableArray called selectedIndexes, and the selected row/rows are shown in the console using a NSLog. This works fine:
- (IBAction)doSomethingWithSelectedRows:(id)sender {
for (NSNumber *data in self.selectedIndexes) {
NSLog(#"Row %ld is selected.", (long)[data integerValue] +1);
}
if (WHAT SHOULD BE PUT HERE?) {
}
//Do whatever you want here.
}
The question is, what should be put in that if statement above if you want this to happen:
if (row 2 and row 1 are selected {
//Run this code.
}
UPDATE:
Here's more code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if ([selectedCell accessoryType] == UITableViewCellAccessoryNone) {
[selectedCell setAccessoryType:UITableViewCellAccessoryCheckmark];
[_selectedIndexes addObject:[NSNumber numberWithInteger:indexPath.row]];
} else {
[selectedCell setAccessoryType:UITableViewCellAccessoryNone];
[_selectedIndexes removeObject:[NSNumber numberWithInteger:indexPath.row]];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (IBAction)doSomethingWithRowsSelected:(id)sender {
for (NSNumber *data in self.selectedIndexes) {
NSLog(#"Row %ld is selected.", (long)[data integerValue] +1);
}
}
While you can use an array, as you do, an NSMutableIndexSet is a much better data structure for this task. It allows you to check whether a given index is in the set without having to iterate through all of the objects.
#property (strong,nonatomic) NSMutableIndexSet *selectedRows;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = indexPath.row;
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if ([self.selectedRows containsIndex:row]) {
[self.selectedRows removeIndex:row];
[selectedCell setAccessoryType:UITableViewCellAccessoryNone];
} else {
[self.selectedRows addIndex:indexPath.row];
[selectedCell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Now you can easily check if any two rows are selected:
- (IBAction)doSomethingWithRowsSelected:(id)sender {
if ([self.selectedRows containsIndex:1] && [self.selectedRows containsIndex:2]) {
// Do whatever
}
}
Also, don't forget to set the accessory type in cellForRowAtIndexPath based on [self.selectedRows containsIndex:indexPath.row] otherwise your checkmarks will be wrong when you scroll.
Here is what I will do,
1st, get the array of selected rows by using indexPathsForSelectedRows,
2nd, iterate the array to check if if indexPath.row is 1 or 2.
3rd, if your if statement's condition is true, then do ...

UITableview Insert/Delete

I am having one number of section base tableview in that plus button which will add new item in tableview.
While i try add or delete the section from tableview and reload that tableview it will blink old record many times and then add or delete the section. Here is the code.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SymbolTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([selectIndexPath containsObject:[NSNumber numberWithInt:(int)indexPath.section]])
{
[selectSymbol removeObject:cell.lblsymbol.text];
[selectIndexPath removeObject:[NSNumber numberWithInt:(int)indexPath.section]];
}
else
{
NSString *strr = cell.lblsymbol.text;
[selectSymbol addObject:strr];
[selectIndexPath addObject:[NSNumber numberWithInt:(int)indexPath.section]];
}
[tblDetail reloadData];
}
what is the issue?
try following code it may helps you
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath
{
SymbolTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([selectIndexPath containsObject:[NSNumber numberWithInt:(int)indexPath.section]])
{
[tblDetail beginUpdates];
[selectSymbol removeObject:cell.lblsymbol.text];
[selectIndexPath removeObject:[NSNumber numberWithInt:(int)indexPath.section]];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}
else
{
NSString *strr = cell.lblsymbol.text;
[tblDetail beginUpdates];
[selectSymbol addObject:strr];
[selectIndexPath addObject:[NSNumber numberWithInt:(int)indexPath.section]];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}
}
You can reload cell which you removed/added
You will not reload all table.
Try the following code please
[self.tableView deleteRowsAtIndexPaths:#[ indexPath ] withRowAnimation:UITableViewRowAnimationFade];
or
[self.tableView insertRowsAtIndexPaths:#[ indexPath ] withRowAnimation:UITableViewRowAnimationFade];
for inserting row in tableview
- (void)insertNewObject:(id)sender
{
if (!arrayForTableContent) {
arrayForTableContent = [[NSMutableArray alloc] init];
}
NSInteger count = [arrayForTableContent count];
NSString *strTobeAdded = [NSString stringWithFormat:#"%d",count+1];
[arrayForTableContent insertObject:strTobeAdded atIndex:count];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:count inSection:0];
[self.tableViewForScreen insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
I'm not writing all the code nor test it in the debugger, but I will give you an advice of how to proper implement this. Let's say you have a datasource, like this:
NSMutableArray *data = #[#"First", #"Second", #"Third"];
Datasource: In numberOfRowsInSection, you return data.count; in cellForRowAtIndexPath, you will configure the cell with the respective index from the array;
Delegate: In didSelectRowAtIndexPath, remove the object from the data array, say you will need something like this: [data removeObjectAtIndex(indexPath.row)]; then call reloadData on the table view. The table view will smoothly return now just two items.

Attempt to create two animations for cell

I would like to create cell by expand and collapse. To do this i animate the cell by reloading. When i expand it works fine. But when i collapse it crashes and the reason is Attempt to create two animations for cell. I know Ive given same array paths and it wont reload two cell at the time. Is there a way to fix this.
I am new to coding, so i would be happy to get a simple solution.
Code:
The reason am using lastSelIPath is when first cell is expanded and touch the second cell the first cell would collapse and the second cell would expand.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath* lastSelIPath = [ NSIndexPath indexPathForRow:selectedIndex_ inSection:0 ];
[ tableView deselectRowAtIndexPath:indexPath animated:YES ];
if (selectedIndex_ == indexPath.row)
{
selectedIndex_ = -1;
}
else
{
selectedIndex_ = indexPath.row;
}
NSIndexPath* ipath = [ NSIndexPath indexPathForRow:indexPath.row inSection:0 ];
NSArray* indexPathArr = [ NSArray arrayWithObjects:lastSelIPath, ipath, nil ];
[ tableView beginUpdates ];
[ tableView reloadRowsAtIndexPaths:indexPathArr withRowAnimation:UITableViewRowAnimationAutomatic ];
[ tableView endUpdates ];
}
So what i did now I set a condition and reload the one i wanted. Is it a correct method to follow please help.
Modified code:
if ( lastSelIPath.row == ipath.row )
{
[tableView reloadRowsAtIndexPaths:#[ipath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
else
{
[tableView reloadRowsAtIndexPaths:#[lastSelIPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadRowsAtIndexPaths:#[ipath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
Crash is right, you are trying to attempt two animation. Your indexPathArr contains two objects. Do not create this array and try this:
[tableView reloadRowsAtIndexPaths:#[lastSelIPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadRowsAtIndexPaths:#[ipath] withRowAnimation:UITableViewRowAnimationAutomatic];
Let me know, if it helps
[tableView reloadRowsAtIndexPaths:#[newIndexPath, oldIndexPath] withRowAnimation:UITableViewRowAnimationNone];
If newIndexPath is same as oldIndexPath, it will crash in iOS8, but this is fixed in iOS9 and later.
You can use third party library for expanding cell.
https://github.com/bennyguitar/CollapseClick
& implement there delegate method as
-(int)numberOfCellsForCollapseClick
{
}
-(NSString *)titleForCollapseClickAtIndex:(int)index {
}
-(UIView *)viewForCollapseClickContentViewAtIndex:(int)index {
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (selectedTag==1) {
selectedIndex=indexPath.row;
[self performSelector:#selector(Write Your Action) withObject:nil];
}
NSMutableArray *modifiedRows = [NSMutableArray array];
if ([indexPath isEqual:self.expandedIndexPath]) {
[modifiedRows addObject:self.expandedIndexPath];
self.expandedIndexPath = nil;
}
else {
if (self.expandedIndexPath)
[modifiedRows addObject:self.expandedIndexPath];
self.expandedIndexPath = indexPath;
[modifiedRows addObject:indexPath];
}
// This will animate updating the row sizes
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];
// Preserve the deselection animation (if desired)
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Using Selected Tag write the condition of your cell expand action. may be it helps.

Why am I getting an assert Error When trying to Delete a section in a tableview ios

I have a tableView with some sections, which all have a footer, and then I have a tableViewFooter on the Tableview itself.
If I scroll down to the bottom of my tableview and delete the last item(therefore deleting the section altogether) in any sections above the last section (second last and up) it gives me this error
2014-02-21 13:19:55.066 xxxx[5436:60b] *** Assertion failure in -[UIViewAnimation initWithView:indexPath:endRect:endAlpha:startFraction:endFraction:curve:animateFromCurrentPosition:shouldDeleteAfterAnimation:editing:], /SourceCache/UIKit/UIKit-2903.23/UITableViewSupport.m:2661
Uncaught exception: Cell animation stop fraction must be greater than start fraction
at endUpdates
this is my code
[self.tableView beginUpdates];
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
if(indexPath != nil){
TableSection * sec = [self.sections objectAtIndex:indexPath.section];
NSMutableDictionary *dic =[sec.items objectAtIndex:indexPath.row];
Product* product = [dic valueForKey:PRODUCT];
//removing the item in the section
[sec.items removeObject:dic];
//deleting item from products
NSMutableArray *temp = [NSMutableArray array];
for (Product *p in self.dataCon.listPersister.products) {
if ([p.product.objId isEqualToString: product.product.objId]) {
[temp addObject:p];
}
}
for (Product *p in temp) {
[self.dataCon.listPersister.products removeObject:p];
}
//if it was the last object in section, delete the section else just delete the single row
if(sec.items.count == 0)
{
[self.sections removeObject:sec];
[self.footers removeObjectAtIndex:indexPath.section];
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
} else
{
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
USFooterView *footer = [self.footers objectAtIndex:indexPath.section];
footer.totalLabel.text = [self.dataCon.listPersister getTotalForShopId:sec.title];
self.footerView.totalLabel.text = [self.dataCon.listPersister getTotalForAllProducts];
}
}
[self.tableView endUpdates];
I had the same code earlier, just without my tableView and table sections having footers, where it worked, so I think they might be the problem, but I'm not entirely sure that's the reason it's acting up.
I have seen this post
UITableView tableFooterView may cause crash?
And the post that it links to, but that didn't help me.
Any help is appreciated :)
In the else statement you delete row from table view:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
But not from data source. Delete row from array which you use as data source and it should works.
I found a "fix", but I'm avoiding the use of sectionFooter, because that seems to be bugged.
I created an extra cell at the end of each section, with the same setup I had for my footer View before, and made that last cell not editable with
-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
TableSection * sec = [self.sections objectAtIndex:indexPath.section];
if (sec.items.count != indexPath.row) {
return YES;
} else
return NO;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [sec.items count] +1 ;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"normalcell";
static NSString *CellIdentifier1 = #"footercell";
TableSection * sec = [self.sections objectAtIndex:indexPath.section];
if (indexPath.row != sec.items.count) {
//use normal type of cell
return cell;
} else{
//use footer type of cell
return cell;
}
}
So the last cell Imitates a "footer", but it's not stuck to the bottom of the frame, but I'll have to live with that. It's better than crashes.
Try using UITableViewRowAnimationLeft or UITableViewRowAnimationRight as the delete row animation(deleteRowsAtIndexPaths:withRowAnimation:).
It crashed for me when using UITableViewRowAnimationAutomatic, but not with the other two. I have not tried all of them but it seems to be a bug with the animation code for some of the options.

UITableViewCell check/uncheck within one section only

I'm trying to setup my table so that the user can select one item per section.
For example:
- Section 0
- Row 0 √
- Row 1
- Section 1
- Row 0
- Row 1 √
- Row 2
So in the above example if the user selects section 0, row 1 then row 0 in the same section should be unchecked and the selected row gets a checkmark.
Same goes for section 1 where any selected row should get a checkmark and then I want to remove the checkmark from the previously selected row in the same section.
- Section 0
- Row 0
- Row 1 √
- Section 1
- Row 0
- Row 1
- Row 2 √
Please keep in mind that I won't have a predefined number of sections or rows, so the code should work for this scenario. Here's what I currently have, hopefully that might get me started on the right path.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
MyObject *myObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if ([myObject.optionSelected boolValue] == NO) {
[myObject setOptionSelected:[NSNumber numberWithBool:YES]];
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
} else {
[myObject setOptionSelected:[NSNumber numberWithBool:NO]];
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
if ([tableView numberOfRowsInSection:indexPath.section] > 1) {
NSMutableArray *cells = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < [tableView numberOfRowsInSection:indexPath.section]; ++i) {
[cells addObject:[tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:indexPath.section]]];
}
for (UITableViewCell *deselectCell in cells) {
if ([self.tableView indexPathForCell:deselectCell] != indexPath && deselectCell != cell) {
MyObject *tempObject = [self.fetchedResultsController objectAtIndexPath:[self.tableView indexPathForCell:deselectCell]];
[tempObject setOptionSelected:[NSNumber numberWithBool:NO]];
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
}
}
}
As you can see I am also setting the object's selected state, I'd like this to remain intact :)
Thanks for any feedback and help in advance!
Your tableview should declare a mutable array to hold the currently selected paths:
NSMutableArray *selectedCellPaths = [[NSMutableArray alloc] init];
Then your tableView:didSelectRowAtIndexPath: should look like this
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
MyObject *myObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if ([myObject.optionSelected boolValue] == NO) {
[myObject setOptionSelected:[NSNumber numberWithBool:YES]];
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
[selectedCellPaths addObject:indexPath];
} else {
[myObject setOptionSelected:[NSNumber numberWithBool:NO]];
[cell setAccessoryType:UITableViewCellAccessoryNone];
if ([selectedCellPaths containsObject:indexPath]) {
[selectedCellPaths removeObject:indexPath];
}
}
// Now we're going to remove all but the cell path that is actually selected.
NSMutableArray *cellsToRemove = [[NSMutableArray alloc] init];
for (NSIndexPath *selectedCellIndexPath in selectedCellPaths) {
if ([selectedCellIndexPath compare:indexPath] != NSOrderedSame && selectedCellIndexPath.section == indexPath.section) {
// deselect cell at selectedCellPath
[cellsToRemove addObject:selectedCellIndexPath];
}
}
[selectedCellPaths removeObjectsInArray:cellsToRemove];
}
Note I have just put in a comment where you would want to deselect the actual cell at the cell path that is not selected. You need to fill that code in yourself.
I haven't tested this code, just modified what you had in TextMate but it should work barring minor changes.

Resources