contentSize не обновляется после вызова reloadData в UICollectionView

Кто-нибудь знает, почему contentSize не обновляется сразу после вызова reloadData в UICollectionView?

Если вам нужно знать contentSize, лучшая работа, которую я нашел, заключается в следующем:

[_collectionView reloadData];

double delayInSeconds = 0.0001;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^(void)
    {
        // TODO: Whatever it is you want to do now that you know the contentSize.
    });

Очевидно, что это довольно хрупкий хак, который делает предположения о реализации Apple, но до сих пор он работал достаточно надежно.

Есть ли у кого-нибудь другие обходные пути или знания о том, почему это происходит? Я обсуждаю отправку радара, потому что я не могу понять, почему они не могут вычислить contentSize в том же цикле выполнения. Именно так UITableView работал на протяжении всей своей реализации.

РЕДАКТИРОВАТЬ: этот вопрос используется для ссылки на метод setContentOffset внутри блока, потому что я хочу прокрутить представление коллекции в своем приложении. Я удалил вызов метода, потому что ответы людей были сосредоточены на том, почему я не использовал scrollToItemAtIndexPath внутри, почему не обновляется contentSize.


person Reid Main    schedule 30.09.2013    source источник
comment
Я думаю, что более подходящим способом было бы использовать scrollToItemAtIndexPath:atScrollPosition:animated:. Что касается того, почему размер содержимого не обновляется, я могу только предположить, что это оптимизация для предотвращения вызова потенциально многочисленных и дорогостоящих вычислений динамического размера или возврата неточной информации и, соответственно, выполнения ненужной обработки и работы.   -  person Matt    schedule 01.10.2013
comment
В реальном приложении я использую это. Для простоты я решил упомянуть setContentOffset. Это хороший момент, когда повторный вызов reloadData может привести к дорогостоящим вычислениям. Жаль, что нет метода reloadData с обработчиком завершения.   -  person Reid Main    schedule 01.10.2013
comment
Позвоните layoutIfNeeded на UICollectionView, прежде чем получить доступ к его contentSize   -  person onmyway133    schedule 05.03.2020


Ответы (6)


Чтобы получить размер содержимого после перезагрузки, попробуйте вызвать collectionViewContentSize объекта макета. Меня устраивает.

person Clement T    schedule 12.02.2014
comment
Сначала я был настроен скептически, но у меня тоже сработало :) - person Zhang; 09.06.2014
comment
Похоже, что вызовы reloadData не сразу запускают представление коллекции для изменения его свойств рендеринга, но, как предположил Клемент, вы можете перейти непосредственно к объекту макета, чтобы немедленно получить эту информацию. - person Reid Main; 23.05.2015
comment
SWIFT 3: пусть contentSize = self.myCollectionView.collectionViewLayout.collectionViewContentSize - person drpawelo; 23.10.2017
comment
Это также решило мою проблему :) вызывало выполнение пакетных обновлений, чтобы получить высоту содержимого, и вызывало сбои, но моя главная проблема заключалась в том, что высоты содержимого еще не было. Но мой макет имел правильную высоту. - person Andrew Edwards; 06.12.2018

Это сработало для меня:

[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView.collectionViewLayout prepareLayout];
person almas    schedule 20.04.2015
comment
У меня работает с комбинацией layoutIfNeeded. - person Kiran Jasvanee; 13.05.2016
comment
Это сработало для меня. У меня есть эта ошибка из-за ячейки с собственным размером, что contentSize не обновляется после перезагрузки данных в iOS 10 и ранее. В iOS 11 мне не нужно было этого делать. - person X.Y.; 12.03.2018
comment
@x.y, как ты это исправил в iOS 11? Это просто сработало? - person LinusGeffarth; 27.06.2018

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

«Недопустимое обновление: недопустимое количество элементов в разделе 0. Количество элементов, содержащихся в существующем разделе после обновления (7), должно быть равно количеству элементов, содержащихся в этом разделе до обновления (100), плюс или минус количество элементов, вставленных или удаленных из этого раздела (0 добавленных, 0 удаленных) и плюс или минус количество элементов, перемещенных в этот раздел или из него (0 добавленных, 0 удаленных)».

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

NSArray *indexPaths = @[indexPath1, indexPath2];
[self.collectionView performBatchUpdates:^() 
{
    [self.collectionView insertItemsAtIndexPaths:indexPaths];
} completion:^(BOOL finished) {
    // TODO: Whatever it is you want to do now that you know the contentSize.
}];

Ниже приведено отредактированное решение моего исходного ответа, предоставленное Kernix, которое, я не думаю, гарантированно сработает.

Попробуйте выполнить BatchUpdates:completion: в UICollectionView. У вас должен быть доступ к обновленным свойствам в блоке завершения. Это будет выглядеть так:

[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^() 
{

} completion:^(BOOL finished) {
    // TODO: Whatever it is you want to do now that you know the contentSize.
}];
person brodney    schedule 12.10.2013
comment
Это выглядит как твердое решение. Изменения будут анимированы, чего я не хочу, но я уверен, что должен быть способ убить анимацию. - person Reid Main; 15.10.2013
comment
Вы можете переопределить исходные атрибуты макета в подклассе макета, используя initialLayoutAttributesForAppearingItemAtIndexPath:. Установите рамку там, чтобы она была рамкой ячеек, которые изменяются. - person brodney; 15.10.2013
comment
Я почти уверен, что это приведет к сбою, вы должны поместить reloadData из блока PerformBatch. - person Aurelien Porte; 29.01.2014

После звонка

[self.collectionView reloadData];

использовать этот:

[self.collectionView setNeedsLayout];
[self.collectionView layoutIfNeeded];

тогда вы можете получить настоящую contentSize

person Ben Shabat    schedule 02.06.2021

Сначала вызовите prepareLayout, и тогда вы получите правильный contentSize:

[self.collectionView.collectionViewLayout prepareLayout];
person Robert Mao    schedule 16.05.2014
comment
Неправильно. Реализация по умолчанию ничего не делает, как заявляет Apple: developer.apple.com/library/prerelease/ios/documentation/UIKit/ - person Can Poyrazoğlu; 08.06.2015
comment
согласен с @CanPoyrazoğlu - person Max MacLeod; 17.08.2015
comment
Это интересно — у меня есть случай, когда вызов invalidateLayout с последующим reloadData не вызовет вызов sizeForItem:. Однако вызов prepareLayout непосредственно между недействительными и перезагрузкой приводит к пересчету размеров во время перезагрузки, как я и ожидал... Документы говорят, что реализация пуста, но я не уверен, что полностью в это верю... - person tyler; 14.09.2016
comment
@CanPoyrazoğlu в документах говорится, что определение метода пусто для UICollectionViewLayout. Во многих случаях вы будете использовать подкласс по умолчанию, UICollectionViewFlowLayout, который, кажется, не имеет пустой реализации, и почему он работает для большинства. - person Jordan Smith; 28.04.2017

В моем случае у меня был пользовательский класс макета, который наследует UICollectionViewFlowLayout и устанавливал его только в Interface Builder. Я случайно удалил класс из проекта, но Xcode не дал мне никакой ошибки, а contentSize представления коллекции всегда возвращал ноль.

Я вернул класс, и он снова начал работать.

person da1    schedule 31.10.2019