- PVSM.RU - https://www.pvsm.ru -

Использование UIDynamicAnimator из UIKit Dynamics при создании таблиц на основе UICollectionView

Как известно в последней версии SDK (фреймворк UIKit Dynamics) разработчики могут определять динамическое поведение для UIView объектов, а также других объектов, принимающих протокол UIDynamicItem.

В данной статье я хочу поделиться опытом использования таких объектов.

Целью было сделать анимацию, аналогичную той, что используется в приложении Messages на iOS 7:

image

После поисков и изучения документации решено было использовать UIDynamicAnimator [1] в связке с UICollectionView [2].

Для этого необходимо было создать класс-наследник UICollectionViewFlowLayout [3]:

#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 мы можем указать как изменится поведение с течением времени.

Исходный код [4]

Полезные ссылки про анимации в iOS 7:

Автор: shpygar

Источник [10]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/46546

Ссылки в тексте:

[1] UIDynamicAnimator: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIDynamicAnimator_Class/Reference/Reference.html

[2] UICollectionView: https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionView_class/Reference/Reference.html

[3] UICollectionViewFlowLayout: https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewLayout_class/Reference/Reference.html#//apple_ref/occ/cl/UICollectionViewLayout

[4] Исходный код: http://yadi.sk/d/VUpNmgR-BVyf9

[5] Introduction to UIKit Dynamics: http://www.teehanlax.com/blog/introduction-to-uikit-dynamics/

[6] UIKit Dynamics Tutorial: http://www.raywenderlich.com/50197/uikit-dynamics-tutorial

[7] iOS 7 SDK: Обзор изменений: http://osxdev.ru/blog/ios/137.html

[8] Эффект параллакса в iOS приложениях: http://blog.denivip.ru/index.php/2013/07/%d1%8d%d1%84%d1%84%d0%b5%d0%ba%d1%82-%d0%bf%d0%b0%d1%80%d0%b0%d0%bb%d0%bb%d0%b0%d0%ba%d1%81%d0%b0-%d0%b2-ios-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%b8%d1%8f%d1%85/

[9] Осваиваем Core Motion в iOS: http://blog.denivip.ru/index.php/2013/07/%d0%be%d1%81%d0%b2%d0%b0%d0%b8%d0%b2%d0%b0%d0%b5%d0%bc-core-motion-%d0%b2-ios/

[10] Источник: http://habrahabr.ru/post/198890/