ios 如何滚动到 UITableView 的确切结尾?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33705371/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to scroll to the exact end of the UITableView?
提问by Padmaja
I have a UITableView
that is populated with cells with dynamic height. I would like the table to scroll to the bottom when the view controller is pushed from view controller.
我有一个UITableView
填充有动态高度的单元格。当视图控制器从视图控制器推送时,我希望表格滚动到底部。
I have tried with contentOffset
and tableView scrollToRowAtIndexPath
but still I am not getting the perfect solution for exactly I want.
我已经尝试过contentOffset
,tableView scrollToRowAtIndexPath
但仍然没有得到我想要的完美解决方案。
Can anyone please help me fix this issue?
任何人都可以帮我解决这个问题吗?
Here is my code to scroll:
这是我要滚动的代码:
let indexPath = NSIndexPath(forRow: commentArray.count-1, inSection: 0)
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
回答by Amit Verma
For Swift 3.0
对于Swift 3.0
Write a function :
写一个函数:
func scrollToBottom(){
DispatchQueue.main.async {
let indexPath = IndexPath(row: self.dataArray.count-1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
and call it after you reload the tableview data
并在重新加载 tableview 数据后调用它
tableView.reloadData()
scrollToBottom()
回答by Umair Afzal
I would use more generic approach to this:
我会使用更通用的方法来解决这个问题:
Swift4
斯威夫特4
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)
}
}
}
回答by John Rogers
I tried Umair's approach, however in UITableView
s, sometimes there can be a section with 0 rows; in which case, the code points to an invalid index path (row 0 of an empty section is not a row).
我尝试了 Umair 的方法,但是在UITableView
s 中,有时可能会有 0 行的部分;在这种情况下,代码指向无效的索引路径(空部分的第 0 行不是行)。
Blindly minusing 1 from the number of rows/sections can be another pain point, as, again, the row/section could contain 0 elements.
盲目地从行/节数中减去 1 可能是另一个痛点,因为同样,行/节可能包含 0 个元素。
Here's my solution to scrolling to the bottom-most cell, ensuring the index path is valid:
这是我滚动到最底部单元格的解决方案,确保索引路径有效:
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)
}
}
回答by Jayraj Vala
For perfect scroll to bottom solution use tableView contentOffset
对于完美的滚动到底部解决方案,请使用 tableView contentOffset
func scrollToBottom() {
let point = CGPoint(x: 0, y: self.tableView.contentSize.height + self.tableView.contentInset.bottom - self.tableView.frame.height)
if point.y >= 0{
self.tableView.setContentOffset(point, animated: animate)
}
}
在主队列中执行滚动到底部工作因为它会延迟执行并导致工作,因为在加载 viewController 并延迟通过主队列 tableView 现在知道它的内容大小。
I rather use self.view.layoutIfNeeded()
after filling my data onto tableView and then call my method scrollToBottom()
. This works fine for me.
我宁愿self.view.layoutIfNeeded()
在将数据填充到 tableView 后使用,然后调用我的方法scrollToBottom()
。这对我来说很好用。
回答by iPrabu
When you push the viewcontroller having the tableview you should scrollTo the specified indexPath only after your Tableview is finished reloading.
当您推送具有 tableview 的视图控制器时,您应该仅在 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)
})
The reason for putting the method inside the dispatch_async is once you execute reloadData the next line will get executed immediately and then reloading will happen in main thread. So to know when the tableview gets finished(After all cellforrowindex is finished) we use GCD here. Basically there is no delegate in tableview will tell that the tableview has finished reloading.
将方法放入 dispatch_async 的原因是一旦您执行 reloadData 下一行将立即执行,然后重新加载将在主线程中发生。所以要知道 tableview 何时完成(在所有 cellforrowindex 完成之后)我们在这里使用 GCD。基本上 tableview 中没有委托会告诉 tableview 已完成重新加载。
回答by Gangireddy Rami Reddy
Works in Swift 4+ :
适用于 Swift 4+ :
self.tableView.reloadData()
let indexPath = NSIndexPath(row: self.yourDataArray.count-1, section: 0)
self.tableView.scrollToRow(at: indexPath as IndexPath, at: .bottom, animated: true)
回答by mattyU
A little update of @Umair answer in case your tableView is empty
如果您的 tableView 为空,@Umair 答案的一些更新
func scrollToBottom(animated: Bool = true, delay: Double = 0.0) {
let numberOfRows = tableView.numberOfRows(inSection: tableView.numberOfSections - 1) - 1
guard numberOfRows > 0 else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [unowned self] in
let indexPath = IndexPath(
row: numberOfRows,
section: self.tableView.numberOfSections - 1)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: animated)
}
}
回答by silly_cone
This works in Swift 3.0
这适用于 Swift 3.0
let pointsFromTop = CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude)
tableView.setContentOffset(pointsFromTop, animated: true)
回答by elia
If you used UINavigationBar
with some height and UITableView
in the UIView
try it to subtract UINavigationBar
height from UITableView
's frame height . Cause your UITableView
's top
point same with UINavigationBar
's bottom
point so this affect your UITableView
's bottom
items to scrolling.
如果你使用的UINavigationBar
一些高度和UITableView
在UIView
尝试减去UINavigationBar
从高度UITableView
的框架的高度。因为您UITableView
的top
点用相同UINavigationBar
的bottom
所以这点影响您UITableView
的bottom
项目滚动。
Swift 3
斯威夫特 3
simpleTableView.frame = CGRect.init(x: 0, y: navigationBarHeight, width: Int(view.frame.width), height: Int(view.frame.height)-navigationBarHeight)
回答by Dmitry Salnikov
[Swift 3, iOS 10]
[Swift 3,iOS 10]
I've ended up using kind-of-hacky solution, but it doesn't depend on rows indexpaths (which leads to crashes sometimes), cells dynamic height or table reload event, so it seems pretty universal and in practice works more reliable than others I've found.
我最终使用了一种有点hacky的解决方案,但它不依赖于行索引路径(有时会导致崩溃)、单元格动态高度或表重新加载事件,因此它看起来非常普遍,并且在实践中比我发现的其他人。
use KVO to track table's contentOffset
fire scroll event inside KVO observer
schedule scroll invocation using delayed Timer to filter multiple
observer triggers
使用 KVO 跟踪表的 contentOffset
在 KVO 观察器中触发滚动事件
使用延迟计时器来安排滚动调用以过滤多个
观察者触发器
The code inside some ViewController:
一些 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()
}
}