Как прокрутить до конца UITableView на iPhone до появления представления


139

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

В настоящее время у меня есть следующая функция

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[log count]-1 inSection:0];
[self.table scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];

log - это изменяемый массив, содержащий объекты, составляющие содержимое каждой ячейки.

Приведенный выше код работает нормально, viewDidAppearоднако имеет неприятный побочный эффект: при первом появлении представления верхняя часть таблицы отображается с последующим переходом вниз. Я бы предпочел, чтобы table viewможно было прокрутить вниз до его появления.

Я попробовал свиток viewWillAppearи , viewDidLoadно в обоих случаях данные не были загружены в таблицу еще и как исключение.

Любое руководство будет очень признательно, даже если это всего лишь случай сказать мне, что у меня есть все, что возможно.

Ответы:


150

Я верю этому зову

 tableView.setContentOffset(CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude), animated: false)

буду делать то, что ты хочешь.


14
Отлично, спасибо. Я создал CGPoint с достаточно высоким значением Y, чтобы он всегда отображал дно. После загрузки представления я могу использовать (self.table.contentSize.height - self.table.frame.size.height) для перемещения вниз тем же методом
acqu13sce

8
Хотя это идеальный ответ, так как нам не нужно вычислять количество ячеек, высоту табличного представления и т. Д. НО я хотел бы указать, что нам нужно вызвать это перед перезагрузкой табличного представления ... Это не сработает, если мы напишите это после[table reloadData];
Fahim Parkar

4
не работает на ios 10-12 - таблица просто пропадает в первый раз
Вячеслав Герчиков 07

2
Или просто пролистайте до CGPoint(x: 0, y: tableView.contentSize.height)?
Amber K

6
Ура !!! Заставляет таблицу исчезнуть: -o. Лучше всего использовать [self.table scrollToRowAtIndexPath: indexPath atScrollPosition: UITableViewScrollPositionBottom animated: NO];
Карл Хайн

124

Думаю, самый простой способ:

if (self.messages.count > 0)
{
    [self.tableView 
        scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.messages.count-1 
        inSection:0] 
        atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}

Версия Swift 3:

if messages.count > 0 {
    userDefinedOptionsTableView.scrollToRow(at: IndexPath(item:messages.count-1, section: 0), at: .bottom, animated: true)
}

3
Это не сработает, если ячейка выше, чем UITableView
Олав Гаусакер,

3
Ячейка выше, чем UITableView? никогда не слышал о таком варианте использования.
Chamira Fernando

@ChamiraFernando, это самый простой способ :)
AITAALI_ABDERRAHMANE

Обратите внимание, что имеет смысл заменить messages.countуже реализованный myTableView.dataSource!.tableView(myTableView, numberOfRowsInSection: 0). Да, он длиннее, но может избежать повторения кода. Вам также необходимо обработать необязательное dataSource(не разворачивать принудительно, как в этом примере).
Николай Суванджиев 08

@ChamiraFernando Я знаю, что этот вопрос старый, но то, что вы никогда не видели, не означает, что этого не происходит. Чтобы ответить на ваш вопрос, в таких приложениях, как Foursquare, может быть такая ситуация, когда пользователь пишет отзыв. Высота ячейки больше, чем высота табличного представления. Это прекрасная ситуация.
Кайо,

121

Из ответа Джейкоба это код:

- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    if (self.messagesTableView.contentSize.height > self.messagesTableView.frame.size.height) 
    {
        CGPoint offset = CGPointMake(0, self.messagesTableView.contentSize.height - self.messagesTableView.frame.size.height);
        [self.messagesTableView setContentOffset:offset animated:YES];
    }
}

На iOS 11 вы должны использовать скорректированную высоту рамки табличного представления:UIEdgeInsetsInsetRect(self.messagesTableView.frame, self.messagesTableView.safeAreaInsets).height
Slav

41

Если вам нужно прокрутить до ТОЧНОГО конца содержимого, вы можете сделать это следующим образом:

- (void)scrollToBottom
{
    CGFloat yOffset = 0;

    if (self.tableView.contentSize.height > self.tableView.bounds.size.height) {
        yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height;
    }

    [self.tableView setContentOffset:CGPointMake(0, yOffset) animated:NO];
}

7
Работает с автопложением, НО важно вызывать этот метод из viewDidLayoutSubviews
Оматы,

Не могли бы вы объяснить, зачем нам это нужно yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height; ? Спасибо.
Unheilig

