Dealing with UITableView's indexPathsForSelectedRows - ios

I'm not sure how I can implement that my mock UITableView object answers correctly for indexPathsForSelectedRows.
In my App the user can (in editing state) select cells in a table view, which represents the files/folders of a given directory.
Once the user selects a folder item the previously selected files items should be deselected. My test (using OCHamcrest/OCMockito) looks like this.
- (void)test_tableViewwillSelectRowAtIndexPath_DeselectsPreviouslySelectedCells
{
// given
[given(self.mockTableView.editing) willReturnBool:YES];
// when
[self.sut tableView:self.mockTableView willSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:SectionIdFile]];
[self.sut tableView:self.mockTableView willSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:SectionIdFolder]];
// then
}
The problem is that I can verify that the file item is selected but I can't ask the the mockTableView for its selected rows. Could somebody tell me how to handle that? Do I have to record the tableView:selectRowAtIndexPath:animated:scrollPosition: calls myself and provide the correct answer when the tableView is asked for that information?

As the mockTableView can not record (like the real UITableView) the indexPath's of the selected cell, you have to make sure that the mock object returns the correct answer for that method. So in my case the test looks now like this.
- (void)test_tableViewwillSelectRowAtIndexPath_DeselectsPreviouslySelectedCellsForSectionIdFile
{
// given
[given(self.mockTableView.editing) willReturnBool:YES];
NSArray *selectedRows = #[[NSIndexPath indexPathForRow:0 inSection:SectionIdFile], [NSIndexPath indexPathForRow:1 inSection:SectionIdFile]];
[given([self.mockTableView indexPathsForSelectedRows]) willReturn:selectedRows];
// when
[self.sut tableView:self.sut.myTableView willSelectRowAtIndexPath:selectedRows[0]];
[self.sut tableView:self.sut.myTableView willSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:SectionIdFolder]];
// then
[verify(self.mockTableView) deselectRowAtIndexPath:selectedRows[0] animated:YES];
[verify(self.mockTableView) deselectRowAtIndexPath:selectedRows[1] animated:YES];
}

Related

Programmatically select UITableView cell not in current view

So it's easy to select a row that is currently in the UITableView. For example, to select the first row:
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
animated:YES
scrollPosition:UITableViewScrollPositionNone];
Say that I have an array that is the data source for the table and the array count is greater than the number of cells displayed in the tableview. How can I get the UITableView to scroll to an index from the array that is beyond what is currently being displayed in the tableview?
All I am trying to do is to replicate programmatically what a user would do with their index finger as they scroll down the table.
My specific table displays 9 rows. My array has 20+ items. As the UIViewController loads, it retrieves the row number that should be selected (from an integer stored in NSUserDefaults). But I find that it will only scroll to the correct array position if that integer value is between 0 and 8. If it is 9 or greater, nothing happens, and I can't figure out how to make it respond to this. I've looked at all the UITableViewDelegate methods and none seems to address this.
What I've been doing to scroll and select a specific row is this (example arbitrarily selecting row 11):
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:11 inSection:0]
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:11 inSection:0]
animated:YES
scrollPosition:UITableViewScrollPositionTop];
Can anyone help me? I assume it isn't difficult, but I'm stuck.
Thanks!
Your cells that are off the screen ain't selecting because you are using reusable cells. The cells from the visible screen will be used later, it isn't that all 100 cells are cached and each cell is responsible for each row. What it means is that they could or couldn't have something in it already. For example, lets say you have cell for row 1. When it comes off the screen, in the next few cells it will be reused as cell 15 or something, and if it had selected properties, it will still have it. It is like a new job and you get a desk from the developer before you - you could have desk with his trash, but it could also be clean.
I wouldn't select them as you select them by method, but in if statement in your cellForRowAtIndexPath. Something along the lines (added comments):
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
// When using method with forIndexPath you don't have to check for nil because you will always get cell
MyTableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
MyObj *obj = [self.myArray objectAtIndex:indexPath.row];
cell.location.text = obj.location.location_description;
// other formatting, text display, image loading, etc.
if ([self.selectedObjects containsObject:obj]) {
// do some selecting stuff
} else {
// but don't forget to unselect because you can get already selected cell
}
return cell;
}
Edit: To select invisible cell, first scroll to it, then select:
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
Try using UITableViewScrollPositionBottom instead of UITableViewScrollPositionNone
That is use this code
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:10 inSection:0]
animated:YES
scrollPosition:UITableViewScrollPositionBottom];
I figured this out. My code was running in viewDidLoad which is too early. I needed to move it to viewDidAppear. At least I know that I am not losing my mind.

