Ios 如何滚动到UITableView的确切结尾?
我有一个Ios 如何滚动到UITableView的确切结尾?,ios,swift,uitableview,Ios,Swift,Uitableview,我有一个UITableView,其中填充了具有动态高度的单元格。当视图控制器从视图控制器中推出时,我希望表格滚动到底部 我尝试了contentOffset和tableView scrollToRowAtIndexPath但是我仍然没有得到我想要的完美解决方案 有人能帮我解决这个问题吗 以下是我要滚动的代码: let indexPath = NSIndexPath(forRow: commentArray.count-1, inSection: 0) tableView.scrollToRowAt
UITableView
,其中填充了具有动态高度的单元格。当视图控制器从视图控制器中推出时,我希望表格滚动到底部
我尝试了contentOffset
和tableView scrollToRowAtIndexPath
但是我仍然没有得到我想要的完美解决方案
有人能帮我解决这个问题吗
以下是我要滚动的代码:
let indexPath = NSIndexPath(forRow: commentArray.count-1, inSection: 0)
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
推送具有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已完成重新加载。对于Swift 3.0
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)
})
编写一个函数:
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()
这适用于Swift 3.0
let pointsFromTop = CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude)
tableView.setContentOffset(pointsFromTop, animated: true)
在Swift 3+中工作:
self.tableView.setContentOffset(CGPoint(x: 0, y: self.tableView.contentSize.height - UIScreen.main.bounds.height), animated: true)
如果使用了具有一定高度的
UINavigationBar
,并且在UIView
中使用了UITableView
,请尝试从UITableView
的帧高度中减去UINavigationBar
高度。使UITableView
顶部的点与UINavigationBar
底部的点相同,从而影响UITableView
底部的项目滚动
斯威夫特3
[Swift 3,iOS 10]
我最终使用了一种骇人的解决方案,但它不依赖于行索引路径(有时会导致崩溃)、单元格动态高度或表格重新加载事件,因此它看起来非常通用,在实践中比我发现的其他方法更可靠
- 使用KVO跟踪表的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)
}
}
KVO观察器内部的fire scroll事件
使用延迟计时器计划滚动调用以过滤多个
观察者触发器
某些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()
}
}
我将使用更通用的方法:
迅捷4
扩展UITableView{
func scrolltobttom(){
DispatchQueue.main.async{
设indepath=indepath(
行:self.numberOfRows(第1节:self.numberOfSections-1)-1,
章节:self.numberOfSections-1)
如果HasRowatineXpath(indexPath){
scrollToRow(at:indepath,at:.底部,动画:true)
}
}
}
func scroll totop(){
DispatchQueue.main.async{
设indexPath=indexPath(行:0,节:0)
如果HasRowatineXpath(indexPath){
scrollToRow(at:indepath,at:.top,动画:false)
}
}
}
func hasRowAtIndexPath(indexPath:indexPath)->Bool{
返回indexPath.section
我尝试了Umair的方法,但是在UITableView
s中,有时可能有一个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)
}
}
扩展UITableView{
func scrollToBottomRow(){
DispatchQueue.main.async{
保护self.numberOfSections>0其他{return}
//尝试使用至少有一行的最底部部分
var section=max(self.numberOfSections-1,0)
var行=最大值(self.numberOfRows(第节)-1,0)
var indepath=indepath(行:行,节:节)
//确保索引路径有效,否则请使用上面的部分(可以使用部分
//包含导致无效索引路径的0行)
while!self.indexPathIsValid(indexPath){
截面=最大值(截面-1,0)
行=最大值(self.numberOfRows(第节)-1,0)
indexPath=indexPath(行:行,节:节)
//如果我们到了最后一节,尝试使用第一行
如果indexath.section==0{
indexPath=indexPath(行:0,节:0)
打破
}
}
//如果[0,0]是有效的(可能没有数据源?),请确保不会遇到错误
//这里例外
保护self.indexPathIsValid(indexPath)else{return}
scrollToRow(at:indepath,at:.底部,动画:true)
}
}
func indexPathIsValid(indexPath:indexPath)->Bool{
让section=indexPath.section
让row=indexPath.row
返回节
要获得完美的滚动到底部解决方案,请使用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现在知道了其内容大小,所以它会延迟执行并导致正常工作。
我宁愿在将数据填充到tableView之后使用self.view.layoutifneed()
,然后调用我的方法scrollToBottom()
。这对我来说很好。如果您的tableView为空,请对@Umair answer进行一点更新
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)
}
}
在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)
这是一个包含完成闭合器的函数:
func reloadAndScrollToTop(completion: @escaping () -> Void) {
self.tableView.reload()
completion()
}
和使用:
self.reloadAndScrollToTop(completion: {
tableView.scrollToRow(at: indexPath, at: .top, animated: true))
})
安全加载所有表格单元格后,将执行tableView.scrollTo…
行。您可以
tableView.scrollRectToVisible(CGRect(x: 0, y: tableView.contentSize.height, width: 1, height: 1), animated: true)
self.view.layoutIfNeeded()
extension UITableView {
func scrollToBottom(animated:Bool) {
let numberOfRows = self.numberOfRows(inSection: self.numberOfSections - 1) - 1
if numberOfRows >= 0{
let indexPath = IndexPath(
row: numberOfRows,
section: self.numberOfSections - 1)
self.scrollToRow(at: indexPath, at: .bottom, animated: animated)
} else {
let point = CGPoint(x: 0, y: self.contentSize.height + self.contentInset.bottom - self.frame.height)
if point.y >= 0{
self.setContentOffset(point, animated: animated)
}
}
}
}
DispatchQueue.main.async {
let index = IndexPath(row: self.itens.count-1, section: 0)
self.tableView.scrollToRow(at: index, at: .bottom, animated: true)
}
func scrollToBottom(animated: Bool) {
DispatchQueue.main.async {
let bottomOffset = CGPoint(x: 0, y: self.contentSize.height - self.bounds.size.height + self.safeAreaBottom)
if bottomOffset.y > 0 {
self.setContentOffset(bottomOffset, animated: animated)
}
}
}
import UIKit
extension UITableView {
func scrollToBottom(animated: Bool) {
DispatchQueue.main.async {
let point = CGPoint(x: 0, y: self.contentSize.height + self.contentInset.bottom - self.frame.height)
if point.y >= 0 {
self.setContentOffset(point, animated: animated)
}
}
}
}
extension UITableView {
func scrollToBottom(){
DispatchQueue.main.async {
let indexPath = IndexPath(
row: self.numberOfRows(inSection: self.numberOfSections-1) - 1,
section: self.numberOfSections - 1)
if self.hasRowAtIndexPath(indexPath: indexPath) {
self.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
}
func scrollToTop() {
DispatchQueue.main.async {
let indexPath = IndexPath(row: 0, section: 0)
if self.hasRowAtIndexPath(indexPath: indexPath) {
self.scrollToRow(at: indexPath, at: .top, animated: false)
}
}
}
func hasRowAtIndexPath(indexPath: IndexPath) -> Bool {
return indexPath.section < self.numberOfSections && indexPath.row < self.numberOfRows(inSection: indexPath.section)
}
}