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

Анимация изменения кадра UICollectionView при вставке ячеек

Я хочу изменить размер кадра UICollectionView в анимации, которая запускается рядом с анимированной вставкой ячейки в один и тот же вид коллекции внутри блока performBatchUpdates:completion:.

Это код, который запускает вставку ячейки:

[collectionView performBatchUpdates:^{
    indexPathOfAddedCell = ...;
    [collectionView insertItemsAtIndexPaths:@[ indexPathOfAddedCell ]];
} completion:nil];

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

Проблема с этим подходом заключается в том, что триггер KVO для contentSize срабатывает слишком поздно: анимация вставки ячейки уже завершена в то время (на самом деле триггеры KVO прямо перед обработчиком завершения performBatchUpdates:completion: вызывается, но после анимация воспроизведена в пользовательском интерфейсе).

Я не использую автомат.

Изменить: я положил пример проекта, чтобы продемонстрировать свою проблему на GitHub.

Изменить 2: Я должен упомянуть, что мне это нужно для компонента, который я пишу (OLEContainerScrollView), который должен быть 100% независимым представления коллекции. Из-за этого я не могу подклассифицировать макет представления коллекции и не влияю на код, запускающий анимацию ячеек. В идеале решение также будет работать для UITableView, которое проявляет то же поведение.

4b9b3361

Ответ 1

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

Для представлений таблиц триггер запуска анимации - -[UITableView _endCellAnimationsWithContext:]. Этот метод устанавливает все необходимые анимации (только для будущих видимых ячеек), выполняет их и устанавливает блок завершения, который в конечном итоге вызывает -[UITableView _updateContentSize]. _updateContentSize использует внутренний метод -[UITableView _contentSize] для установки правильного размера содержимого прокрутки. Поскольку _endCellAnimationsWithContext: имеет дело только с анимацией, данные за табличным представлением уже обновлены, поэтому вызов _contentSize (или с помощью valueForKey:@"_contentSize") возвращает правильный размер.

Это очень похоже на представления коллекции. Триггер -[UICollectionView _endItemAnimations], он запускает много анимаций для каждой ячейки, верхнего и нижнего колонтитула, и когда все анимации заканчиваются, -[UICollectionView _updateAnimationDidStop:finished:context:] устанавливает правильный размер содержимого. Поскольку это представление коллекции, его макет представления коллекций фактически знает размер целевого контента, поэтому вы можете вызвать -[UICollectionViewLayout collectionViewContentSize], чтобы получить обновленный размер содержимого.

Ни один из них не является действительно хорошим вариантом для использования в магазине приложений. Один из вариантов, который я могу придумать, - это ISA-swizzling для каждого подкласса прокрутки, добавленный и обертывающий все анимационные точки входа, отслеживание их партий или нет, и либо в конце партии, либо в конце автономной анимированной операции используйте соответствующие методы (-[UITableView _contentSize] и -[UICollectionViewLayout collectionViewContentSize]), чтобы получить размер целевого содержимого.


Оригинальный ответ, если вы хотите услышать об изменениях в размере вида коллекции в своих собственных представлениях коллекции:

Подкласс макета представления коллекции (если вы еще этого не сделали) и используя либо центр уведомлений, либо метод делегата, сообщите в prepareForAnimatedBoundsChange: для других анимаций. Они будут добавлены в блок анимации.

Из документации:

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

Вам может потребоваться определить, какие изменения есть, и только уведомлять об анимациях вставки.

Ответ 2

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

#import "ViewController.h"

@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate>

@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@property (weak, nonatomic) IBOutlet UIView *otherView;
@property (nonatomic) NSInteger numberOfItemsInCollectionView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.numberOfItemsInCollectionView = 1; // This is our model
}

- (IBAction)addItem:(id)sender
{
    // Change the model
    self.numberOfItemsInCollectionView += 1;

    [UIView animateWithDuration:0.24 animations:^{
        NSIndexPath *indexPathOfInsertedCell = [NSIndexPath indexPathForItem:self.numberOfItemsInCollectionView - 1 inSection:0];
        [self.collectionView insertItemsAtIndexPaths:@[ indexPathOfInsertedCell ]];

        CGRect collectionViewFrame = self.collectionView.frame;
        collectionViewFrame.size.height = (self.numberOfItemsInCollectionView * 40) + 94;
        self.collectionView.frame = collectionViewFrame;
    }];

}

#pragma mark - UICollectionViewDataSource

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return self.numberOfItemsInCollectionView;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
{
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCell" forIndexPath:indexPath];
    return cell;
}

@end

Или если вы хотите эффект затухания:

- (IBAction)addItem:(id)sender
{
    // Change the model
    self.numberOfItemsInCollectionView += 1;

    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionFade;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.fillMode = kCAFillModeForwards;
    transition.duration = 0.5;

    [[self.collectionView layer] addAnimation:transition forKey:@"UICollectionViewInsertRowAnimationKey"];
    NSIndexPath *indexPathOfInsertedCell = [NSIndexPath indexPathForItem:self.numberOfItemsInCollectionView - 1 inSection:0];
    [self.collectionView insertItemsAtIndexPaths:@[ indexPathOfInsertedCell ]];

    CGRect collectionViewFrame = self.collectionView.frame;
    collectionViewFrame.size.height = (self.numberOfItemsInCollectionView * 40) + 94;
    self.collectionView.frame = collectionViewFrame;
}

Я проверил это с вашим демо-проектом, и его работы для меня. Пожалуйста, попробуйте это и прокомментируйте, если он работает для вас тоже.

Ответ 3

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

EDIT: Вы можете запустить этот код при изменении модели

Вот соответствующий код для вашего образца проекта:

- (IBAction)addItem:(id)sender
{
    // Change the model
    self.numberOfItemsInCollectionView += 1;

    // Animate cell insertion
    [self.collectionView performBatchUpdates:^{
        NSIndexPath *indexPathOfInsertedCell = [NSIndexPath indexPathForItem:self.numberOfItemsInCollectionView - 1 inSection:0];
        [self.collectionView insertItemsAtIndexPaths:@[ indexPathOfInsertedCell ]];


    } completion:nil];

    // animate the collection view frame
    [UIView animateWithDuration:.5
                     animations:^{
                         CGRect collectionViewFrame = self.collectionView.frame;
                         collectionViewFrame.size.height = (self.numberOfItemsInCollectionView * 40) + 94;
                         self.collectionView.frame = collectionViewFrame;
                     }];
}