Анимация изменения кадра 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
, которое проявляет то же поведение.
Ответы
Ответ 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;
}];
}