3
@Unheilig Если вы прокрутите до self.tableView.contentSize.heightсодержимого, представление таблицы может быть не видно, потому что вы прокручиваете содержимое ниже. Поэтому вам нужно прокрутить до одного «видимого промежутка в табличном представлении» над концом табличного представления.
Hans One

31

Я использую автопрокладку, и ни один из ответов у меня не помог. Вот мое решение, которое наконец сработало:

@property (nonatomic, assign) BOOL shouldScrollToLastRow;


- (void)viewDidLoad {
    [super viewDidLoad];

    _shouldScrollToLastRow = YES;
}


- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    // Scroll table view to the last row
    if (_shouldScrollToLastRow)
    {
        _shouldScrollToLastRow = NO;
        [self.tableView setContentOffset:CGPointMake(0, CGFLOAT_MAX)];
    }
}

1
У меня это почти работает, но я получаю странный графический сбой, пока данные моей таблицы загружаются из внешнего API. В моем случае мне нужно позвонить setContentOffsetв какой-то другой момент, когда данные были извлечены и просмотр таблицы перезагружен?
jmoz

Попробуйте установить смещение в обработчике завершения вашего запроса.
RaffAl

2
Это не работает в ios 10 - просто показывает таблицу с черным фоном
RunLoop

2
Вместо использования CGFLOAT_MAXя использовал contentSize.height - frame.height + contentInset.bottomпри установке начального смещения содержимого. Использование CGFLOAT_MAXказалось испортить для меня.
Baza207

23

Принятое решение по @JacobRelkin не работает для меня в прошивкой 7.0 с помощью Auto Layout.

У меня есть собственный подкласс, UIViewControllerи я добавил переменную экземпляра _tableViewв качестве его подпредставления view. Я позиционировал _tableViewс помощью Auto Layout. Я пробовал вызывать этот метод в конце viewDidLoadи даже в viewWillAppear:. Ни то, ни другое не сработало.

Итак, я добавил следующий метод в свой собственный подкласс UIViewController.

- (void)tableViewScrollToBottomAnimated:(BOOL)animated {
    NSInteger numberOfRows = [_tableView numberOfRowsInSection:0];
    if (numberOfRows) {
        [_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:numberOfRows-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:animated];
    }
}

Звонок [self tableViewScrollToBottomAnimated:NO]по окончании viewDidLoadработ. К сожалению, он также вызывает tableView:heightForRowAtIndexPath:три раза вызов каждой ячейки.


23

Вот расширение, которое я реализовал в Swift 2.0. Эти функции следует вызывать после tableviewзагрузки:

import UIKit

extension UITableView {
    func setOffsetToBottom(animated: Bool) {
        self.setContentOffset(CGPointMake(0, self.contentSize.height - self.frame.size.height), animated: true)
    }

    func scrollToLastRow(animated: Bool) {
        if self.numberOfRowsInSection(0) > 0 {
            self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0) - 1, inSection: 0), atScrollPosition: .Bottom, animated: animated)
        }
    }
}

3
Это лучше, чем использовать размер содержимого. Для Swift3. если self.numberOfRows (inSection: 0)> 0 {self.scrollToRow (at: IndexPath.init (row: self.numberOfRows (inSection: 0) -1, section: 0), at: .bottom, animated: animated)}
Soohwan Park

если scrollToLastRow этот метод не работает идеально, просто добавьте self.layoutIfNeeded (), и он работает отлично !!
Йогеш Патель,

17

На самом деле "Swifter" способ сделать это быстро:

var lastIndex = NSIndexPath(forRow: self.messages.count - 1, inSection: 0)
self.messageTableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)

работа Идеально подходит для меня.


15

Детали

  • Xcode 8.3.2, быстрый 3.1
  • Xcode 10.2 (10E125), Swift 5

Код

import UIKit

extension UITableView {
    func scrollToBottom(animated: Bool) {
        let y = contentSize.height - frame.size.height
        if y < 0 { return }
        setContentOffset(CGPoint(x: 0, y: y), animated: animated)
    }
}

Применение

tableView.scrollToBottom(animated: true)

Полный образец

Не забудьте вставить код решения!

import UIKit

class ViewController: UIViewController {

    private weak var tableView: UITableView?
    private lazy var cellReuseIdentifier = "CellReuseIdentifier"

