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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 08:14:22  来源:igfitidea点击:

How to scroll to the exact end of the UITableView?

iosswiftuitableview

提问by Padmaja

I have a UITableViewthat 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 contentOffsetand tableView scrollToRowAtIndexPathbut still I am not getting the perfect solution for exactly I want.

我已经尝试过contentOffsettableView 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 UITableViews, 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 的方法,但是在UITableViews 中,有时可能会有 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 UINavigationBarwith some height and UITableViewin the UIViewtry it to subtract UINavigationBarheight from UITableView's frame height . Cause your UITableView's toppoint same with UINavigationBar's bottompoint so this affect your UITableView's bottomitems to scrolling.

如果你使用的UINavigationBar一些高度和UITableViewUIView尝试减去UINavigationBar从高度UITableView的框架的高度。因为您UITableViewtop点用相同UINavigationBarbottom所以这点影响您UITableViewbottom项目滚动。

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()
    }
}