Scroll to top of table view in iOS 11 - uitableview

I have a table view and I want it to always be scrolled to the top when it first appears. The following code used to work fine but it no longer does anything in iOS 11. How do you scroll to the top of a table view in iOS 11?
- (void)viewWillAppear:(BOOL)animated
{
[self.tableView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO];
}

You can use scrollToRowAtIndexPath method
as -
ObjC
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:true];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:true];
}
SWIFT 4
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: UITableViewScrollPosition.top, animated: true)
}
You can also set custom row in indexpath by putting various row & section value.

Seeing as it's in viewWillAppear, I wonder if you are doing this in the wrong place (since the view and table hasn't been displayed yet). Try doing this in viewDidAppear.
On my side, when I wire up [self.tableView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated: NO] to an IBAction, it dutifully moves to the very first (or 0-th) cell in the table (without animating the scroll), so that call definitely still works in iOS 11.

Related

UITableview scroll to top of the index position when move to another page

I have an UITableView, if I move to the another screen and when I come back to the UITableView screen the scroll automatically stops at 1st index position, how can I overcome it.
Thanks.
Override viewWillAppear method like below and scroll to first IndexPath,
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tblList.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
}
For Objective-C,
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[tblList scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
For Objective c
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:self.sectionToShow];
[cont.tableview scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
Your question and description are contradictory, either you want the tableview to remain at same position when user comes back to the page, or you want the tableview to scroll to top. If you go with first option then this solution could help you, for the second option you refer to PPL answer.
Save the tableview content offset before your push to another page.
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
tableViewContentOffset = self.tableView.contentOffset;
}
and set tableview offset again in viewWillAppear
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView setContentOffset:tableViewContentOffset];
}
The easiest solution is to finish your data fetching and calling of reloadData method of UITableView into viewDidLoad method of your UIViewController.
So whenever you move to another scene your previous screen will remain as it is, because viewDidLoad only called once when your UIViewController is initialized.

how to call selectItemAtIndexPath immediately

I am using this line
[self.collectionView selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone];
inside the cellForItemAtIndexPath method, to programatically select an item in the collection view and it is working, the problem is that it only works after I touch the collection view. How do I get it to work immediately after the collection view is loaded?
Call it inside viewDidLoad or viewDidAppear.
If you know the position you want to select on load, you can create a NSIndexPath object by using: [NSIndexPath indexPathForRow:inSection:]
For example: NSIndexPath *myIndex = [NSIndexPath indexPathForRow:0 inSection:0];

deselectRowAtIndexPath issue

I have just started working with tableViews, and I have 5 cells in a tableview in a viewController embedded in a navigationController. When a cell is pressed another view is pushed in. My problem is that the cell I press is selected but the selection doesn't go away. I have tried with this code:
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:indexPath animated:NO];
NewViewController *newViewController = [[NewViewController alloc] init];
newViewController.theTitle = [self.tableView cellForRowAtIndexPath:indexPath].textLabel.text;
[self.navigationController pushViewController: newViewController animated:YES];
}
The tableView is initialized in the viewDidLoad method. Still when I click a cell the new viewController is pushed in but the cell stays selected. What might be the issue here?
You've put your code in the didDeselectRowAtIndexPath - put it in the didSelectRowAtIndexPath. This is an easy mistake to make since didDeselect comes up in code completion before didSelect.
Summary :
You have tableview that is in a ViewController that is in a NavigationController
Use touch a cell
A new ViewController is push on the NavigationController Stack
User hit back and goes back to the screen where you have a tableView
That is what I understand from your question.
At this moment the cell in your tableView should still be selected in order for the user to have a reminder of the last action he as done, but you also want it to get unselected animated at the point ( the mail application is a good example of this behaviour).
So the best place to deselect the cell in your tableView is in - (void)viewDidAppear:(BOOL)animated method of the UIViewController that have the tableview inside it's view. By doing so you will let the user a chance to see the selection before it gently fade away like in mail.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:animated];
}
You can pass nil as argument of deselectRowAtIndexPath, tableView handles it gracefully.
Did you also implement tableView:didSelectRowAtIndexPath:? This method is called every time you tap a cell. So you should put your code there.
The method you are using, tableView:didDeselectRowAtIndexPath: is called whenever a cell is selected and you are tapping a different cell.
I think your problem is that you use wrong method. Change didDeselectRowAtIndexPath to didSelectRowAtIndexPath.

Modal segue needs 2 clicks instead of one

