Keeping selection upon updating UITableView via reloadData - ios

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.

Related

Objective-C deselectRowAtIndexPath is kinda working

In my cellForRowAtIndexPath and I pre selecting a row:
NSArray *tempArray = [[communityPrefs objectForKey:#"Community"] componentsSeparatedByString:#","];
NSMutableArray *tempArrayMutable = [[NSMutableArray alloc] initWithArray:tempArray];
if ([tempArrayMutable containsObject:cell.textLabel.text])
{
[selectedAreaTable selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
This part works great. However the selected item is at the bottom of the page and the user will not see it, but when they scroll down to that cell it is selected.
Now I am trying to write a piece of code the will deselect all selected cells like so:
for(NSIndexPath *index in selectedAreaTable.indexPathsForSelectedRows)
{
if(index.row != 0)
{
[selectedAreaTable deselectRowAtIndexPath:index animated:NO];
}
}
but after I run this code the cell at the bottom is still selected. So my question is, why is this cell not being deselected? Is it because its not there until you scroll to it? How can I fix this problem?
Is it because its not there until you scroll to it?
Yes. I believe that you should change "selected" state using cell objects only for visible rows. All other rows should retrieve "selected" status in cellForRowAtIndexPath method
user979331,
you dont have to remove the selection in seperate method rather you can handle that too in cellForRowAtIndexPath as well.
You can declare the array NSMutableArray *tempArrayMutable as a property,
when you want to deselect all cell lets assume a method named clear all selection
-(void)clearAllSelection {
[self.tempArrayMutable removeAllObjects];
self.tableView reloadData];
}
Finally in cellForRowAtIndexPath change as
if ([self.tempArrayMutable containsObject:cell.textLabel.text])
{
[selectedAreaTable selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
else{
[selectedAreaTable deselectRowAtIndexPath:indexPath animated:NO];
}

Using a button to scroll a uicollectionview - how do i check bounds?

I don't know how to figure out whether I get to the last IndexPath and when to "rewind" and scroll to the first IndexPath
This is some setup:
- (void)viewWillLayoutSubviews;
{
[super viewWillLayoutSubviews];
UICollectionViewFlowLayout *flowLayout = (id)self.collectionView.collectionViewLayout;
if (UIDeviceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
flowLayout.itemSize = CGSizeMake(1024.0f, 768.0f);
} else {
flowLayout.itemSize = CGSizeMake(1024.0f, 768.0f);
}
[flowLayout invalidateLayout]; //force the elements to get laid out again with the new size
visibleItems = [self.collectionView indexPathsForVisibleItems];
self.currentIndexPath = [visibleItems firstObject];
[self.collectionView.collectionViewLayout invalidateLayout];
}
This is my button code:
- (IBAction)addToUploadQueque:(id)sender {
NSLog(#"current: %#",self.currentIndexPath);
NSInteger section = [self numberOfSectionsInCollectionView:self.collectionView] - 1;
NSInteger item = [self collectionView:self.collectionView numberOfItemsInSection:section]-1;
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:item inSection:section];
NSIndexPath *firstIndexpath =[NSIndexPath indexPathForItem:0 inSection:0];
NSLog(#"current: %#",lastIndexPath);
if (self.currentIndexPath <= lastIndexPath) {
NSInteger newLast = [self.currentIndexPath indexAtPosition:self.currentIndexPath.length-1]+1;
self.currentIndexPath = [[self.currentIndexPath indexPathByRemovingLastIndex] indexPathByAddingIndex:newLast];
[self.collectionView scrollToItemAtIndexPath:self.currentIndexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
animated:YES];
}else{
self.currentIndexPath = [visibleItems firstObject];
[self.collectionView scrollToItemAtIndexPath:self.currentIndexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
animated:YES];
}
}
I'm making a button that iterates through each cell in the collection view and when it gets to the end (instead of going out of bounds) scroll back to the first cell.
Can anyone tell me what i'm doing wrong?
Judging from your comments, it doesn't sound like you want to just enumerate the visible cells in one pass, but rather want to enumerate through them manually. And it sounds like you're having an issue getting the next NSIndexPath. The problem with your code snippet is that you're incrementing the row/item (depending upon whether you're dealing with UITableView or UICollectionView), but not considering whether you've reached the end of a section, much less the end of the data source, before you try to use your incremented data source.
You could do something like:
NSInteger item = self.currentIndexPath.item;
NSInteger section = self.currentIndexPath.section;
item++; // go to next item
if (item >= [self.collectionView numberOfItemsInSection:section]) { // if you reached end of section ...
item = 0; // ... go to the start of the next section
section++;
if (section >= [self.collectionView numberOfSections]) { // if you reached the end of the data source ...
// all done, so set section to zero to go back to beginning, e.g. // ... then you're done
section = 0;
}
}
self.currentIndexPath = [NSIndexPath indexPathForItem:item inSection:section]; // otherwise, this is your new NSIndexPath
BTW, if we're going to focus on your code snippet, the another issue is that you're using the <= operator to compare two index paths. You cannot do that. You have to use the compare method of NSIndexPath.
But that if statement strikes me as unnecessary, as there is no index path in the data source after the last index path. If you're incrementing logic (above) correctly detects the end of the data source, then this if statement is unnecessary.
There are many, many issues in this code. But I'm wondering whether, rather than going through all of those details, whether a simpler approach is possible. If you just want to perform uploads for all of the visible rows, perhaps you could do something radically simpler, such as:
for (NSIndexPath *indexPath in self.collectionView.indexPathsForVisibleItems) {
// if the cell has some visual indication to reflect upload has been initiated,
// do that here
// do your asynchronous upload here, where the completion block dispatches
// updates to the cell/collectionView (to reflect that the individual upload
// is done)
}
Note, your code is scrolling to the cell (presumably when the upload is done). I might try to dissuade you from that approach, but instead just update the cell (e.g. set some flag that your cellForItemAtIndexPath method references, and then call reloadItemsAtIndexPaths for each row as that row finishes. Because uploads can be slow, you might not want the UI scrolling around as these asynchronous uploads finish.

tableviewcontroller within container view controller: programatically selecting uitableviewcell, highlighting blinks

I am trying to programatically highlight a table view cell and trigger the selection logic by doing the following
NSIndexPath*indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[self tableView:self.tableView didSelectRowAtIndexPath:indexPath];
The row highlights only for a split second. I want it to stay highlighted until I select another row.
I tried adding these lines
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath: indexPath];
cell.highlighted = YES;
but when I did this, the highlight remained even when I clicked on another row and did not go away until I clicked the first row again.
Any ideas?
Try calling selectRowAtIndexPath but not didSelectRowAtIndexPath. I believe the latter is called as a result of the former. If your delegate deSelects the last selected index path in didSelectRowAtIndexPath, then the double call would result in deselecting what you had just selected
seems like the issue was because I was calling the code from viewDidLoad, I moved it to viewDidAppear and now its fine

Dealing with UITableView's indexPathsForSelectedRows

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];
}

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];
}

Resources