Как прокрутить до конца UITableView?

У меня есть UITableView который заполняется ячейками с динамической высотой. Я хотел бы, чтобы таблица прокручивалась вниз, когда контроллер представления выталкивается из контроллера просмотра.

Я пробовал с contentOffset и tableView scrollToRowAtIndexPath но все же я не получаю идеальное решение именно для этого.

Может кто-нибудь, пожалуйста, помогите мне исправить эту проблему?

Вот мой код для прокрутки:

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

Ответы

Ответ 1

Для 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.reloadData()
scrollToBottom()

Ответ 2

Я бы использовал более общий подход к этому:

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

Ответ 3

Я пробовал подход 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)
    }
}

Ответ 4

Когда вы нажимаете viewcontroller с табличным представлением, вы должны прокручивать указанный индексный патч только после завершения перезагрузки Tableview.

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 завершил перезагрузку.

Ответ 5

Для идеального прокрутки к нижнему решению используйте 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 теперь знает свой размер содержимого.

Я скорее использую self.view.layoutIfNeeded() после заполнения моих данных в tableView и затем вызываю мой метод scrollToBottom(). Это прекрасно работает для меня.

Ответ 6

Небольшое обновление ответа @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)
    }
}

Ответ 7

Быстрая реализация 4 для TableView основанная на @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)
    }
}

Ответ 8

Это работает в Swift 3.0

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

Ответ 9

Работает в 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)

Ответ 10

Если вы использовали UINavigationBar с некоторой высотой и UITableView в UIView попробуйте вычесть высоту UINavigationBar из высоты кадра UITableView. Причина, по которой ваша top точка UITableView совпадает с bottom точкой UINavigationBar так что это влияет на прокрутку ваших bottom элементов UITableView.

Swift 3

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

Ответ 11

[Swift 3, iOS 10]

Я в конечном итоге использовал своеобразное хакерское решение, но оно не зависит от строк indexpaths (что иногда приводит к сбоям), ячеек динамической высоты или перезагрузки таблицы, поэтому кажется довольно универсальным и на практике работает более надежно, чем другие, которых я нашел.

  • используйте KVO для отслеживания содержимого таблицыOffset

  • событие с кадровой прокруткой внутри наблюдателя 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()
    }
}

Ответ 12

Эта функция содержит доводчик завершения:

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

И используйте:

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

Строка tableView.scrollTo ... будет выполнена после безопасной загрузки всех ячеек таблицы.

Ответ 13

Работает в Swift 3+:

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