插入单元格时动画UICollectionView框架更改

时间:2014-06-24 18:35:11

标签: ios animation uicollectionview uiviewanimation

我想将与动画单元格插入一起运行的动画中的UICollectionView的帧大小更改为performBatchUpdates:completion:块内的同一个集合视图。

这是触发单元格插入的代码:

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

因为单元格插入会导致集合视图的contentSize发生变化,所以我尝试注册KVO以更改该属性,然后从KVO处理程序触发集合视图框架更新。

这种方法的问题是contentSize的KVO触发器触发得太晚了:当时单元插入动画已经完成(实际上,KVO在performBatchUpdates:completion:的完成处理程序获得之前触发在动画播放完UI之后调用了。)

我没有使用自动布局。

编辑:我将a sample project to demonstrate my problem放在GitHub上。

编辑2:我应该提到我需要这个我正在编写的组件(OLEContainerScrollView),该组件应该100%独立于集合视图。因此,我不能对集合视图布局进行子类化,也不会影响触发单元格动画的代码。理想情况下,解决方案也适用于UITableView,它表现出相同的行为。

3 个答案:

答案 0 :(得分:10)

我查看了集合视图和表视图如何更新其内容插入,实际上,滚动视图的内容大小仅在动画完成后才更新。如果不使用私有API,似乎没有一种非常好的方法来监听未来的内容大小,但它是可能的。

对于表格视图,动画开始的触发器为-[UITableView _endCellAnimationsWithContext:]。此方法设置所需的所有动画(仅用于将来的可见单元格),执行它们并设置最终调用-[UITableView _updateContentSize]的完成块。 _updateContentSize使用内部-[UITableView _contentSize]方法设置正确的滚动视图内容大小。由于_endCellAnimationsWithContext:仅处理动画,因此表格视图背后的数据已更新,因此调用_contentSize(或使用valueForKey:@"_contentSize")会返回正确的大小。

集合视图非常相似。触发器为-[UICollectionView _endItemAnimations],它会为每个单元格,页眉和页脚启动许多动画,当所有动画完成后,-[UICollectionView _updateAnimationDidStop:finished:context:]会设置正确的内容大小。由于这是一个集合视图,因此其集合视图布局实际上知道目标内容大小,因此您可以调用-[UICollectionViewLayout collectionViewContentSize]来获取更新的内容大小。

这些都不是在应用商店中使用的好选择。我可以想到的一个选项是添加每个滚动视图子类的ISA调度,并包装所有可动画的入口点,跟踪它们是否被批处理,并且在批处理结束时或在独立动画操作结束时,使用获取目标内容大小的相应方法(-[UITableView _contentSize]-[UICollectionViewLayout collectionViewContentSize])。


原始答案,以防您想要了解自己的集合视图中集合视图大小的更改:

对集合视图布局进行子类化(如果尚未使用),并使用通知中心或委托方法,请在prepareForAnimatedBoundsChange:上通知其他动画。它们将被添加到动画块中。

来自文档:

  

您还可以使用此方法执行其他动画。任何   您创建的动画将添加到用于处理的动画块中   插入,删除和界限的变化。

您可能需要确定更改内容,并仅通知插入​​动画。

答案 1 :(得分:7)

我已经查看了你的demo project,我认为不需要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;
}

我已经通过您的演示项目检查了这个,它的作用对我而言。 如果它也适合你,请试试这个并发表评论。

答案 2 :(得分:-1)

为什么不简单地添加动画块来更改集合视图的框架?
这会产生一个动画边界变化,与插入淡入淡出一起很好。 此解决方案不需要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;
                     }];
}