    override func viewDidLoad() {
        super.viewDidLoad()
        let tableView = UITableView(frame: view.frame)
        view.addSubview(tableView)
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
        self.tableView = tableView
        tableView.dataSource = self
        tableView.performBatchUpdates(nil) { [weak self] result in
            if result { self?.tableView?.scrollToBottom(animated: true) }
        }
    }
}

extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 100
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath )
        cell.textLabel?.text = "\(indexPath)"
        return cell
    }
}

9

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

NSIndexPath *scrollIndexPath = [NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0];
[[self tableView] scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];

не сработало, потому что давало ошибку, когда высота таблицы была меньше высоты кадра. Обратите внимание, в моей таблице только один раздел.

Решение, которое сработало для меня, заключалось в реализации следующего кода в viewWillAppear:

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// on the initial cell load scroll to the last row (ie the latest Note)
if (initialLoad==TRUE) {
    initialLoad=FALSE; 
    NSIndexPath *scrollIndexPath = [NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0];
    [[self tableView] scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
        CGPoint offset = CGPointMake(0, (1000000.0));
        [self.tableView setContentOffset:offset animated:NO];
    }
}

Для BOOL ivar initialLoad установлено значение TRUE в viewDidLoad.


Вам scrollToRowAtIndexPathвообще нужно звонить ? Вы уже звоните setContentOffsetпотом, что может сделать первый звонок бессмысленным.
Carlos P

9

Для Swift:

if tableView.contentSize.height > tableView.frame.size.height {
    let offset = CGPoint(x: 0, y: tableView.contentSize.height - tableView.frame.size.height)
    tableView.setContentOffset(offset, animated: false)
}


5

Для Swift 3 (Xcode 8.1):

override func viewDidAppear(_ animated: Bool) {
    let numberOfSections = self.tableView.numberOfSections
    let numberOfRows = self.tableView.numberOfRows(inSection: numberOfSections-1)

    let indexPath = IndexPath(row: numberOfRows-1 , section: numberOfSections-1)
    self.tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.middle, animated: true)
}

3
Это не отвечает на вопрос OP, это то, что работало с самого начала. Также вы должны вызвать super.viewDidAppear
streem

4

Это конечно жучок. Возможно, где-то в вашем коде вы используетеtable.estimatedRowHeight = value (например, 100). Замените это значение на максимальное значение, которое, по вашему мнению, может получить высота строки , например 500 .. Это должно решить проблему в сочетании со следующим кодом:

//auto scroll down example
let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))

dispatch_after(time, dispatch_get_main_queue(), {
    self.table.scrollToRowAtIndexPath(NSIndexPath(forRow: self.Messages.count - 1, inSection: 0), atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
})

4

После долгой возни вот что сработало для меня:

var viewHasAppeared = false

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if !viewHasAppeared { goToBottom() }
}

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    viewHasAppeared = true
}

private func goToBottom() {
    guard data.count > 0 else { return }
    let indexPath = NSIndexPath(forRow: data.count - 1, inSection: 0)
    tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: false)
    tableView.layoutIfNeeded()
}

Ключ оказался не оборачивать scrollToRowAtIndexPathвнутри , dispatch_asyncкак предполагают некоторые, но просто после его вызоваlayoutIfNeeded .

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

(Также NB, вам нужен viewHasAppearedфлаг, потому что вы не хотите, чтобы он вызывается goToBottomкаждый раз viewDidLayoutSubviews. Он вызывается, например, при изменении ориентации.)


3

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

//Scroll to bottom of table
CGSize tableSize = myTableView.contentSize;
[myTableView setContentOffset:CGPointMake(0, tableSize.height)];


2

Если вам нужно загружать данные асинхронно перед прокруткой вниз, вот возможное решение:

tableView.alpha = 0 // We want animation!
lastMessageShown = false // This is ivar

viewModel.fetch { [unowned self] result in
    self.tableView.reloadData()

    if !self.lastMessageShown {
        dispatch_async(dispatch_get_main_queue()) { [unowned self] in
            if self.rowCount > 0 {
                self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: self.rowCount, inSection: 0), atScrollPosition: .Bottom, animated: false)
            }

            UIView.animateWithDuration(0.1) {
                self.tableView.alpha = 1
                self.lastMessageShown = true // Do it once
            }
        }
    }
}

2

Функция на Swift 3 прокрутите вниз

 override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(false)
        //scroll down
        if lists.count > 2 {
            let numberOfSections = self.tableView.numberOfSections
            let numberOfRows = self.tableView.numberOfRows(inSection: numberOfSections-1)
            let indexPath = IndexPath(row: numberOfRows-1 , section: numberOfSections-1)
            self.tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.middle, animated: true)
        }
    }

