I have a custom flow layout which is adjusting the attributes for cells when they are being inserted and deleted from the CollectionView with the following two functions, but I'm unable to figure out how you would adjust the default animation duration.
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
// Assign the new layout attributes
attributes.transform3D = CATransform3DMakeScale(0.5, 0.5, 0.5);
attributes.alpha = 0;
return attributes;
}
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
// Assign the new layout attributes
attributes.transform3D = CATransform3DMakeScale(0.5, 0.5, 0.5);
attributes.alpha = 0;
return attributes;
}
To solve problem without hack that was proposed in the answer by gavrix
you could subclass UICollectionViewLayoutAttributes with new property CABasicAnimation *transformAnimation
, than create custom transformation with a suitable duration and assign it to attributes in initialLayoutAttributesForAppearingItemAtIndexPath
, then in UICollectionViewCell apply the attributes as needed:
@interface AnimationCollectionViewLayoutAttributes : UICollectionViewLayoutAttributes
@property (nonatomic, strong) CABasicAnimation *transformAnimation;
@end
@implementation AnimationCollectionViewLayoutAttributes
- (id)copyWithZone:(NSZone *)zone
{
AnimationCollectionViewLayoutAttributes *attributes = [super copyWithZone:zone];
attributes.transformAnimation = _transformAnimation;
return attributes;
}
- (BOOL)isEqual:(id)other {
if (other == self) {
return YES;
}
if (!other || ![[other class] isEqual:[self class]]) {
return NO;
}
if ([(( AnimationCollectionViewLayoutAttributes *) other) transformAnimation] != [self transformAnimation]) {
return NO;
}
return YES;
}
@end
In Layout class
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
AnimationCollectionViewLayoutAttributes* attributes = (AnimationCollectionViewLayoutAttributes* )[super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
transformAnimation.duration = 1.0f;
CGFloat height = [self collectionViewContentSize].height;
transformAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, 2*height, height)];
transformAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, attributes.bounds.origin.y, 0)];
transformAnimation.removedOnCompletion = NO;
transformAnimation.fillMode = kCAFillModeForwards;
attributes.transformAnimation = transformAnimation;
return attributes;
}
+ (Class)layoutAttributesClass {
return [AnimationCollectionViewLayoutAttributes class];
}
then in UICollectionViewCell apply the attributes
- (void) applyLayoutAttributes:(AnimationCollectionViewLayoutAttributes *)layoutAttributes
{
[[self layer] addAnimation:layoutAttributes.transformAnimation forKey:@"transform"];
}
change CALayer's speed
@implementation Cell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.layer.speed =0.2;//default speed is 1
}
return self;
}
Building on @rotava's answer, you can temporarily set the animation speed by using a batch update of the collection view:
[self.collectionView performBatchUpdates:^{
[self.collectionView.viewForBaselineLayout.layer setSpeed:0.2];
[self.collectionView insertItemsAtIndexPaths: insertedIndexPaths];
} completion:^(BOOL finished) {
[self.collectionView.viewForBaselineLayout.layer setSpeed:1];
}];
UICollectionView
initiates all animations internally using some hardcoded value. However, you can always override that value until animations are committed.
In general, process looks like this:
- begin animations
- fetch all layout attribues
- apply attributes to views (UICollectionViewCell's)
- commit animations
applying attributes is done under each UICollectionViewCell and you can override animationDuration in appropriate method. The problem is that UICollectionViewCell has public method applyLayoutAttributes:
BUT it's default implementation is empty!. Basically, UICollectionViewCell has other private method called _setLayoutAttributes:
and this private method is called by UICollectionView and this private method calls applyLayoutAttributes:
at the end. Default layout attributes, like frame, position, transform are applied with current animationDuration
before applyLayoutAttributes:
is called.
That said, you have to override animationDuration
in private method _setLayoutAttributes:
- (void) _setLayoutAttributes:(PSTCollectionViewLayoutAttributes *)layoutAttributes
{
[UIView setAnimationDuration:3.0];
[super _setLayoutAttributes:layoutAttributes];
}
This is obviously, not applestore-safe. You can use one of those runtime hacks to override this private method safely.
After trying [CATransaction setAnimationDuration:]
and [UIView setAnimationDuration:]
in every possible phase of the layout process without success, I figured out a somewhat hacky way to change the duration of cell animations created by UICollectionView
that doesn't rely on private API's.
You can use CALayer
's speed
property to change the relative media timing of animations performed on a given layer. For this to work with UICollectionView
, you can change layer.speed
to something less than 1 on the cell's layer. Obviously it's not great to have the cell's layer ALWAYS have a non-unity animation speed, so one option is to dispatch an NSNotification
when preparing for cell animations, to which your cells subscribe, that will change the layer speed, and then change it back at an appropriate time after the animations are finished.
I don't recommend using this approach as a long-term solution as it's pretty roundabout, but it does work. Hopefully Apple will expose more options for UICollectionView animations in the future.
You can set the layer's speed property (like in Rotoava's Answer) to change the control the speed of the animation. The problem is you are using arbitrary values because you do not know the actual duration of the insertion animation.
Using this post you can figure out what the default animation duration is.
newAnimationDuration = (1/layer.speed)*originalAnimationDuration
layer.speed = originalAnimationDuration/newAnimationDuration
If you wanted to make the animation 400ms long, in your layout you would:
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath];
//set attributes here
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
CGFloat originalAnimationDuration = [CATransaction animationDuration];
CGFloat newAnimationDuration = 0.4f;
cell.layer.speed = originalAnimationDuration/newAnimationDuration;
return attributes;
}
In my case I had cells which could be dragged off screen and I wanted to change the duration of the deletion animation based on the speed of the pan gesture.
In the gesture recognizer (which should be part of your collection view):
- (void)handlePanGesture:(UIPanGestureRecognizer *)sender
{
CGPoint dragVelocityVector = [sender velocityInView:self.collectionView];
CGFloat dragVelocity = sqrt(dragVelocityVector.x*dragVelocityVector.x + dragVelocityVector.y*dragVelocityVector.y);
switch (sender.state) {
...
case UIGestureRecognizerStateChanged:{
CustomLayoutClass *layout = (CustomLayoutClass *)self.collectionViewLayout;
layout.dragSpeed = fabs(dragVelocity);
...
}
...
}
Then in your customLayout:
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath];
CGFloat animationDistance = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
CGFloat originalAnimationDuration = [CATransaction animationDuration];
CGFloat newAnimationDuration = animationDistance/self.dragSpeed;
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
cell.layer.speed = originalAnimationDuration/newAnimationDuration;
return attributes;
}
Without subclassing:
[UIView animateWithDuration:2.0 animations:^{
[self.collection reloadSections:indexSet];
}];
An update to @AshleyMills since forBaselineLayout is deprecated
This works
self.collectionView.performBatchUpdates({ () -> Void in
let indexSet = IndexSet(0...(numberOfSections - 1))
self.collectionView.insertSections(indexSet)
self.collectionView.forFirstBaselineLayout.layer.speed = 0.5
}, completion: { (finished) -> Void in
self.collectionView.forFirstBaselineLayout.layer.speed = 1.0
})
You can change UICollectionView layout.speed property, that should change animation duration of your layout...
来源:https://stackoverflow.com/questions/12922780/how-do-you-set-the-duration-for-uicollectionview-animations