#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, а реализация сильно отличается от встроенного представления коллекции.