Ответ 1
Код ниже должен помочь вам/указать вам в правильном направлении. У него есть некоторые дополнительные функции, а также - как очистка ненужных действий аниматора, если они не видны, отслеживая прикосновение, так что поведение аниматора с этой точки. Изъятый из старого проекта так должен работать. Пример проекта Github с видеороликом создания демонстрации - https://github.com/serendipityapps/SpringyCollectionView
@interface VVSpringCollectionViewFlowLayout ()
@property (nonatomic, strong) UIDynamicAnimator *dynamicAnimator;
@property (nonatomic, strong) NSMutableSet *visibleIndexPathsSet;
@property (nonatomic, assign) CGFloat latestDelta;
@end
@implementation VVSpringCollectionViewFlowLayout
- (id)init {
if (self = [super init]) {
self.dynamicAnimator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self];
self.visibleIndexPathsSet = [NSMutableSet set];
}
return self;
}
- (id)initWithCoder:(nonnull NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
self.dynamicAnimator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self];
self.visibleIndexPathsSet = [NSMutableSet set];
}
return self;
}
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
return [self.dynamicAnimator itemsInRect:rect];
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
return [self.dynamicAnimator layoutAttributesForCellAtIndexPath:indexPath];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
return [self.dynamicAnimator layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
}
-(void)prepareLayout {
[super prepareLayout];
// Need to enlarge visible rect slightly to avoid flickering.
CGRect visibleRect = CGRectInset((CGRect){.origin = self.collectionView.bounds.origin, .size = self.collectionView.frame.size}, -100, -100);
NSArray *itemsInVisibleRectArray = [super layoutAttributesForElementsInRect:visibleRect];
NSArray *cells = [itemsInVisibleRectArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes *item, NSDictionary *bindings) {
return !item.representedElementKind;
}]];
NSSet *itemsIndexPathsInVisibleRectSet = [NSSet setWithArray:[cells valueForKey:@"indexPath"]];
// Remove any behaviours that are no longer visible.
NSArray *noLongerVisibleBehavioursCells = [self.dynamicAnimator.behaviors filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIAttachmentBehavior *behaviour, NSDictionary *bindings) {
UICollectionViewLayoutAttributes *item= (UICollectionViewLayoutAttributes*)[[behaviour items] firstObject];
if (!item.representedElementKind) {
BOOL currentlyVisible = [itemsIndexPathsInVisibleRectSet member:[item indexPath]] != nil;
return !currentlyVisible;
}
else {
return NO;
}
}]];
[noLongerVisibleBehavioursCells enumerateObjectsUsingBlock:^(UIAttachmentBehavior *behaviour, NSUInteger index, BOOL *stop) {
UICollectionViewLayoutAttributes *item = (UICollectionViewLayoutAttributes*)[[behaviour items] firstObject];
[self.dynamicAnimator removeBehavior:behaviour];
[self.visibleIndexPathsSet removeObject:[item indexPath]];
}];
// Add any newly visible behaviours.
CGPoint touchLocation = [self.collectionView.panGestureRecognizer locationInView:self.collectionView];
// A "newly visible" item is one that is in the itemsInVisibleRect(Set|Array) but not in the visibleIndexPathsSet
NSArray *newlyVisibleItems = [cells filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes *item, NSDictionary *bindings) {
BOOL currentlyVisible = [self.visibleIndexPathsSet member:item.indexPath] != nil;
return !currentlyVisible;
}]];
[newlyVisibleItems enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *item, NSUInteger idx, BOOL *stop) {
CGPoint center = item.center;
UIAttachmentBehavior *springBehaviour = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:center];
springBehaviour.length = 0.0f;
springBehaviour.damping = 0.8f;
springBehaviour.frequency = 1.0f;
// If our touchLocation is not (0,0), we'll need to adjust our item center "in flight"
if (!CGPointEqualToPoint(CGPointZero, touchLocation)) {
CGFloat yDistanceFromTouch = fabs(touchLocation.y - springBehaviour.anchorPoint.y);
CGFloat xDistanceFromTouch = fabs(touchLocation.x - springBehaviour.anchorPoint.x);
CGFloat scrollResistance = (yDistanceFromTouch + xDistanceFromTouch) / 1500.0f;
if (self.latestDelta < 0) {
center.y += MAX(self.latestDelta, self.latestDelta*scrollResistance);
}
else {
center.y += MIN(self.latestDelta, self.latestDelta*scrollResistance);
}
item.center = center;
}
[self.dynamicAnimator addBehavior:springBehaviour];
[self.visibleIndexPathsSet addObject:item.indexPath];
}];
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
UIScrollView *scrollView = self.collectionView;
CGFloat delta = newBounds.origin.y - scrollView.bounds.origin.y;
self.latestDelta = delta;
CGPoint touchLocation = [self.collectionView.panGestureRecognizer locationInView:self.collectionView];
__block UIDynamicAnimator *weakDynamicAnimator = self.dynamicAnimator;
[self.dynamicAnimator.behaviors enumerateObjectsUsingBlock:^(UIAttachmentBehavior *springBehaviour, NSUInteger idx, BOOL *stop) {
CGFloat yDistanceFromTouch = fabs(touchLocation.y - springBehaviour.anchorPoint.y);
CGFloat xDistanceFromTouch = fabs(touchLocation.x - springBehaviour.anchorPoint.x);
CGFloat scrollResistance = (yDistanceFromTouch + xDistanceFromTouch) / 1500.0f;
UICollectionViewLayoutAttributes *item = (UICollectionViewLayoutAttributes*)[springBehaviour.items firstObject];
CGPoint center = item.center;
if (delta < 0) {
center.y += MAX(delta, delta*scrollResistance);
}
else {
center.y += MIN(delta, delta*scrollResistance);
}
item.center = center;
[weakDynamicAnimator updateItemUsingCurrentState:item];
}];
return NO;
}
@end
Ответ для SampleCode: Образец кода отображает этот нечетный эффект вобуляции, потому что размер элементов генерируется программно и не округлен - точность вычислений создает проблемы для UIDynamics и движка физики, и она никогда не сможет достичь равновесия. Простое округление сгенерированного размера элемента дает физике шанс. см. строку NoteCollectionViewController.swift 77.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let w = round(CellAspectRatio.width * collectionView.frame.width)
let h = round(CellAspectRatio.height * collectionView.frame.height)
return CGSizeMake(w, h)
}