如何滚动到UITableView的确切结尾?

我有一个UITableView ,其中填充了动态高度的单元格。 当从视图控制器推送视图控制器时,我希望表格滚动到底部。

我已经尝试过contentOffsettableView scrollToRowAtIndexPath但我仍然没有得到我想要的完美解决方案。

有人可以帮我解决这个问题吗?

这是我要滚动的代码:

 let indexPath = NSIndexPath(forRow: commentArray.count-1, inSection: 0) tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true) 

对于Swift 3.0

写一个函数:

 func scrollToBottom(){ DispatchQueue.main.async { let indexPath = IndexPath(row: self.dataArray.count-1, section: 0) self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true) } } 

并在重新加载tableview数据后调用它

 tableView.reloadData() scrollToBottom() 

我会使用更通用的方法:

Swift4

 extension UITableView { func scrollToBottom(){ DispatchQueue.main.async { let indexPath = IndexPath( row: self.numberOfRows(inSection: self.numberOfSections -1) - 1, section: self.numberOfSections - 1) self.scrollToRow(at: indexPath, at: .bottom, animated: true) } } func scrollToTop() { DispatchQueue.main.async { let indexPath = IndexPath(row: 0, section: 0) self.scrollToRow(at: indexPath, at: .top, animated: false) } } } 

当您按下具有tableview的viewcontroller时,只有在Tableview完成重新加载后才应滚动指定的indexPath。

 yourTableview.reloadData() dispatch_async(dispatch_get_main_queue(), { () -> Void in let indexPath = NSIndexPath(forRow: commentArray.count-1, inSection: 0) tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true) }) 

将方法放入dispatch_async的原因是,一旦执行reloadData,下一行将立即执行,然后在主线程中重新加载。 因此要知道tableview何时完成(在完成所有cellforrowindex之后),我们在这里使用GCD。 基本上tableview中没有委托会告诉tableview已经完成重新加载。

我尝试过Umair的方法,但是在UITableView ,有时会有一个包含0行的部分; 在这种情况下,代码指向无效的索引路径(空部分的第0行不是行)。

从行/节的数量中盲目地减少1可能是另一个痛点,因为行/节也可以包含0个元素。

这是我滚动到最底部单元格的解决方案,确保索引路径有效:

 extension UITableView { func scrollToBottomRow() { DispatchQueue.main.async { guard self.numberOfSections > 0 else { return } // Make an attempt to use the bottom-most section with at least one row var section = max(self.numberOfSections - 1, 0) var row = max(self.numberOfRows(inSection: section) - 1, 0) var indexPath = IndexPath(row: row, section: section) // Ensure the index path is valid, otherwise use the section above (sections can // contain 0 rows which leads to an invalid index path) while !self.indexPathIsValid(indexPath) { section = max(section - 1, 0) row = max(self.numberOfRows(inSection: section) - 1, 0) indexPath = IndexPath(row: row, section: section) // If we're down to the last section, attempt to use the first row if indexPath.section == 0 { indexPath = IndexPath(row: 0, section: 0) break } } // In the case that [0, 0] is valid (perhaps no data source?), ensure we don't encounter an // exception here guard self.indexPathIsValid(indexPath) else { return } self.scrollToRow(at: indexPath, at: .bottom, animated: true) } } func indexPathIsValid(_ indexPath: IndexPath) -> Bool { let section = indexPath.section let row = indexPath.row return section < self.numberOfSections && row < self.numberOfRows(inSection: section) } } 

TableView Swift 4实现很大程度上基于@John Rogers的回答:

 extension UITableView { func scrollToBottom() { DispatchQueue.main.async { guard self.numberOfSections > 0 else { return } // Make an attempt to use the bottom-most section with at least one row var section = max(self.numberOfSections - 1, 0) var row = max(self.numberOfRows(inSection: section) - 1, 0) var indexPath = IndexPath(row: row, section: section) // Ensure the index path is valid, otherwise use the section above (sections can // contain 0 rows which leads to an invalid index path) while !self.indexPathIsValid(indexPath: indexPath) { section = max(section - 1, 0) row = max(self.numberOfRows(inSection: section) - 1, 0) indexPath = IndexPath(row: row, section: section) // If we're down to the last section, attempt to use the first row if indexPath.section == 0 { indexPath = IndexPath(row: 0, section: 0) break } } // In the case that [0, 0] is valid (perhaps no data source?), ensure we don't encounter an // exception here guard self.indexPathIsValid(indexPath: indexPath) else { return } self.scrollToRow(at: indexPath, at: .bottom, animated: true) } } private func indexPathIsValid(indexPath: IndexPath) -> Bool { return indexPath.section < numberOfSections && indexPath.row < numberOfRows(inSection: indexPath.section) } } 

这适用于Swift 3.0

 let pointsFromTop = CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude) tableView.setContentOffset(pointsFromTop, animated: true) 

如果您在UIView使用了UINavigationBarUITableView ,请尝试从UITableView的帧UINavigationBar减去UINavigationBar高度。 导致你的UITableViewtopUINavigationBarbottom点相同,这会影响你的UITableViewbottom项目滚动。

斯威夫特3

 simpleTableView.frame = CGRect.init(x: 0, y: navigationBarHeight, width: Int(view.frame.width), height: Int(view.frame.height)-navigationBarHeight) 

[Swift 3,iOS 10]

我最终使用了一种hacky解决方案,但它不依赖于行索引路径(有时会导致崩溃),单元格动态高度或表重新加载事件,因此它看起来非常普遍,并且在实践中比我找到的其他人。

  • 使用KVO跟踪表的contentOffset

  • KVO观察者内部的火焰滚动事件

  • 使用延迟计时器计划滚动调用以过滤多个
    观察者触发器

一些ViewController里面的代码:

 private var scrollTimer: Timer? private var ObserveContext: Int = 0 override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) table.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.new, context: &ObserveContext) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) table.removeObserver(self, forKeyPath: "contentSize") } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if (context == &ObserveContext) { self.scheduleScrollToBottom() } } func scheduleScrollToBottom() { if (self.scrollTimer == nil) { self.scrollTimer = Timer(timeInterval: 0.5, repeats: false, block: { [weak self] (timer) in let table = self!.table let bottomOffset = table.contentSize.height - table.bounds.size.height if !table.isDragging && bottomOffset > 0 { let point: CGPoint = CGPoint(x: 0, y: bottomOffset) table.setContentOffset(point, animated: true) } timer.invalidate() self?.scrollTimer = nil }) self.scrollTimer?.fire() } } 

适用于Swift 3+:

  self.tableView.setContentOffset(CGPoint(x: 0, y: self.tableView.contentSize.height - UIScreen.main.bounds.height), animated: true)