Issues Programmatically Scrolling to Bottom of Table or to Last Table Cell

Originator:johnbrayton
Number:rdar://34865052 Date Originated:2017-10-06
Status:open Resolved:
Product:iOS + SDK -> UIKit Product Version:Xcode Version 9.1 beta (9B37), iOS 11.1 (15B5066f)
Classification:UI/Usability Reproducible:Always
 
Summary:

In iOS 10.3, it is possible to programmatically scroll to the bottom of a UITableView with any one of the following lines of code:

// assumes indexPathOfLastRow defined
self.tableView.scrollToRow(at: indexPathOfLastRow, at: .bottom, animated: true)

self.tableView.scrollRectToVisible(CGRect(x: 0, y: self.tableView.contentSize.height - 1.0, width: 1.0, height: 1.0), animated: true)

self.tableView.setContentOffset(CGPoint(x: 0.0, y: self.tableView.contentSize.height), animated: true)

In iOS 11 and in current iOS 11.1 beta releases, these lines of code will scroll the table view to near the end but often not to the last cell or to the very end. I am attaching a sample project demonstrating the problem.

The attached sample project will reproduce the problem every time. Factors that appear to contribute to the problem are:
• Fixed height cells and fixed height header views.
• If using self-sizing cells and headers, headers and cells being taller than their estimated heights.
• Large quantities of sections or cells.
• Large heights of sections or cells.
• Using animated: true. I can reproduce the issue with animated: false, but animated: true appears to exacerbate the issue.


Steps to Reproduce:

1.  Run the attached sample app.


Expected Results:

After waiting 2 seconds, I expect the app to scroll to the bottom of the table or to the last cell in the table.


Actual Results:

On iOS 10.3, the app will scroll to the bottom of the table or to the last cell in the table as expected. On iOS 11 or iOS 11.1, the app will not scroll all the way to the bottom or to the very last cell.


Version/Build:

Xcode Version 9.1 beta (9B37), iOS 11.1 (15B5066f)


Configuration:

iPhone or Ipad, device or simulator.


===============


This is the sample code from the sample project I attached. It does not depend on any other classes, except that it needs code to instantiate and present this table view controller.

class TableViewController : UITableViewController {
    
    let numSections = 1000
    let numRowsPerSection = 1000
    
    let rowHeight = CGFloat(1000)
    let sectionHeaderHeight = CGFloat(1000)

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        self.tableView.register(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "header")
        self.tableView.rowHeight = self.rowHeight
        self.tableView.sectionHeaderHeight = self.sectionHeaderHeight
    }
    
    override func viewDidAppear(_ animated: Bool) {
        let when = DispatchTime.now() + 2
        DispatchQueue.main.asyncAfter(deadline: when) {
            
            // Any one of these three lines of code will scroll to near the last cell but not quite to the last
            // cell on iOS 11. All three work as expected on iOS 10.
            
            self.tableView.scrollToRow(at: IndexPath(row: self.numRowsPerSection-1, section: self.numSections-1), at: .bottom, animated: true)
//            self.tableView.scrollRectToVisible(CGRect(x: 0, y: self.tableView.contentSize.height - 1.0, width: 1.0, height: 1.0), animated: true)
//            self.tableView.setContentOffset(CGPoint(x: 0.0, y: self.tableView.contentSize.height), animated: true)
        }
    }
    
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return numSections
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return numRowsPerSection
    }
    
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = "section \(indexPath.section), row \(indexPath.row)"
        return cell
    }
    
    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        return self.tableView.dequeueReusableHeaderFooterView(withIdentifier: "header")
    }
    
}

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!