Как сохранить смещение содержимого UITableView после вызова -reloadData - PullRequest
52 голосов
/ 27 декабря 2011
CGPoint offset = [_table contentOffset];
[_table reloadData];
[_table setContentOffset:offset animated:NO];    //unuseful

//    __block UITableView *tableBlock = _table;
//    [self performBlock:^(id sender) {
//        [tableBlock setContentOffset:offset];
//    } afterDelay:2];

Я не знаю ни одного метода делегата, который вызывается после reloadData.И использование afterDelay:2, что является своего рода хаком, может быть слишком коротким или слишком длинным, так как я могу это реализовать?

Ответы [ 12 ]

101 голосов
/ 09 июля 2015

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

CGPoint offset = tableView.contentOffset;
[tableView.messageTable reloadData];
[tableView layoutIfNeeded]; // Force layout so things are updated before resetting the contentOffset.
[tableView setContentOffset:offset];
85 голосов
/ 22 января 2016

Вызов reloadData в tableView не меняет смещение содержимого. Однако, если вы используете UITableViewAutomaticDimension, который был представлен в iOS 8, у вас может возникнуть проблема.

При использовании UITableViewAutomaticDimension необходимо написать метод делегата tableView: estimatedHeightForRowAtIndexPath: и вернуть UITableViewAutomaticDimension вместе с tableView: heightForRowAtIndexPath:, который также возвращает то же самое.

У меня были проблемы с iOS 8 при использовании этого. Это было потому, что метод estimatedHeightForRowAtIndexPath: возвращал неточные значения, хотя я использовал UITableViewAutomaticDimension. Это была проблема с iOS 8, поскольку не было проблем с устройствами на iOS 9.

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

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSNumber *key = @(indexPath.row);
    NSNumber *height = @(cell.frame.size.height);

    [self.cellHeightsDictionary setObject:height forKey:key];
}

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSNumber *key = @(indexPath.row);
    NSNumber *height = [self.cellHeightsDictionary objectForKey:key];

    if (height)
    {
        return height.doubleValue;
    }

    return UITableViewAutomaticDimension;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return UITableViewAutomaticDimension;
}

Проверка наличия высоты выполняется при первой загрузке страницы.

26 голосов
/ 11 октября 2017

Swift 4 вариант ответа @Skywalker:

fileprivate var heightDictionary: [Int : CGFloat] = [:]

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    heightDictionary[indexPath.row] = cell.frame.size.height
}

override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    let height = heightDictionary[indexPath.row]
    return height ?? UITableViewAutomaticDimension
}

Другое решение (взято из MessageKit):

Этот метод следует вызывать вместо reloadData. Это может соответствовать конкретным случаям.

public func reloadDataAndKeepOffset() {
    // stop scrolling
    setContentOffset(contentOffset, animated: false)

    // calculate the offset and reloadData
    let beforeContentSize = contentSize
    reloadData()
    layoutIfNeeded()
    let afterContentSize = contentSize

    // reset the contentOffset after data is updated
    let newOffset = CGPoint(
        x: contentOffset.x + (afterContentSize.width - beforeContentSize.width),
        y: contentOffset.y + (afterContentSize.height - beforeContentSize.height))
    setContentOffset(newOffset, animated: false)
}
21 голосов
/ 21 августа 2015

По умолчанию reloadData сохраняет contentOffset.Тем не менее, он может быть обновлен, если у вас есть неточные оценочные значения RowHeight.

15 голосов
/ 28 марта 2018

В моем случае снимите флажок с высоты строки автоматически и оцените автоматически решенную задачу

fix reload problem

12 голосов
/ 27 декабря 2011

Я недавно работал с reloadData - reloadData не меняет contentOffset и не прокручивает представление таблицы.Это фактически остается тем же самым, если смещение меньше, чем новый объем данных.

7 голосов
/ 27 июля 2018

Если вы применяете метод estimatedHeightForRowAtIndexPath и ваша оценка неверна, вы, возможно, попадете в эту ситуацию.

Чтобы решить эту проблему, вы можете вернуть большую высоту, которая больше, чем высота каждой ячейки в вашем tableView, например:

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { 
    return 800.f; // if 800 is bigger than every possible value of your cell height.
}
6 голосов
/ 30 ноября 2017

Если вы вставляете данные в начале массива dataSource, вам нужно изменить contentOffset следующим образом: Swift 3 +

func prepareReloadData() {
    let previousContentHeight = tableView.contentSize.height
    let previousContentOffset = tableView.contentOffset.y
    tableView.reloadData()
    let currentContentOffset = tableView.contentSize.height - previousContentHeight + previousContentOffset
    tableView.contentOffset = CGPoint(x: 0, y: currentContentOffset)
}
2 голосов
/ 09 декабря 2016

@ Ответ Скайуокера показал лучший способ решения проблемы предполагаемой высоты клеток. Но иногда проблема лежит в другом месте.
Иногда проблема заключается в contentInsets табличного представления. Если вы перезагрузите данные, когда tableView не виден на экране, вы можете столкнуться с неправильным смещением после того, как представление таблицы появится на экране.
Это происходит потому, что UIViewController может управлять вставками, если его scrollView, когда появляется scrollView, позволяет лежать scrollView под прозрачными навигационными панелями и statusBar.
Я сталкивался с таким поведением в iOS 9.1

2 голосов
/ 24 августа 2016

У меня была такая же проблема, однако ни один из предложенных здесь ответов не работал.Вот как я это решил.Подкласс UITableView и метод переопределения layoutSubviews, например:

override func layoutSubviews() {
    let offset = contentOffset
    super.layoutSubviews()
    contentOffset = offset
}
...