Reloading Table and Clearing Cell Row Selection - uitableview

Up to iOS 8 and Xcode 6, I was able to use a UITableViewController much like Apple's Settings to configure my applications. In segue'ing from one table back to the calling table, I would reload the calling table to update data in the cells, re-select the calling row and then let the view appear animated to clear the selection.
Here's the code in the viewWillAppear method I've been using:
[self.tableView reloadData];
[self.tableView selectRowAtIndexPath:lastselected animated:YES scrollPosition:UITableViewScrollPositionNone];
[super viewWillAppear:animated];
While the cell data is updated, the nice slow disappearance of the selected row/cell doesn't happen. It is effectively cleared immediately. I've tried just about everything I can think of and can't get this working. Please help...
What has changed and how can I get that nice transition back?
Thank you.

viewWillAppear is called before your view is on the screen. So you'll not get any animations working correctly if fired from inside that method. Use viewDidAppear instead and you should see more reliable animations on transition.. Potentially use performSelector with a delay as well if you feel it's happening too quickly.
Personally as well, I'd call [super viewDidAppear:animated]; first in this case. I can't see a reason why it'd be below the other lines of code.
Also just so I'm being 100% clear... viewWillAppear is obviously ok for reloading the tableView so lets do this...
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView reloadData];
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.tableView selectRowAtIndexPath:lastSelected animated:YES scrollPosition:UITableViewScrollPositionNone];
}
Or with the perform selector delay on viewDidAppear...
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSelector:#selector(animateCellSelectionAtIndexPath:) withObject:lastSelected afterDelay:0.2f];
}
-(void)animateCellSelectionAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
}

Related

tableView reloadData and deselectRowAtIndexPath

