Как известно в последней версии SDK (фреймворк UIKit Dynamics) разработчики могут определять динамическое поведение для UIView объектов, а также других объектов, принимающих протокол UIDynamicItem.
В данной статье я хочу поделиться опытом использования таких объектов.
Целью было сделать анимацию, аналогичную той, что используется в приложении Messages на iOS 7:
После поисков и изучения документации решено было использовать UIDynamicAnimator в связке с UICollectionView.
Для этого необходимо было создать класс-наследник UICollectionViewFlowLayout:
#import <UIKit/UIKit.h>
@interface DVCollectionViewFlowLayout : UICollectionViewFlowLayout
@end
добавить свойство с типом UIDynamicAnimator
#import "DVCollectionViewFlowLayout.h"
@interface DVCollectionViewFlowLayout()
//объект-аниматор
@property (nonatomic, strong) UIDynamicAnimator *dynamicAnimator;
@end
@implementation DVCollectionViewFlowLayout
@synthesize dynamicAnimator = _dynamicAnimator;
-(id)initr{
self = [super init];
if (self){
_dynamicAnimator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self];
}
return self;
}
Динамическое поведение становится активным, когда мы его добавляем к объекту, который является экземпляром UIDynamicAnimator. Аниматор определяет контекст, в котором динамическое поведение выполняется.
После этого мы должны переопределить в нём нижеперечисленные функции:
#import "DVCollectionViewFlowLayout.h"
// .........
- (void)prepareLayout{
[super prepareLayout];
CGSize contentSize = [self collectionViewContentSize];
NSArray *items = [super layoutAttributesForElementsInRect:CGRectMake(0, 0, contentSize.width, contentSize.height)];
if (items.count != self.dynamicAnimator.behaviors.count) {
[self.dynamicAnimator removeAllBehaviors];
for (UICollectionViewLayoutAttributes *item in items) {
UIAttachmentBehavior *springBehavior = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:item.center];
//экспериментальным путём подобранные значения для лучшего восприятия (на глаз)
springBehavior.length = 0.f;
springBehavior.damping = 1.f;
springBehavior.frequency = 6.8f;
[self.dynamicAnimator addBehavior:springBehavior];
}
}
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
return [self.dynamicAnimator itemsInRect:rect];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
return [self.dynamicAnimator layoutAttributesForCellAtIndexPath:indexPath];
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
CGFloat scrollDelta = newBounds.origin.y - self.collectionView.bounds.origin.y;
CGPoint touchLocation = [self.collectionView.panGestureRecognizer locationInView:self.collectionView];
for (UIAttachmentBehavior *springBehavior in self.dynamicAnimator.behaviors) {
CGPoint anchorPoint = springBehavior.anchorPoint;
CGFloat touchDistance = fabsf(touchLocation.y - anchorPoint.y);
//экспериментальным путём подобранные значения для лучшего восприятия (на глаз)
CGFloat resistanceFactor = 0.002;
UICollectionViewLayoutAttributes *attributes = springBehavior.items.firstObject;
CGPoint center = attributes.center;
float resistedScroll = scrollDelta * touchDistance * resistanceFactor;
float simpleScroll = scrollDelta;
float actualScroll = MIN(abs(simpleScroll), abs(resistedScroll));
if(simpleScroll < 0){
actualScroll *= -1;
}
center.y += actualScroll;
attributes.center = center;
[self.dynamicAnimator updateItemUsingCurrentState:attributes];
}
return NO;
}
-(void)dealloc{
[self.dynamicAnimator removeAllBehaviors];
self.dynamicAnimator = nil;
}
Объект UIAttachmentBehavior определяет связь между динамическим элементом item класса UICollectionViewLayoutAttributes и точкой item.center (центром этого элемента). Когда один элемент или точка перемещается, присоединенный элемент также перемещается. C помощью свойств damping и frequency мы можем указать как изменится поведение с течением времени.
Полезные ссылки про анимации в iOS 7: