scrollToRow doesn't always scroll to top

I have a UITableView which hold various Settings for an app, including Location. If the user taps on this, there's a segue on the Storyboard that moves to a new UITableViewController which lists all the countries, in sections according to their inital letters. Before the segue, the original TableViewController passes the new TableViewController the index of the currenttly chosen location. I'd like the new TVC to scroll so that this location is at the top when the new TVC is presented.


I created a dummy app with 300 rows and no sections. The 'scrollToRow' function works fine with the vast majority of cases. But if I pass the row 282 as the index, it comes a cropper. It scrolls to row 282, but doesn't move it to the top. It moves it close to the top. The bottom row visible is row 298, rather than 299, so it could clearly scroll up more. I have no idea why it doesn't.


I call the 'scrollToRow' in my 'viewDidLoad' of the new TVC.


var selectedIndex: IndexPath?

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.layoutIfNeeded()
    self.tableView.scrollToRow(at: selectedIndex!, at: .top, animated: false)
}

I've also tried putting the lines in the original TVC...


override func prepare(for segue: UIStoryboardSeghue, sender: Any?) {
    if segue.identifier == "SegueTest" {
        if let test2TVC = segue.destination as? Test2TVC {
            let selectedIndex = IndexPath(row: 282, section: 0)
            test2TVC.selectedIndex = selectedIndex
            test2TVC.tableView.layoutIfNeeded()
            test2TVC.tableView.scrollToRow(at: selectedIndex, at: .top, animated: false)
        }
    }
}


But sadly, this made no difference. I must be doing something wrong as this can clearly be achieved - it's exactly what Apple do in their Settings app on the iPhone. Or perhaps there's a clever work-around.


Any ideas extremely welcome!

Thanks

Replies

Try to move these 2 lines from viewDidLoad


    self.tableView.layoutIfNeeded() 
    self.tableView.scrollToRow(at: selectedIndex!, at: .top, animated: false)


to viewWillAppear


problem is that when you call in viewDidload, all IBOutlets may not yet be set.

Putting them in prepare is the same issue.

Claude31 - thanks very much for the response. Apologies for not responding earlier, but I've been doing some investigation.

Your solution worked perfectly for the dummy app as well as for the amended dummy with sections and section headers.


Sadly, it didn't work when it came to the app I'm working on. And I think it has something to do with the NavigationController I'm using.

That appears to be nudging the tableView down and hence the scrolling does not appear to be going to the top.


Have you come across this before? And is there a way around this?

Many thanks

So you mean that your tableViewController (class Test2TVC) is embedded in a navController ?


Then, in prepare, take care that the destination is the navigationController, not a Test2TVC, the tableViewController you want to reach.


If you test with a print line 9, you should see that.


override func prepare(for segue: UIStoryboardSeghue, sender: Any?) { 
    if segue.identifier == "SegueTest" { 
        if let test2TVC = segue.destination as? Test2TVC { 
            let selectedIndex = IndexPath(row: 282, section: 0) 
            test2TVC.selectedIndex = selectedIndex 
            test2TVC.tableView.layoutIfNeeded() 
            test2TVC.tableView.scrollToRow(at: selectedIndex, at: .top, animated: false) 
        } else {
            print("Not the right destination")
        }
    } 
}



So you have to reach the TableViewController (Test2TVC) itself. You do it in 2 steps:

- first get the NavigationController

- Then get the destination viewController in the navigation stack



override func prepare(for segue: UIStoryboardSeghue, sender: Any?) {
    if segue.identifier == "SegueTest" {
        if let destNavC = segue.destination as? UINavigationController {     // That is what the segue links to
            if let test2TVC = destNavC.topViewController as?  Test2TVC {    // That is the real destination, not segue.destination as? Test2TVC {
                print("That's the right destination")      // JUST for test
                let selectedIndex = IndexPath(row: 282, section: 0)
                test2TVC.selectedIndex = selectedIndex
                test2TVC.tableView.layoutIfNeeded()
                test2TVC.tableView.scrollToRow(at: selectedIndex, at: .top, animated: false)
            }
        }
    }
}

Now you should see in log: "That's the right destination"


Note: I assumed you wanted to go to the first controller in the nav, hence the topVIewController

If you need to reach another one, use instead

destNavC.viewControllers[i]

Up to you to find the right value of i.

Claude31 - Many, many thanks for your responses. Because I had assumed that the problem lay with the scrollToRow call (which to the extent of the viewWillAppear solution, it did), I had over simplied my dummy app. The moment I introduced a NavigationController into the dummay app, this also then started to behave strangely. But it is the original TableViewController that sits inside the NavigationController, not the Test2TVC.


The full structure (of the actual app) is as follows:

- A TabBar Controller with four tabs.

- Each tab has its own NavigationController stack, in order to link screens in that app that are linked by use.

- One of the these four tabs is a Settings tab, which has a NavigationController and the Settings screen as its Root View Controller.

- There is then a segue set up in the Storyboard to link the Settings screen with the Location screen.


Tab Bar Controller -> Navigation Controller -> Settings TableView Controller -(segue)-> Locations TableView Controller.


Many apologies if in trying to simplify things, I've lead you down the wrong path!


Thanks

Did you make the test with the prints ? That will confirm or not the root cause.


Have you a dummy app you could send, that would be easier.


Could you post a mail address temprarily, so that we can share files easily ?

The first print test doesn't print out anything, but nor does the second. The segue is between two UIViewControllers and, as such, the segue.destination is also a UIViewController.


Do have a dummy app that highlights the problem, but would need to pare down the data somewhat. Also, as I'm a little new to all this, how do I set up a temporary email address?


Thanks

I looked at it a bit more.


Problem could be the following:


when you move to top, you cannot move so high that the bottom of the table appears empty.

So, it will work well till some row (9 in section 1 in your case), and after, it does not move any higher.

For row 10, you miss 1 position to the top.

For row 11, you miss 2 positions to the top.


It appears that the navigation bar has some effect.

If you make the tableView the entry point of the storyboard, it works for row 10 (difference from previous), not for 11 (but miss only 1 position).

So, there may be a limited problem due to the navigation header, but really limited.

Could file a bug on this however.