Row deletion does not refresh table view in ios app

I have spent hours searching for the solution with out any luck. I am trying to delete a row (also deselect same row) programmatically. After row deletion call below, UITableViewDelgate methods get called expectedly and data source is updated but UITableView is not refreshed. deselectRowAtIndexPath call also does not work. I tried all kinds of scenarios as shown by commented lines.
Here is my code:
checkoutPerson is called as a result of observer listening for NSNotificationCenter messages.
- (void) checkoutPerson: (NSNumber*) personId {
Person *person = [_people objectForKey:personId];
if( person )
{
// Remove person from data source
int rowIndex = person.rowIndex;
S2Log(#"Deleting row number=%d", rowIndex);
[_allKeys removeObjectAtIndex:rowIndex];
[_people removeObjectForKey: personId];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:0];
//[[self tableView] beginUpdates];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
S2Log(#"Deleting indexPath row=%d", [indexPath row]);
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
//[[self tableView] endUpdates];
S2Log(#"Reloading data");
//[[self tableView] reloadData];
//[self performSelector:#selector(refreshView) withObject:nil afterDelay:1.5];
//[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}
}
I will appreciate for help.
Thanks
-Virendra
I believe deleted cell is not being recycled. If I delete row in the middle, last row is always erased (since there is one less item) but the deleted row remains.
Use the above code between two function for table view
[tableView beginUpdates];
// the deletion code from data source and UITableView
[tableView endUpdates];
By calling this functions you are telling UITableView that you are about to make updates for deleting your cell.
Edit
The other problem I see with your code is you first delete the data from the data source.
Now you are asking for the UITableViewCell (which actually reloads the UITableView)
and then you are deleting the row from UITableView
I guess you should fetch the UITableViewCell before deleting values from your data source.
I found the problem. It has nothing to do with the code I posted above. It is syncing problem between visual display and the contents of data source. I have an embedded UITableView as part of a composite view. In composite view's controller, I was wiring up UITableView's delegate and data source to an instance of UITableViewController. Instead of this, I should have set UITableViewController's tableView property to the embedded UITableView. It seems that UITableView has to be contained within UITableViewController in order to correctly sync up table view visual display to the contents of data source. This also fixes row deselection and scrolling. I also needed to delay reloadData call in which case deleteRowsAtIndexPaths:withRowAnimation is not required. All you need is to modify the contents of your data source and call reloadData with a delay of 1.5 Seconds.
Thanks to all for great help.

UICollectionView, scrolling to an Item without knowing its NSIndexPath

I have a UIcollectionview that shows an array of objects called Items.
At one point in the lifecycle of my app I do need to move the collectionview ( or scroll it ) to a specific Item that I receive Via NSnotification. I do not know the NSIndexPath of that Item but its definitely available in the collectionview.
I tried
NSIndexPath *myIndexPath = [NSIndexPath indexPathForItem:myItem inSection:1];
[self.collectionView scrollToItemAtIndexPath:myIndexPath
atScrollPosition:UICollectionViewScrollPositionCenteredVertically
animated:NO];
But this gives back an arbitrary number and not relevant.
How can I do that?
I dont know details of code so i am generalising my answer.
You can have a unique property for myItem say ID.
If you are maintaining an array of items say : myItemArray, and populating collection view in same order then following can work:
NSArray* myItemArray; //array used to populate collectionView
NSIndexPath *myIndexPath; //this is what we need to know
int index = 0;
//myItem is object of item type & notifiedItem is notified item
for (Item* myItem in myItemArray) {
if (myItem.ID == notifiedItem.ID) {
myIndexPath = [NSIndexPath indexPathForItem:index inSection:1];//you got the index path right
break;
}
index++;
}
//use the index path

Use an IBAction to select and press a table cell row

I am trying desperately to make this IBAction just effectively press a cell at a selected row. I have managed to get it to select a row, but I can't work out how to effectively click on this cell! I am only making my first app but I have managed to figure most things out by myself, but just can't seem to find out how to do this, i'm hoping it is a simple solution (or there is a much better way to do it than I have).
Here is the code for my IBAction anyway:
- (IBAction)myButton:(id)sender {
// Specify which cell I wan't to select
NSIndexPath *myIP = [NSIndexPath indexPathForRow:0 inSection:0];
// Select it
[self.tableView selectRowAtIndexPath:myIP animated:NO scrollPosition:UITableViewScrollPositionTop];
// Click this cell??
}
Thanks in advance for any help
Just tell the delegate that you've selected it
[self tableView:self.tableView didSelectRowAtIndexPath:myIP];
Assuming that self is your VC that controls the table.
The below stackoverflow answer looks like exactly what you need...
Automatically cell selected UITableView
Not a clean way to achieve but as per my understanding, You can add custom UIButton (transparent) on each cell such a way it covers almost complete cell in cellForRowAtIndexPath:, disable row selection. On these button you can use addTarget:action:
If your view controller that has the UITableView in it, is not subclassing UITableViewController you need to create an IBOutlet of the UITableView call it myTableView or whatever you'd like, then in your IBAction you can reference it like this:
- (IBAction)myButton:(id)sender {
// Specify which cell I wan't to select
NSIndexPath *myIP = [NSIndexPath indexPathForRow:0 inSection:0];
// Select it
[self.myTableView selectRowAtIndexPath:myIP animated:NO scrollPosition:UITableViewScrollPositionTop];
// (Per Dmitry's answer)
[self.myTableView didSelectRowAtIndexPath:myIP];
}

Keeping selection upon updating UITableView via reloadData

I am trying to implement a view similar to that of Apple's calendar application's setting the start time and end time view. I have the view looking great, but I am running into two problems. First, I want to have the first row selected automatically. I sort of have this working using:
NSIndexPath *indexPath=[NSIndexPath indexPathForRow:0 inSection:0];
[dateTableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];
However, an animation shows, visually selecting the rown when the view is loaded, I want it to already be selected when the view loads.
The second, and much more important, problem is that when I reload the data after the picker has updated I lose the selection, and my task simply doesn't work.
Now I know this is the default action of reloadData, I am looking for an alternative method to accomplish my goal, or perhaps how I can augment reloadData to not deselect the current selection.
My task is included below:
-(IBAction)dateChanged
{
NSIndexPath *index = self.dateTableView.indexPathForSelectedRow;
if(index == 0)
{
if (self.date2 == plusOne ) {
self.date = [datePicker date];
plusOne = [self.date dateByAddingTimeInterval:60*60];
self.date2 = plusOne;
}
else
{
self.date = [datePicker date];
}
}
else{
self.date2 = [datePicker date];
}
[dateTableView reloadData];
}
Note: plusOne is a variable that initially indicates an hour from the current time.
Thanks in advance!
For the first problem, set animated:NO on the method call. You are currently setting it to YES.
[dateTableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionBottom];
For the second problem, it's not really clear what you are trying to select after reloading the data, but I see a problem with this line:
if(index == 0)
You are asking if the pointer to the object is 0. I think what you want is either to check that index == nil or that index.section == 0 && index.row == 0 or something like that.
Anyway, if you call reloadData on the UITableView, you're going to lose the selection. At that point, you need to select a new row. If there is an item in your data model that you want to select, you need to figure out where it is and select it based on where it will be in the table (You should know this because you are providing that information in the UITableViewDataSource delegate methods.). Alternatively, if you want to select the NSIndexPath you saved in the first line of dateChanged, just select it after reloading the data.
For the first problem, write your code in didSelectRowAtIndexPath: in a method. You can then call this method from didSelectRowAtIndexPath: You should pass indexPath as argument to your function like,
-(void)doActionsInDidSelectRow:(NSIndexPath*)indexPath{
// write code.
}
In viewDidLoad call this method as
-(void)viewDidLoad{
NSIndexPath *indexPath=[NSIndexPath indexPathForRow:0 inSection:0];
[dateTableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];
[self doActionsInDidSelectRow:indexPath];
}
For the second problem my suggestion is, each time when you selecting a cell store that cell's text in a NSString. When you reload data, inside cellForRowAtIndexPath: just compare cell's text with the string content you stored previously. If they equal just make selection using
[dateTableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];
method. Hope this will solve your issues.

Resources