Перезапустить заголовок или нижний колонтитул UICollectionView?
У меня есть некоторые данные, которые извлекаются в другом потоке, который обновляет заголовок UICollectionView. Однако я не нашел эффективного способа перезагрузки дополнительного представления, такого как верхний или нижний колонтитул.
Я могу вызвать collectionView reloadSections:
, но это перезагружает весь раздел, который не нужен. collectionView reloadItemsAtIndexPaths:
только кажется нацеленным на ячейки (а не дополнительные представления). И вызов setNeedsDisplay
в самом заголовке тоже не работает. Я что-то пропустил?
Ответы
Ответ 1
Вы также можете использовать (ленивый способ)
collectionView.collectionViewLayout.invalidateLayout() // swift
[[_collectionView collectionViewLayout] invalidateLayout] // objc
Более сложным было бы предоставить контекст
collectionView.collectionViewLayout.invalidateLayout(with: context) // swift
[[_collectionView collectionViewLayout] invalidateLayoutWithContext:context] // objc
Затем вы можете создать или настроить контекст самостоятельно, чтобы сообщить о том, что должно быть обновлено, см.: UICollectionViewLayoutInvalidationContext
Там есть функция, которую вы можете переопределить:
invalidateSupplementaryElements(ofKind:at:) // swift
Другой вариант (если вы уже загрузили правильный заголовок/нижний колонтитул/дополнительное представление) и вы хотите обновить представление только новыми данными, чем вы можете использовать одну из следующих функций для его получения:
supplementaryView(forElementKind:at:)//get specific one
visibleSupplementaryViews(ofKind:)//all visible ones
То же самое касается видимых ячеек с visibleCells
. Преимущество простого получения представления и не полной перезагрузки представления состоит в том, что ячейки сохраняют его состояние. Это особенно удобно для ячеек табличного представления, когда для удаления/редактирования/и т.д. Используется свайп, поскольку это состояние теряется после перезагрузки ячейки.
Если вы чувствуете себя фанатиком, вы можете, конечно, также написать несколько расширений, чтобы получать только ячейки/дополнительные виды данного вида, используя дженерики
if let view = supplementaryView(forType: MySupplementaryView.self, at: indexPath) {
configure(view, at indexPath)
}
это предполагает, что у вас есть функция, которая регистрирует/удаляет представления в примере с их именем класса. Я сделал пост об этом здесь
Ответ 2
Я просто столкнулся с той же проблемой, и в итоге я просмотрел представление, используя его тег, чтобы отредактировать метку:
UICollectionReusableView *footer = (UICollectionReusableView*)[self.collectionView viewWithTag:999];
UILabel *footerLabel = (UILabel*)[footer viewWithTag:100];
Как вы сказали, нет необходимости перезагружать весь раздел, который также отменяет любую анимацию на ячейках. Мое решение не идеально, но это достаточно легко.
Ответ 3
У меня такая же проблема. Я попробовал ответить @BobVorks, и он работает нормально, если только ячейка была повторно использована иначе, это не будет. Итак, я попытался найти более чистый способ достичь этого, и я приступил к перезагрузке всего UICollectionView после выполнения executeBatchUpdate (блок завершения), и он отлично работает. Он перезагружает коллекцию без отмены анимации в insertItemsAtIndexPath. На самом деле я лично проголосовал за последние 2 ответа, потому что я нахожу работу, но в моем случае это самый чистый способ сделать это.
[self.collectionView performBatchUpdates:^{
// perform indexpaths insertion
} completion:^(BOOL finished) {
[self.collectionView reloadData];
}];
Ответ 4
[UIView performWithoutAnimation:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:4]];
}];
[UIView performWithoutAnimation:^{
[self.collectionView reloadData];
}];
Ответ 5
Версия Swift 3/4/5:
collectionView.collectionViewLayout.invalidateLayout()
Внимание!
Если вы одновременно измените количество элементов collectionView
(например, нижний колонтитул отобразится только при загрузке всех ячеек), произойдет сбой. Сначала необходимо перезагрузить данные, чтобы убедиться, что количество элементов одинаково до и после invalidateLayout()
:
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
Ответ 6
Вот два способа сделать это.
1.
Создайте изменчивую модель для возврата данных, которые в конечном итоге будут доступны. Используйте KVO в унаследованном классе UICollectionReusableView, чтобы наблюдать за изменениями и обновлять представление заголовка новыми данными по мере их появления.
[model addObserver:headerView
forKeyPath:@"path_To_Header_Data_I_care_about"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
затем реализовать метод прослушивателя в представлении заголовка
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
2.
добавлять уведомление уведомления в представление и отправлять уведомление, когда данные успешно доступны. Недостатком является то, что это широкое применение, а не чистый дизайн.
// place in shared header file
#define HEADER_DATA_AVAILABLE @"Header Data Available Notification Name"
// object can contain userData property which could hole data needed.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(headerDataAvailable:) name:HEADER_DATA_AVAILABLE object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:HEADER_DATA_AVAILABLE object:nil];
Ответ 7
Вот что я сделал для обновления только заголовков разделов, которые в настоящее время загружаются в память:
- Добавьте слабыйToStrong
NSMapTable
. Когда вы создаете заголовок, добавьте заголовок как слабо удерживаемый ключ с объектом indexPath. Если мы повторно используем заголовок, мы обновим indexPath.
- Когда вам нужно обновить заголовки, вы можете теперь перечислять объекты/ключи из
NSMapTable
по мере необходимости.
@interface YourCVController ()
@property (nonatomic, strong) NSMapTable *sectionHeaders;
@end
@implementation YourCVContoller
- (void)viewDidLoad {
[super viewDidLoad];
// This will weakly hold on to the KEYS and strongly hold on to the OBJECTS
// keys == HeaderView, object == indexPath
self.sectionHeaders = [NSMapTable weakToStrongObjectsMapTable];
}
// Creating a Header. Shove it into our map so we can update on the fly
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
PresentationSectionHeader *header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"presentationHeader" forIndexPath:indexPath];
// Shove data into header here
...
// Use header as our weak key. If it goes away we don't care about it
// Set indexPath as object so we can easily find our indexPath if we need it
[self.sectionHeaders setObject:indexPath forKey:header];
return header;
}
// Update Received, need to update our headers
- (void) updateHeaders {
NSEnumerator *enumerator = self.sectionHeaders.keyEnumerator;
PresentationSectionHeader *header = nil;
while ((header = enumerator.nextObject)) {
// Update the header as needed here
NSIndexPath *indexPath = [self.sectionHeaders objectForKey:header];
}
}
@end
Ответ 8
Этот вопрос очень старый, но простой способ сделать это - просто установить задержку, которая покрывает время, в течение которого ваше представление оживляет и отключает анимацию при обновлении представления... обычно удаление или вставка занимает около 0,35 секунд так просто:
delay(0.35){
UIView.performWithoutAnimation{
self.collectionView.reloadSections(NSIndexSet(index: 1))
}
Ответ 9
Моя проблема возникла, когда размеры фреймов для дополнительных видов изменились после аннулирования макета. Оказалось, что дополнительные взгляды не были освежающими. Оказывается, что это так, но я UICollectionReusableView
объекты UICollectionReusableView
программно, и я не UILabel
старые UILabel
. Таким образом, когда представление коллекции удаляло каждый заголовок, UILabels накапливалось, вызывая непредсказуемый вид.
Решение состояло в том, чтобы создать каждый UICollectionReusableView полностью внутри viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
метод, начиная с a) удаления всех подпредставлений из ячейки dequeued, затем b) получения размера кадра из атрибутов макета элемента разрешить добавление новых подпредставлений.
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
yourClass *header = (yourClass *)[collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"identifier" forIndexPath:indexPath];
[[header viewWithTag:1] removeFromSuperview]; // remove additional subviews as required
UICollectionViewLayoutAttributes *attributes = [collectionView layoutAttributesForSupplementaryElementOfKind:kind atIndexPath:indexPath];
CGRect frame = attributes.frame;
UILabel *label = [[UILabel alloc] initWithFrame: // CGRectMake based on header frame
label.tag = 1;
[header addSubview:label];
// configure label
return header;
}
Ответ 10
let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader)[0] as! UICollectionReusableView
Я использовал описанный выше метод, чтобы получить текущий заголовок и успешно обновить его.