My UITableView needs 2 clicks to show the detail page of the selected cell : one for selection and another one for show the detail view. I would like one clic to directly show the detail view of the clicked cell.
I use a modal segue with this method inside my UITableViewManager.m :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetail"]) {
NSIndexPath *indexPath = [playerList indexPathForSelectedRow];
TCPlayerStat *object = _objects[indexPath.row];
[[segue destinationViewController] setPlayerStat:object];
}
}
I can't work with a push segue because I don't have Navigation Controller (and don't really want to).
I've looked in the TableView Attribute Inspector but didnt find out anything relevant for this. I have Selection "Single Selection" selected and "Show Selection on Touch" checked.
I don't know if this is possible and if it is, where to look..
Thanks for your help.
EDIT 1: When I write the two methods like this, it still doesn't work (needs 2 clicks) and I have a new warning log:
"Warning: Attempt to present TCDetailViewController: 0xa27b900 on
TCRootViewController: 0xa24f050 while a presentation is in progress!"
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"showDetail" sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetail"]) {
NSIndexPath *indexPath = [playerList indexPathForSelectedRow];
TCPlayerStat *object = _objects[indexPath.row];
TCDetailViewController *detailViewController = [segue destinationViewController];
[detailViewController setPlayerStat:object];
}
}
EDIT 2: I don't know why, but sometimes it works perfectly and doesn't need a second click on the table view. Can't find out :/
Solution below
I fond out what the problem was!
I changed a parameter on the Storyboard that I shouldn't have. I wanted the selection cell not to display a background highlight color when I click on it, but it seems the Segue is based on it to work decently.
HOW TO FIX:
In the storyboard, select your Table View Cell in the Navigator and don't chose the "None" option in "Selection" (Attributes Inspector). "Blue", "Gray" or "Default" seems to work nice.
I already had the selection attribute set to "Default" and was still experiencing the two-click problem. That suggestion did, however, point to a related solution. In didSelectRowAtIndexPath, call:
self.tableView.deselectRowAtIndexPath(indexPath, animated: false)
(I'm using Swift, obviously.)
I think the more correct way to solve this problem is to perform any action on Main thread, in your case triggering the segue:
Objective-C
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"showDetail" sender:self];
});
Swift
DispatchQueue.main.async {
self.performSegue(withIdentifier: "showDetail", sender: self)
}
It's actually a bug, related to the main RunLoop and tableViewCell, while selectionStyles force RunLoop to be awake (as it has to process the animation), if you don't have selectionStyle, plus you don't do any startup animations inside your presented viewController, RunLoop will go asleep sometimes.
If you, for some reason, don't want to do the action (in your case it's triggering the segue) on main thread, or you don't have startup animation on presented ViewController, you can simply add empty main thread call at the end and it will also trigger RunLoop to be awake.
Objective-C
[self performSegueWithIdentifier:#"showDetail" sender:self];
dispatch_async(dispatch_get_main_queue(), ^{});
Swift
self.performSegue(withIdentifier: "showDetail", sender: self)
DispatchQueue.main.async {}
A solution that works for me (XCode 9.2) and seems easy, but still allows for None for the Selection is :
As #Tulleb says, in the storyboard use anything but None for the Table View Cell "Selection", but then set it to .none in the code.
For example, like this...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tv.dequeueReusableCell(withIdentifier: "cell")
cell?.selectionStyle = .none // This is the important line
Use
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
instead of
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
Then you can write any code and it works on the first click.
FYI. until now, with Xcode 8.2.1 and Swift 3.x, the issue also exists for my customized UICollectionViewCell subclass (But I suppose subclass is not needed to reproduce this issue. I have no time to test this case.).
I summarize the issue as: tapping on the previously selected cell will consume the tap event to cause deselection, instead of expectedly triggering the selection segues.
So the solution is to, either not use the selection segues, say, but use programmatically triggering segues, or deselect the cell itself whenever a selection segue is triggered.

Ipad TableView for UISplittview

I have a splittView and on the left side of my splittview I added an UITable.
#interface LeftViewController : UITableViewController
I also added all the functions which are requiered for the my Tableview.
I also can see and put data into my tableview...
Now I was trying to add a background Image or set the first cell as a default cell and I notice that I have nowhere a tableview declared in my code ( except of the tableview functions)
How is that possible and how can I set now a background image or select the first cell?
I know that this is the code to select the cell, but there is no myTableView....
- (void)viewDidAppear:(BOOL)animated {
NSIndexPath *indexPath=[NSIndexPath indexPathForRow:0 inSection:0];
[myTableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];
}
Do I have to create a new tableview?
Best regards
Customizations are normally done in the viewDidLoad method. Since you are using a UITableViewController you can access the table view through the tableView property.
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];

Resources