scrollToRowAtIndexPath with UITableView does not w

2019-01-21 11:45发布

问题:

I have in iOS8 a table view like this:

tableView = UITableView(frame: view.bounds, style: .Plain)
view.addSubview(tableView)

When the user types and sends some text in the keyboard, the application ivoke the following method to scroll the tableView. The goal is to view the new text in the screen (like a chat)

let numberOfRows = tableView.numberOfRowsInSection(0)
        if numberOfRows > 0 {
            let indexPath = NSIndexPath(forRow: numberOfRows-1, inSection: 0)
            tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: animated)
        }

But the table view does not scroll to the bootom.

Someone has a solution?

Thank you.

Explanation:

Definition of the class:

class ChatViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextViewDelegate 

then I have the definition of the table view:

var tableView: UITableView! 

in viewDidLoad:

tableView = UITableView(frame: view.bounds, style: .Plain) 
tableView.dataSource = self 
tableView.delegate = self 
view.addSubview(tableView) 

then I have the call to the code that should make the scroll (second block of code on my answer). When I launch the application I expect the tableview to scroll down, but it does not work.

回答1:

The solution below worked for me. It's a combination of solutions found on StackOverflow. I called "scrollToRowAtIndexPath" after a very short delay.

func tableViewScrollToBottom(animated: Bool) {

    let delay = 0.1 * Double(NSEC_PER_SEC)
    let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))

    dispatch_after(time, dispatch_get_main_queue(), {

        let numberOfSections = self.tableView.numberOfSections()
        let numberOfRows = self.tableView.numberOfRowsInSection(numberOfSections-1)

        if numberOfRows > 0 {
            let indexPath = NSIndexPath(forRow: numberOfRows-1, inSection: (numberOfSections-1))
            self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: animated)
        }

    })
}

called by :

tableViewScrollToBottom(true)

Swift 3

func tableViewScrollToBottom(animated: Bool) {
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
        let numberOfSections = self.tableView.numberOfSections
        let numberOfRows = self.tableView.numberOfRows(inSection: numberOfSections-1)

        if numberOfRows > 0 {
            let indexPath = IndexPath(row: numberOfRows-1, section: (numberOfSections-1))
            self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: animated)
        }
    }
}


回答2:

The solution at my problem is:

let numberOfSections = tableView.numberOfSections()
let numberOfRows = tableView.numberOfRowsInSection(numberOfSections-1)

if numberOfRows > 0 {
    println(numberOfSections)
    let indexPath = NSIndexPath(forRow: numberOfRows-1, inSection: (numberOfSections-1))
    tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: animated)
}


回答3:

Try reloading first before calling scroll. Very weired but works for iOS8.

   tableView.reloadData()
   tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: animated)


回答4:

SWIFT 3 VERSION of @Fox5150 answer, with an extension :

extension UITableView {

    func tableViewScrollToBottom(animated: Bool) {

        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {

            let numberOfSections = self.numberOfSections
            let numberOfRows = self.numberOfRows(inSection: numberOfSections-1)
            if numberOfRows > 0 {
                let indexPath = IndexPath(row: numberOfRows-1, section: (numberOfSections-1))
                self.scrollToRow(at: indexPath, at: UITableViewScrollPosition.bottom, animated: animated)
            }
        }
    }
}

USAGE:

self.tableView.tableViewScrollToBottom(animated: true)


回答5:

I have the problem with a bigger index say even 15. Only indexes till 5-6 use to work. By making "animated: false" scrollToRowAtIndexPath() worked perfectly.



回答6:

The better solution is to flip upside down the tableView so you can show new message just by -insertRowsAtIndexPaths:withRowAnimation:

to do it you need first to flip your table by:

_tableView.transform = CGAffineTransformMakeScale (1,-1);

then don't forget to flip back your cells, best place to do it in -awakeFromNib by subclassing:

- (void)awakeFromNib {
    [super awakeFromNib];
    self.contentView.transform = CGAffineTransformMakeScale (1,-1);

//if you have accessoryView
    self.accessoryView.transform = CGAffineTransformMakeScale (1,-1);
}


回答7:

If the keyboard is still visible you have to set the UITableView's contentInset property to compensate for the keyboard height at the bottom of the screen. Otherwise your cell might be hidden behind the keyboard because the UITableView doesn't know that half of its content is hidden by it.

Or use a UITableViewController which handles that automatically.



回答8:

Call scrollToLastPosition() method after loading the tableview like call from viewWillAppear( ) or after table view reload when some thing changed into tableview

func scrollToLastPosition() {
 if !message.isEmpty {
    let indexpath = NSIndexPath(forRow: message.count - 1 , inSection:     0)
    self.tableView.scrollToRowAtIndexPath(indexpath, atScrollPosition: .Bottom, animated: true)
}
}


回答9:

Heres Swift 3 version

let numberOfSections = self.tableView.numberOfSections
let numberOfRows = self.tableView.numberOfRows(inSection: numberOfSections-1)

if numberOfRows > 0 {
    let indexPath = NSIndexPath.init(row: numberOfRows-1, section: numberOfSections-1)
    self.tableView.scrollToRow(at: indexPath as IndexPath, at: UITableViewScrollPosition.bottom, animated: animated)
}


回答10:

// First figure out how many sections there are
 let lastSectionIndex = self.tblTableView!.numberOfSections() - 1

// Then grab the number of rows in the last section

let lastRowIndex = 
self.tblTableView!.numberOfRowsInSection(lastSectionIndex) - 1

// Now just construct the index path

let pathToLastRow = NSIndexPath(forRow: lastRowIndex, inSection: lastSectionIndex)

// Make the last row visible
self.tblTableView?.scrollToRowAtIndexPath(pathToLastRow, atScrollPosition: UITableViewScrollPosition.None, animated: true)


回答11:

It's simple. Just do the scroll operation in viewDidLayoutSubViews()

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    self.scrollToBottom(animated: false)
}

fileprivate func scrollToBottom(animated: Bool) {
    if self.items.count > 0 {
        let lastIndex = IndexPath(row: items.count - 1, section: 1)
        self.tableView.scrollToRow(at: lastIndex, at: .bottom, animated: animated)
    }
}

It seems like tableview could get the correct positon after layout subviews.



回答12:

I know this is an older thread, but it's still relevant. Instead of dealing with timeouts that could sometimes fail to be well timed, use synchronous blocks on the main thread (normally not something I'd do, but we're talking milliseconds):

DispatchQueue.main.sync {
  // Reload code goes here...
  tableView.reloadData()
}

DispatchQueue.main.sync {
  // Row, section and other checks / initialization go here...
  tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}

This always works for me.



回答13:

All above codes are fine, but in my case when I want to load the tableView as well as scroll to bottom of the tableView instantly when the controller loads, for that situation above methods are taking some time to scroll to the bottom of the tableView. So I have followed these codes.

Override tableView reload as

extension UITableView
{
    func reloadData(completion: @escaping ()->()) {
        UIView.animate(withDuration: 0, animations: { self.reloadData() })
        { _ in completion() }
    }
}

Written scroll to bottom method as follows:

func tableViewScrollToBottom
{
     if self.tblChat.contentSize.height > self.tblChat.frame.size.height
     {
         let offset:CGPoint = CGPoint(x: 0,y :self.tblChat.contentSize.height-self.tblChat.frame.size.height)
         self.tblChat.setContentOffset(offset, animated: false)
     }
 }

and then reload tableView when data loads completes from web service or from any core data entity:

tblChat.reloadData()
{
     self.tableViewScrollToBottom
}

Now its working smooth as I wanted.