Подтвердить что ты не робот

Перезапустить заголовок или нижний колонтитул UICollectionView?

У меня есть некоторые данные, которые извлекаются в другом потоке, который обновляет заголовок UICollectionView. Однако я не нашел эффективного способа перезагрузки дополнительного представления, такого как верхний или нижний колонтитул.

Я могу вызвать collectionView reloadSections:, но это перезагружает весь раздел, который не нужен. collectionView reloadItemsAtIndexPaths: только кажется нацеленным на ячейки (а не дополнительные представления). И вызов setNeedsDisplay в самом заголовке тоже не работает. Я что-то пропустил?

4b9b3361

Ответ 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

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