Анимиране на промяна на рамката на 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, което показва същото поведение.


person Ole Begemann    schedule 24.06.2014    source източник
comment
Бихте ли публикували примерен проект, който демонстрира проблема?   -  person Tammo Freese    schedule 27.06.2014
comment
@TammoFreese: Вижте github.com/ole/CollectionViewContentSizeAnimation   -  person Ole Begemann    schedule 29.06.2014
comment
@OleBegemann Искам да разширя отговора си. Доколко сте отворени към API на сивата зона и swizzling?   -  person Leo Natan    schedule 03.07.2014
comment
@LeoNatan: Много отворен, тъй като мисля, че това е единственият начин да направите това.   -  person Ole Begemann    schedule 04.07.2014
comment
@OleBegemann Моля, вижте моя актуализиран отговор. Не е красиво, но може да свърши работа.   -  person Leo Natan    schedule 04.07.2014


Отговори (3)


Разгледах как и изгледите на колекция, и изгледите на таблици актуализират своето вмъкване на съдържание и наистина размерът на съдържанието на изгледа за превъртане се актуализира само след завършване на анимацията и в двата случая. Изглежда, че няма наистина добър метод за слушане на бъдещия размер на съдържанието без използване на частен 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: за други анимации. Те ще бъдат добавени към анимационния блок.

От документацията:

Можете също да използвате този метод за извършване на допълнителни анимации. Всички анимации, които създавате, се добавят към анимационния блок, използван за обработка на вмъкванията, изтриванията и промените в границите.

Може да се наложи да определите какви са промените и да уведомявате само за анимации за вмъкване.

person Leo Natan    schedule 28.06.2014
comment
Здравей Оле, скоро ще погледна. - person Leo Natan; 29.06.2014
comment
Както каза Оле Бегеман, решението трябва да работи и с UITableView, така че prepareForAnimatedBoundsChange: ще работи само за UICollectionView - person arturdev; 04.07.2014
comment
@arturdev Търся общо решение. - person Leo Natan; 04.07.2014
comment
@LeoNatan Уау, благодаря много за задълбоченото разследване. Ще експериментирам с това и ще докладвам резултатите си (ако времето позволява, може да отнеме известно време). - person Ole Begemann; 04.07.2014
comment
@Ole Наздраве, кажи ми как върви. - person Leo Natan; 04.07.2014
comment
@LeoNatan: първите резултати са обещаващи, но все още не съм го накарал да работи. Развъртях [UICollectionView _endItemAnimations] и наистина се извиква в точното време. Обаче collectionViewContentSize на оформлението все още не е актуализиран до новата стойност, така че изглежда не мога да го използвам за управление на анимацията. Ще продължа да експериментирам. - person Ole Begemann; 08.07.2014
comment
@Ole и аз ще погледна. За мен това беше правилната стойност. - person Leo Natan; 08.07.2014
comment
@LeoNatan: Уф, моя грешка, съжалявам. Извиквах оригиналния IMP твърде късно в моя swizzled метод. Вече работи. - person Ole Begemann; 08.07.2014
comment
@Ole просто трябва да маскира swizzle, за да премине AppStore сега. Всъщност не е толкова трудно. - person Leo Natan; 08.07.2014

Разгледах вашия демо проект и смятам, че няма нужда от 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;
}

Проверих това с вашия демо проект и той работи за мен. Моля, опитайте това и коментирайте дали работи и за вас.

person arturdev    schedule 03.07.2014
comment
Благодаря за предложението, но както казах, нямам контрол върху кода, който задейства клетъчните анимации. Следователно не мога да го увия в анимационен блок. - person Ole Begemann; 03.07.2014
comment
Можете ли да обясните? Казахте, че нямате контрол, но какво е ` [collectionView insertItemsAtIndexPaths:@[ indexPathOfAddedCell ]];` в този случай? ` - person arturdev; 03.07.2014
comment
Аз не пиша този код. Както споменах, пиша компонент, който трябва да реагира на промени в изгледа на колекцията. - person Ole Begemann; 03.07.2014

Защо просто не добавите анимационен блок за промяна на рамката на изгледа на колекцията?
Това създава анимирана промяна на границите, която върви добре с избледняването на вмъкването. Нещата на KVO не са необходими за това решение.

РЕДАКТИРАНЕ: Можете да задействате този код, когато промените модела

Ето съответния код за вашия примерен проект:

- (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's frame
    [UIView animateWithDuration:.5
                     animations:^{
                         CGRect collectionViewFrame = self.collectionView.frame;
                         collectionViewFrame.size.height = (self.numberOfItemsInCollectionView * 40) + 94;
                         self.collectionView.frame = collectionViewFrame;
                     }];
}
person de.    schedule 03.07.2014