2
func scrollToBottom() {

    let sections = self.chatTableView.numberOfSections

    if sections > 0 {

        let rows = self.chatTableView.numberOfRows(inSection: sections - 1)

        let last = IndexPath(row: rows - 1, section: sections - 1)

        DispatchQueue.main.async {

            self.chatTableView.scrollToRow(at: last, at: .bottom, animated: false)
        }
    }
}

ты должен добавить

DispatchQueue.main.async {
            self.chatTableView.scrollToRow(at: last, at: .bottom, animated: false)
        }

или он не будет прокручиваться вниз.


2

Используйте этот простой код для прокрутки tableView снизу

NSInteger rows = [tableName numberOfRowsInSection:0];
if(rows > 0) {
    [tableName scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rows-1 inSection:0]
                     atScrollPosition:UITableViewScrollPositionBottom
                             animated:YES];
}

1
Это в основном то, что ОП сказал, что он уже пробовал. В этом ответе не рассматривается вопрос OP о том, как заставить его работать должным образом в viewWillAppear.
jk7

2

Спасибо Джейкобу за ответ. действительно полезно, если кому-то интересно с монотачной версией С #

private void SetScrollPositionDown() {
    if (tblShoppingListItem.ContentSize.Height > tblShoppingListItem.Frame.Size.Height) {
        PointF offset = new PointF(0, tblShoppingListItem.ContentSize.Height - tblShoppingListItem.Frame.Size.Height);
        tblShoppingListItem.SetContentOffset(offset,true );
    }
}

1

В iOS это отлично работало для меня

CGFloat height = self.inputTableView.contentSize.height;
if (height > CGRectGetHeight(self.inputTableView.frame)) {
    height -= (CGRectGetHeight(self.inputTableView.frame) - CGRectGetHeight(self.navigationController.navigationBar.frame));
}
else {
    height = 0;
}
[self.inputTableView setContentOffset:CGPointMake(0, height) animated:animated];

Его нужно вызвать из viewDidLayoutSubviews


1

[self.tableViewInfo scrollRectToVisible: CGRectMake (0, self.tableViewInfo.contentSize.height-self.tableViewInfo.height, self.tableViewInfo.width, self.tableViewInfo.height) анимировано: ДА];


1

Принятый ответ не работал с моей таблицей (тысячи строк, динамическая загрузка), но приведенный ниже код работает:

- (void)scrollToBottom:(id)sender {
    if ([self.sections count] > 0) {
        NSInteger idx = [self.sections count] - 1;
        CGRect sectionRect = [self.tableView rectForSection:idx];
        sectionRect.size.height = self.tableView.frame.size.height;
        [self.tableView scrollRectToVisible:sectionRect animated:NO];
    }
}

1

Нет необходимости в прокрутке, вы можете просто сделать это с помощью этого кода:

[YOURTABLEVIEWNAME setContentOffset:CGPointMake(0, CGFLOAT_MAX)];


1

На iOS 12 принятый ответ кажется неверным. Я работал со следующим расширением



import UIKit

extension UITableView {
    public func scrollToBottom(animated: Bool = true) {
        guard let dataSource = dataSource else {
            return
        }

        let sections = dataSource.numberOfSections?(in: self) ?? 1
        let rows = dataSource.tableView(self, numberOfRowsInSection: sections-1)
        let bottomIndex = IndexPath(item: rows - 1, section: sections - 1)

        scrollToRow(at: bottomIndex,
                    at: .bottom,
                    animated: animated)
    }
}

0

В swift 3.0. Если вы хотите перейти к какой-либо конкретной ячейке таблицы, измените значение индекса ячейки, например измените значение "self.yourArr.count".

self.yourTable.reloadData()
self.scrollToBottom() 
func scrollToBottom(){
    DispatchQueue.global(qos: .background).async {
        let indexPath = IndexPath(row: self.yourArr.count-1, section: 0)
        self.tblComment.scrollToRow(at: indexPath, at: .bottom, animated: true)
    }
}

0

Я считаю, что старые решения не работают с swift3.

Если вы знаете числовые строки в таблице, вы можете использовать:

tableView.scrollToRow(
    at: IndexPath(item: listCountInSection-1, section: sectionCount - 1 ), 
    at: .top, 
    animated: true)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.