UITableView вставляет строки без прокрутки

#iphone #objective-c #uitableview

#iPhone #objective-c #uitableview

Вопрос:

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

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

Это выглядит прерывисто и кажется хакерским. Какой лучший способ сделать это?

 [tableView beginUpdates];

[tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];

[tableView endUpdates];

NSUInteger iContentOffset = 200; //height of inserted rows

[tableView setContentOffset:CGPointMake(0, iContentOffset)];
  

Комментарии:

1. Я пытаюсь подражать тому, что делает большинство приложений Twitter. Они помещают разделитель между старыми и новыми данными (указывающий на наличие новых данных) и позволяют прокручивать временную шкалу в хронологическом порядке.

2. Вы пробовали scrollToNearestSelectedRowAtScrollPosition:animated: или scrollToRowAtIndexPath:atScrollPosition:animated: после вставки?

3. Не могли бы вы объяснить, почему scrollToRowAtIndexPath:atScrollPosition:animated: и scrollToNearestRow... у вас не работают? РЕДАКТИРОВАТЬ: Хах, Ник победил меня.

4. @Nick @ Josh Я могу выполнить эту прокрутку с помощью setContentOffset: но мой реальный вопрос заключается в том, как избежать прокрутки в первую очередь. Кроме того, оба этих варианта работали хуже, чем setContentOffset: из того, что я пробовал.

5. Просто чтобы понять это: например, вы находитесь в индексе 13 и хотите вставить строку в 3, и это не должно прокручиваться и сохранять pos на 13, верно?

Ответ №1:

Если я правильно понимаю вашу миссию,

Я сделал это таким образом:

 if(self.tableView.contentOffset.y > ONE_OR_X_ROWS_HEIGHT_YOUDECIDE
{

    self.delayOffset = self.tableView.contentOffset;
    self.delayOffset = CGPointMake(self.delayOffset.x, self.delayOffset.y  insertedRowCount * ONE_ROW_HEIGHT);

    [self.tableView reloadData];

    [self.tableView setContentOffset:self.delayOffset animated:NO];    


}else
{
    [self.tableView insertRowsAtIndexPath:indexPathArray   WithRowAnimation:UITableViewRowAnimationTop];

}
  

С помощью этого кода, если пользователь находится в середине таблицы, а не вверху, uitableview перезагрузит новые строки без анимации и без прокрутки.
Если пользователь находится в верхней части таблицы, он увидит анимацию вставки строк.

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

Ответ №2:

Лучший способ, который я нашел для получения желаемого поведения, — это вообще не анимировать вставку. Анимации были причиной прерывистости.

Вместо этого я вызываю:

 [tableView reloadData];

// set the content offset to the height of inserted rows 
// (2 rows * 44 points = 88 in this example)
[tableView setContentOffset:CGPointMake(0, 88)]; 
  

Это приводит к появлению перезагрузки одновременно с изменением смещения содержимого.

Комментарии:

1. Это по-прежнему создает проблему, когда представление таблицы не заполнено… есть идеи?

Ответ №3:

Для дальнейших зрителей, ищущих решение Swift 3 :

Вам нужно сохранить текущее смещение UITableView , затем перезагрузить, а затем снова установить смещение на UITableView .

 func reloadTableView(_ tableView: UITableView) {
    let contentOffset = tableView.contentOffset
    tableView.reloadData()
    tableView.layoutIfNeeded()
    tableView.setContentOffset(contentOffset, animated: false)
}
  

Вызывается: reloadTableView(self.tableView)

Комментарии:

1. Как это сделать при добавлении новых строк сверху?

Ответ №4:

Просто позвоните setContentOffset перед endUpdates , это работает для меня.

 [tableView setContentOffset:CGPointMake(0, iContentOffset)];
[tableView endUpdates];
  

Ответ №5:

Я использую ячейки с собственным размером, и расчетная высота строки была довольно бесполезной, потому что ячейки могут значительно различаться по размеру. Итак, вычисление contentOffset у меня не сработало.

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

 -(void) loadMoreElements:(UIRefreshControl *) refreshControl {
  NSIndexPath *topIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]
  id topElement = [myModel elementAtIndexPath:topIndexPath];

  // Somewhere here you'll need to tell your model to get more data
  [self.tableView reloadData];

  NSIndexPath *indexPath = [myModel indexPathForElement:topElement];

  [self.tableView scrollToRowAtIndexPath:indexPath 
                        atScrollPosition:UITableViewScrollPositionTop 
                                animated:NO];

  [refreshControl endRefreshing];
}
  

