Как прокрутить до конца 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)