I am using the following code to deselect a selected table view cell when returning back to the table view in -viewWillAppear:animated.
[self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
I also need to reload the table view's data in this case but when you do that it clears the selected state of the selected cell so you don't see any fade animation.
Is there a way to reload the table data and also preserve the selected state to create the deselect animation?
After several attempts, I've found something that works. You need to set the deselection to occur after a "delay" (of 0 seconds) in order to make sure it happens on the next draw cycle and gets animated properly.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSIndexPath *indexPath = self.tableView.indexPathForSelectedRow;
[self.tableView reloadData];
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[self performSelector:#selector(deselectRow) withObject:nil afterDelay:0];
}
- (void)deselectRow
{
[self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
}
Try this in your viewDidLoad:
[self setClearsSelectionOnViewWillAppear:NO];
The obvious solution suggested by #user2970476 seems to work fine on iOS 7. For iOS 8 I slightly modified #Stonz2's answer to use blocks
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
dispatch_async(dispatch_get_main_queue(), ^(void) { // necessary for iOS 8
[self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
});
}
I also had to set self.clearsSelectionOnViewWillAppear = NO; in viewDidLoad because the IB setting got ignored.
You will need to reload your table view first, select the row you want to indicate then perform the deselect animation. The issue you are having is your order of operation is incorrect.
You can save the current selection with
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
[self.tableView reloadData];
[self.tableView selectRowAtIndexPath:selectedRow animated:NO scrollPosition:UITableViewScrollPositionNone];

Autolayout and Collection View Issue

So the basic issue is that when I click the cell, it should go to cell index 1 in the new view controller, but when you have autolayout on. The collectionview content offset change goes away and it's reset. Turn it off, works fine. Autolayout is somehow causing content offset to reset but I'm not sure why or how to resolve this.
Code available here.
https://github.com/HaloZero/AutolayoutCollectionViewIssue
use you code as :--
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self performSelectorOnMainThread:#selector(doyouwork) withObject:nil waitUntilDone:NO];
}
-(void)doyouwork
{
[self snapToCellAtIndex:1 withAnimation:NO];
}
- (void) snapToCellAtIndex:(NSInteger)index withAnimation:(BOOL) animated
{
NSIndexPath *path = [NSIndexPath indexPathForRow:index inSection:0];
[self.collectionView scrollToItemAtIndexPath:path atScrollPosition:UICollectionViewScrollPositionLeft animated:animated];
}
working for my side.
It is not that auto-layout is resetting the content offset, but the subviews have not been positioned when you try to snap the cell.
With auto-layout, the subviews are positioned a bit later (in which the viewWillAppear is called first). Try to use viewDidLayoutSubviews instead for your snapping of the cell:
From the documentation for viewDidLayoutSubviews:
Notifies the view controller that its view just laid out its subviews.
So after all the constraints have been calculated and your subviews have been laid, you can call the snapping method.
- (void)viewDidLayoutSubviews
{
[super viewWillLayoutSubviews];
[self snapToCellAtIndex:1 withAnimation:NO];
}

How to load new content into a UITableView using IBAction?

FYI - Noob iOS developer here.
My current setup is a UIViewController with a UIView within, then a UITableView within the UIVIew. So it goes like this...
UIViewController --> UIView --> UITableView
The reason for this is because I have other elements wrapped with the tableview. The UIViewController loads dynamic content into the table view. I have a segmented Control in which I want to use to switch the content within the table view.
I've read something on [table reload] and [table beginUpdate] but don't understand how to use it. Any help would be great.
You need to implement a method for UIControlEventValueChanged event ofUISegmentedControl for this.
[yourSegmentedControl addTarget:self action:#selector(segmentChanged:) forControlEvents:UIControlEventValueChanged];
And implement the segmentChanged method like:
- (void)segmentChanged:(id)sender
{
UISegmentedControl *mySegment = (UISegmentedControl *)sender;
switch ([mySegment selectedSegmentIndex])
{
case 1:
//load first contents
break;
case 2:
//load second contents
break;
default:
break;
}
[self.yourTableView reloadData];
}
Ok, so [table reloadData] will reload the data (so if you change the data and want to update the table with the necessary data call this), but straight after you call that make sure to call [table setNeedsDisplay] to refresh the UI.[table beginUpdates]
begins a series of method calls that insert, delete, or select rows and sections of the receiver. You end the processes with [table endUpdates];
Make sure you set your table view's dataSource and delegate to self, this can be done through the xib and programmatically like this:
[table setDelegate: self];
or
[table setDataSource: self];
As said:
Call this delegate method for UISegmentedControl
- (void)segmentedControl:(UISegmentedControl*)segmentedControl didSelectIndex:(NSUInteger)selectedIndex
{
if(selectedIndex == 0)
{
// Update the data
}
else if(selectedIndex == 1)
{
// Update the data
}
[table reloadData];
[table setNeedsDisplay];
}
For example,
Your UIView named *myView and your UITableView named *myTableView,
the time you want to reload tableview, in your UIViewController , you should reload tableview like this:
[self.myView.myTableView reload];
and make sure tableview's delegate and dataSourceDelegate is set correctly.
Need to set the delegate & data source for tableview in ViewController.h file like
UITableViewDataSource, UITableViewDelegate.
Implement delegate & datasource methods in ViewController.m file
[tableview SetDelagate:self];
[tableview SetDatasource:self];
Implement the delegate methods.
And reload the table using
[tableView reloadData];
method.

UITableView - Accessing in Code from ViewWillAppear

I would like to manipulate table cells in my UITableView as it comes back from its detail view, to remind the user what row they last selected. The UITableView was created in IB and there currently isn't a #property or custom class for the UITableView or any other handle. Clearly, it exists when, the user is coming back from the detail view and viewWillAppear is called but I don't see any handle in the debugger for my UITableView.
Any way to simply get a handle to the IB tableView object?
// - The Containing ViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSIndexPath *selected = [self.tableView indexPathForSelectedRow];
if(selected){
//Do something stylish with the last-selected row
NSLog(#"Last row selected was: %#",selected);
}
}
I solved this by creating a class member of NSIndexPath * and set it inside didSelectRowAtIndexPath. Then, when viewWillAppear executes, when the user navigates back fram the table view detail, I have the last-selected row so I can easily highlight it or treat it any way I want:
//The Containing View Controller
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(self.lastSelectedRow){
[self.tableView selectRowAtIndexPath:self.lastSelectedRow animated:YES scrollPosition:UITableViewScrollPositionNone];
}
}

Reloading UITableView behind UISearchDisplayController

I've run into this really strange phenomenon that I can't quite figure out. I have a UITableViewController that manages a UITableView. Pretty simple. I also have a UISearchDisplayController for searching the contents of the table view. The searching functionality will be able to delete items of the content displayed by the table view. So if the user chooses to delete one of the items they found while searching, I want to not only reload the UISearchDisplayController's table view but also the UITableViewController's table view. When I do that, the sections of the regular table view pop out and display above the UISearchDisplayController. It's really quite strange. I think the best way to explain it is with an image:
If any of you know what could possibly be causing this problem or know a workaround, that would be fantastic.
UPDATED AGAIN
As it turns out, if a table's header is reloaded in the background it pops in front of the search controller no matter what.
I solved this by disabling the fetchedResultsController (setting it to nil) and letting it load lazily again when needed when the search disappears.
UPDATED - Original answer below
In my case I'm using two fetchedResultsControllers one for the main tableview and one for the search.
I discovered that preventing animations when adding the section headers prevents this bug. So while searchDisplayController.active I simply disable the animation of the section change. see code below.
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
if (!self.reordering) {
UITableView *myTableView = controller == __fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
UITableViewRowAnimation animation;
if (self.searchDisplayController.active) {
animation = UITableViewRowAnimationNone;
} else {
animation = UITableViewRowAnimationFade;
}
switch(type)
{
case NSFetchedResultsChangeInsert:
[myTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:animation];
break;
case NSFetchedResultsChangeDelete:
[myTableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:animation];
break;
}
}
}
ORIGINAL ANSWER
The other answer doesn't actually work on it's own. The reason is, the header that is showing is not a header in the searchDisplayController's tableview. It's a header from the main tableview that for some reason is being added above the search table view in the view hierarchy.
I solved this problem by disabling updates to the main tableview while searchDisplayController.active = YES.
In my case I'm using a lazily loaded fetched results controller so I did it like this:
- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView {
[self.tableView reloadData];
}
- (void)searchDisplayController:(UISearchDisplayController *)controller willShowSearchResultsTableView:(UITableView *)tableView {
self.fetchedResultsController.delegate = nil;
self.fetchedResultsController = nil;
}
However, I still have the problem that if I want to reloadData on the main tableview so it is seen in the background, the section headers still float in front of the darkened area.
Does anyone have a better solution for this? It seems like a legitimate bug for viewForHeaderInSection and titleForHeaderInSection when data is reloaded while covered by a UISearchDisplayController.
The simplest answer for me is to try and override the search view so you can't see the background table. But that takes away from the "Appleness" of the app.
Our solution is to do the following. It has only been tested in iOS 7:
In viewForHeaderInSection, return nil if self.searchDisplayController.active is YES
In didHideSearchResultsTableView, call [self.tableView reloadData] to reload the headers when the search table disappears
I ran into this recently as well...the approach I decided on was to queue updates to the main tableView in a suspended serial dispatch queue until the the UISearchDisplayController hides the searchResultsTableView. I would probably consider this a bug as the section headers should not show through the main tableView if the searchResultsTableView has taken over that layer.
I solved this in iOS 7 by only reloading the visible rows in the underlying table.
- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
[self.tableView reloadRowsAtIndexPaths:[self.tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationNone];
}
Fixed it..
Add the following lines in viewDidLoad
searchDisplayController.searchResultsTableView.delegate = self;
searchDisplayController.searchResultsTableView.dataSource = self;
That fixed it for me...
[self.searchDisplayController.searchResultsTableView reloadData];
Because of using UITableViewController. self.view is a TableView in the UITableViewController and SearchDisplayController's ContainerView is added to the self.view of UITableViewController. Just use UIViewcontroller.
My solution was to avoid reloading the table if search results were displaying, then reloading any time the search results were dismissed.
I had to set a symbolic breakpoint on UITableView reloadData to find all the calls to reload that were causing the section headers to redraw on top of the search table.
Hopefully you've figured this out by now, but just in case someone stumbles across this question: this is probably happening because your UITableViewController is the data source/delegate for the search table as well as your main table. That is, presumably, you have the same UITableViewDelegate/DataSource methods executing for both table views, and you're returning the same section header for both tables. Make sure you're handling your search results table separately:
- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section {
if (aTableView == [[self searchDisplayController] searchResultsTableView]) {
return nil;
}
// Return a title appropriate for self.tableView here
}

Resources