Ответ №6:

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

 var indexpathsToReload: [IndexPath] = [] //this should contain the new indexPaths
var height: CGFloat = 0.0
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()   1) {
    UIView.performWithoutAnimation {

        self.tableview.reloadSections(NSIndexSet(index: 0) as IndexSet, with: .none) 
        self.tableview.layoutIfNeeded()
        indexpathsToReload.forEach({ (idx) in
            height  = self.feed.rectForRow(at: idx).height
        })
        let afterContentOffset = self.tableview.contentOffset                             
        let newContentOffset = CGPoint(x: afterContentOffset.x, y: afterContentOffset.y   height)
        self.tableview.setContentOffset(newContentOffset, animated: false)
    }
}
  

ПРЕДОСТЕРЕЖЕНИЕ (ПРЕДУПРЕЖДЕНИЕ)
Этот метод не будет работать, если ваш tableview не «заполнен», т. е. в нем всего пара ячеек. В этом случае вам нужно было бы также увеличить contentSize значение tableview вместе с contentOffset . Я обновлю этот ответ, как только разберусь с этим.

ОБЪЯСНЕНИЕ: В принципе, нам нужно установить contentOffset из tableView в положение, в котором оно было до перезагрузки. Для этого мы просто вычисляем общую высоту всех новых ячеек, которые были добавлены, используя предварительно заполненный indexPath массив (может быть подготовлен при получении новых данных и добавлении их в источник данных), это indexPaths для новых ячеек. Затем мы используем общую высоту всех этих новых ячеек с помощью rectForRow(at: indexPath) и устанавливаем ее в качестве y позиции contentOffset tableView после перезагрузки. DispatchQueue.main.asyncAfter В этом нет необходимости, но я поместил его туда, потому что мне просто нужно дать tableview некоторое время, чтобы вернуться в исходное положение, поскольку я делаю это способом «вытащить для обновления». Также обратите внимание, что в моем случае afterContentOffset.y значение всегда 0 , поэтому я мог бы вместо этого жестко закодировать 0 там.

Комментарии:

1. Отличный ответ. layoutIfNeeded здесь главное. Я бы не стал слишком беспокоиться о сценарии «просмотр таблицы не заполнен». Если содержимое не заполняет весь экран, вставка дополнительных данных не приведет к прокрутке TableView в первую очередь.

2. К сожалению, это не работает с ячейками самостоятельного размера. В этом случае ни contentSize , ни contentOffset не являются стабильными, предсказуемыми значениями. Возможно, кому-то больше повезло с UICollectionViewController тем, чтобы это выглядело как табличное представление.

3. @DrMickeyLauer да, вы правы. Решение не работает с ячейками динамической высоты. Я смог добиться этого путем вычисления heightForRow, но поскольку это невозможно выполнить в фоновом потоке, при выполнении вычисления происходит небольшая задержка. Ваш комментарий к просмотру коллекции правильный, поскольку он более мощный, чем TableView, возможно, вы захотите взглянуть на Instagram IGListKit. Требуется некоторое время, чтобы полностью понять, как работает IGListKit в начале, но он может выполнять целую кучу вещей.

4. Одно предостережение с IGListKit заключается в том, что рефакторинг существующего кода для использования IGListKit может потребовать немалой работы, поскольку он использует только UICollectionView, а реализация сильно отличается от встроенного представления коллекции.