ios-如何滚动到UITableView的确切末端?

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

我已经尝试使用contentOffsettableView scrollToRowAtIndexPath,但是我仍然无法获得理想的完美解决方案。

谁能帮我解决这个问题?

这是我要滚动的代码:

let indexPath = NSIndexPath(forRow: commentArray.count-1, inSection: 0)
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
Padmaja asked 2020-08-11T20:07:32Z
12个解决方案
81 votes

对于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()
Amit Verma answered 2020-08-11T20:07:46Z
29 votes

我将使用更通用的方法:

迅捷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)
        }
    }
}
Umair Afzal answered 2020-08-11T20:08:10Z
16 votes

我尝试了Umair的方法,但是在UITableViews中,有时可以有一个包含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)
    }
}
John Rogers answered 2020-08-11T20:08:40Z
7 votes

当您推入具有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,下一行将立即执行,然后在主线程中进行重新加载。 因此,要知道表视图何时完成(所有cellforrowindex完成之后),我们在这里使用GCD。 基本上,在tableview中没有委托会告诉tableview已完成重新加载。

iPrabu answered 2020-08-11T20:09:05Z
7 votes

为了完美滚动到底部解决方案,请使用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)
        }
    }
在主队列中执行滚动到底部的工作是bcoz,它正在延迟执行并导致工作,因为加载viewController并在主队列中进行延迟后tableView现在知道其内容大小。

在将数据填充到tableView之后,我宁愿使用self.view.layoutIfNeeded(),然后调用我的方法scrollToBottom()。这对我来说很好。

Jayraj Vala answered 2020-08-11T20:09:34Z
3 votes

@Umair答案的小更新,以防您的tableView为空

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)
    }
}
mattyU answered 2020-08-11T20:09:54Z
2 votes

适用于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)
GangiReddy Rami Reddy answered 2020-08-11T20:10:14Z
1 votes

在Swift 3.0中有效

let pointsFromTop = CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude)
tableView.setContentOffset(pointsFromTop, animated: true)
silly_cone answered 2020-08-11T20:10:34Z
0 votes

如果在UIView中使用了具有一定高度的UINavigationBarUITableView,请尝试从UITableView的框架高度中减去UINavigationBar的高度。 因为您的UITableViewtop点与UINavigationBarbottom点相同,所以这会影响您的UITableViewbottom项滚动。

迅捷3

simpleTableView.frame = CGRect.init(x: 0, y: navigationBarHeight, width: Int(view.frame.width), height: Int(view.frame.height)-navigationBarHeight)
elia answered 2020-08-11T20:10:59Z
0 votes

[Swift 3,iOS 10]

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

  • 使用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()
    }
}
Dmitry Salnikov answered 2020-08-11T20:11:45Z
0 votes

这是一个包含补全结束符的函数:

func reloadAndScrollToTop(completion: @escaping () -> Void) {
    self.tableView.reload()
    completion()
}

并使用:

self.reloadAndScrollToTop(completion: {
     tableView.scrollToRow(at: indexPath, at: .top, animated: true))
})

在安全装入所有表单元之后,将执行tableView.scrollTo ...行。

Eido 9oya answered 2020-08-11T20:12:14Z
-1 votes

适用于Swift 3+:

        self.tableView.setContentOffset(CGPoint(x: 0, y: self.tableView.contentSize.height - UIScreen.main.bounds.height), animated: true)
Dmitrii Z answered 2020-08-11T20:12:34Z
translate from https://stackoverflow.com:/questions/33705371/how-to-scroll-to-the-exact-end-of-the-uitableview