iOS: 使用UICollectionView实现Wallet效果

Wallet的拖拉效果是不是很炫酷,笔者仿照着自己写了一个Demo, 效果还是可以滴!为什么要用CollectionView来写呢,因为我们可以自定义celllayout attributes,如果cell比较少的话,用scrollview自定义也是可以的,相对更容易控制!

效果.gif

下面由浅入深,一步步的教你怎么做,非常好理解.

第一步:创建一个UICollectionView

基础代码就不贴了,想看的文章末尾有完整的Demo. 对CollectionView不熟悉的同学可以先去补补课,关于这方面的博客很多.

列一下核心代码吧.

子类化UICollectionViewFlowLayout

#import "CollectionViewFlowLayout.h"
#define kCollectionViewWidth (self.collectionView.frame.size.width)
#define kCollectionViewHeight (self.collectionView.frame.size.height)

#define kCellHeight 200 /// cell 高度
#define kCellSpace 100 // cell0Top to cell1Top
#define kComeUpAnimationDuration 0.25
#define kBottomAutoScrollDistance 200
#define kTopAutoScrollDistance 100
#define kCanMoveDistance 30
#define kExpandBottomHeight 50 // 展开cell的bottom距离屏幕bottom的距离,称之为footer吧
#define kExpandBottomFirstCellMarginOfTop 10 // footer里面第一个cell距离顶部的距离
#define kExpandBottomCellSpace 10 // footer里面第cell的间距

#if TARGET_IPHONE_SIMULATOR
#define kAutoScrollSpeed 10
#elif TARGET_OS_IPHONE
#define kAutoScrollSpeed 4
#endif

@implementation CollectionViewFlowLayout

- (instancetype)init
{
    self = [super init];
    if (self) {
        
    }
    return self;
}

- (void)prepareLayout {
    [super prepareLayout];
    self.itemSize = CGSizeMake(kCollectionViewWidth - inset.right - inset.left, kCellHeight);
    self.minimumLineSpacing = kCellSpace - kCellHeight;

}

- (CGSize)collectionViewContentSize {
    UIEdgeInsets inset = self.collectionView.contentInset;
    NSInteger items = [self.collectionView numberOfItemsInSection:0];
    CGSize size = CGSizeMake(self.collectionView.frame.size.width - inset.left - inset.right, (items - 1) * kCellSpace + kCellHeight- inset.bottom);
    return size;
}

这个是关键方法,我们默认只有一个section.这个方法需要返回rect范围之内的cell的布局属性,包括framerect相交的cell.

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSInteger rows = [self.collectionView numberOfItemsInSection:0];
    CGFloat offsetY = self.collectionView.contentOffset.y;
    UIEdgeInsets inset = self.collectionView.contentInset;
    NSMutableArray *attrs = [NSMutableArray arrayWithCapacity:0];\
    // 与rect相交的最大最小item值
    int minRow = MAX(0, floor((offsetY)  / kCellSpace));
    int maxRow = MIN(ceil((offsetY + self.collectionView.frame.size.height)  / kCellSpace), rows);
    for (int row = minRow; row < maxRow; row++) {
        // 顶部只留一个cell
        if (row * kCellSpace >= offsetY - kCellSpace) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:row inSection:0];
            UICollectionViewLayoutAttributes *att = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
            CGRect cellRect = CGRectMake(0, row * kCellSpace, self.collectionView.frame.size.width - inset.right, kCellHeight);
            att.frame = cellRect;
            // 因为我们的cell有重叠,必须设置zIndex,否则复用时层级会有问题
            att.zIndex = att.indexPath.item * 2;
            att.transform3D = CATransform3DMakeTranslation(0, 0, att.indexPath.item * 2);
            if (CGRectIntersectsRect(cellRect, rect) || CGRectContainsRect(cellRect, rect)) {
                [attrs addObject:att];
            }
        }
    }
    return attrs;
}

@end

这个时候看下效果:

9F80A5DD-E8B0-4052-AE07-E53C1DA92E05.png

第二步: 实现cell吸附在顶部的效果

这个时候我们就要控制滑动时cell的坐标,需要在FlowLayout里面重写这个方法:

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSInteger rows = [self.collectionView numberOfItemsInSection:0];
    CGFloat offsetY = self.collectionView.contentOffset.y;
    UIEdgeInsets inset = self.collectionView.contentInset;
    NSMutableArray *attrs = [NSMutableArray arrayWithCapacity:0];\
    // 与rect相交的最大最小item值
    int minRow = MAX(0, floor((offsetY)  / kCellSpace));
    int maxRow = MIN(ceil((offsetY + self.collectionView.frame.size.height)  / kCellSpace), rows);
    for (int row = minRow; row < maxRow; row++) {
        // 顶部只留一个cell
        if (row * kCellSpace >= offsetY - kCellSpace) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:row inSection:0];
            UICollectionViewLayoutAttributes *att = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
            CGRect cellRect = CGRectMake(0, MAX(offsetY, row * kCellSpace), self.collectionView.frame.size.width - inset.right, kCellHeight);
            att.frame = cellRect;
            // 因为我们的cell有重叠,必须设置zIndex,否则复用时层级会有问题
            att.zIndex = att.indexPath.item * 2;
            att.transform3D = CATransform3DMakeTranslation(0, 0, att.indexPath.item * 2);
            if (CGRectIntersectsRect(cellRect, rect) || CGRectContainsRect(cellRect, rect)) {
                [attrs addObject:att];
            }
        }
    }
    return attrs;
}

另外我们需要重新刷新布局,所以需要重写这个方法

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    return YES;
}

哎,顶部的cell可以重叠了

1.gif

第三步: 实现下拉的弹簧效果

同样是在上面的方法中增加代码:

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSInteger rows = [self.collectionView numberOfItemsInSection:0];
    CGFloat offsetY = self.collectionView.contentOffset.y;
    UIEdgeInsets inset = self.collectionView.contentInset;
    NSMutableArray *attrs = [NSMutableArray arrayWithCapacity:0];\
    // 与rect相交的最大最小item值
    int minRow = MAX(0, floor((offsetY)  / kCellSpace));
    int maxRow = MIN(ceil((offsetY + self.collectionView.frame.size.height)  / kCellSpace), rows);
    for (int row = minRow; row < maxRow; row++) {
        // 顶部只留一个cell
        if (row * kCellSpace >= offsetY - kCellSpace) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:row inSection:0];
            UICollectionViewLayoutAttributes *att = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
            CGRect cellRect = CGRectMake(0, MAX(offsetY, row * kCellSpace), self.collectionView.frame.size.width - inset.right, kCellHeight);
            if (offsetY < -inset.top) {
                // 0.25是相对于offsetY的偏移比例,根据需要自行调节
                cellRect.origin.y = att.indexPath.item * kCellSpace - fabs(offsetY + inset.top) + fabs(offsetY + inset.top) * att.indexPath.item * 0.25;
            }
            att.frame = cellRect;
            // 因为我们的cell有重叠,必须设置zIndex,否则复用时层级会有问题
            att.zIndex = att.indexPath.item * 2;
            att.transform3D = CATransform3DMakeTranslation(0, 0, att.indexPath.item * 2);
            if (CGRectIntersectsRect(cellRect, rect) || CGRectContainsRect(cellRect, rect)) {
                [attrs addObject:att];
            }
        }
    }
    return attrs;
}

现在成这样了:

2.gif

第四步: 长按cell进行移动

这部分的代码比较复杂

首先给cell添加一个长按手势,然后在FlowLayout里面处理手势的执行! 然后对要移动的cell进行截图,把这个ImageView添加到当前cell上面,当截图进行移动时,通过坐标来判断当前cell附近的cell是否满足交换条件,如果满足交换条件,则调用- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;方法进行交换,交换完成要更新层级!

另外,如果当前cell处于屏幕的底端或者顶端时,要让CollectionView进行滚动!

因为顶部的cell有吸附的效果,所以在进行顶部滚动交换时会闪屏,没有达到理想的交换效果,笔者暂时还无法解决,希望有大神可以指点下! 所以退而求其次,目前交换时会停止CollectionView的滚动.

下面是具体实现:

#define kCollectionViewWidth (self.collectionView.frame.size.width)
#define kCellHeight 200 /// cell 高度
#define kCellSpace 100 // cell0Top to cell1Top
#define kComeUpAnimationDuration 0.25
#define kBottomAutoScrollDistance 200
#define kTopAutoScrollDistance 100
#define kCanMoveDistance 30

#if TARGET_IPHONE_SIMULATOR
#define kAutoScrollSpeed 10
#elif TARGET_OS_IPHONE
#define kAutoScrollSpeed 4
#endif

typedef NS_ENUM(NSInteger, CollectionViewAutoScrollType){
    CollectionViewAutoScrollNone = 0,
    CollectionViewAutoScrollUp, // 向上
    CollectionViewAutoScrollDown, // 向下
};
@interface CollectionViewFlowLayout ()
@property (nonatomic, strong) UIImageView *shootImageView;

@property(assign, nonatomic) CGPoint longPressGesLastLocation;
@property (nonatomic, strong) CADisplayLink *displayLink;
@property(assign, nonatomic) BOOL isMoveing;
@property (nonatomic, strong) NSIndexPath *currentIndexPath;
@property(weak, nonatomic) CollectionViewCell *currentCell;
@property(assign, nonatomic) CollectionViewAutoScrollType scrollType;

@end

手势的执行方法:

#pragma mark -
#pragma mark -- -- -- -- -- - CollectionViewCell Delegate - -- -- -- -- --
- (void)collectionViewCell:(CollectionViewCell *)cell handlerLongPressGesture:(UILongPressGestureRecognizer *)ges {
    switch (ges.state) {
        case UIGestureRecognizerStateBegan:
        {
            // 对cell进行截图
            NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
            self.currentIndexPath = indexPath;
            self.currentCell = cell;
            self.isMoveing = NO;
            if (!self.shootImageView) {
                self.shootImageView = [UIImageView new];
            }
            self.shootImageView.image = [self screenshotWithView:cell];
            self.shootImageView.frame = cell.frame;
            self.shootImageView.layer.transform = CATransform3DMakeTranslation(0, 0, indexPath.item * 2 + 1);
            [self.collectionView addSubview:self.shootImageView];
            // 让截图浮出来
            cell.hidden = YES;
            [UIView animateWithDuration:kComeUpAnimationDuration animations:^{
                CGRect frame = self.shootImageView.frame;
                frame.origin.y -= 30;
                self.shootImageView.frame = frame;
            } completion:^(BOOL finished) {
            }];
            self.longPressGesLastLocation = [ges locationInView:self.collectionView];
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            // 移动view
            CGPoint location = [ges locationInView:self.collectionView];
            CGFloat translateY = location.y - self.longPressGesLastLocation.y;
            CGRect frame = self.shootImageView.frame;
            
            frame.origin.y += translateY;
            self.shootImageView.frame = frame;
            
            // 如果滑到头则进行滚动
            CGFloat bottom = frame.origin.y - self.collectionView.contentOffset.y - self.collectionView.frame.size.height;
            CGFloat top = frame.origin.y - self.collectionView.contentOffset.y;
            
            if (self.scrollType == CollectionViewAutoScrollNone) {
                // 根据第一次的手势来判断执行那种滚动
                BOOL shouldAutoScrollDown = fabs(top) < kTopAutoScrollDistance && translateY < -0.5;
                BOOL shouldAutoScrollUp = fabs(bottom) < kBottomAutoScrollDistance && translateY > 0.5;
                if (shouldAutoScrollDown) {
                    self.scrollType = CollectionViewAutoScrollDown;
                } else if(shouldAutoScrollUp) {
                    self.scrollType = CollectionViewAutoScrollUp;
                } else {
                    self.scrollType = CollectionViewAutoScrollNone;
                }
                // 处于顶部或者底部的滚动范围之内不做处理
                if (fabs(top) > kTopAutoScrollDistance && fabs(bottom) > kBottomAutoScrollDistance) {
                    [self handlerMoveItemAction];
                }
            } else {
                // 滚动中则只根据距离来判断
                BOOL shouldAutoScrollDown = fabs(top) < kTopAutoScrollDistance;
                BOOL shouldAutoScrollUp = fabs(bottom) < kBottomAutoScrollDistance;
                if (shouldAutoScrollDown) {
                    self.scrollType = CollectionViewAutoScrollDown;
                } else if(shouldAutoScrollUp) {
                    self.scrollType = CollectionViewAutoScrollUp;
                } else {
                    self.scrollType = CollectionViewAutoScrollNone;
                }
            }

            if (self.scrollType != CollectionViewAutoScrollNone) {
                if (!self.displayLink) {
                    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkAction:)];
                    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
                }
            } else {
                [self.displayLink invalidate];
                self.displayLink = nil;
            }
            self.longPressGesLastLocation = [ges locationInView:self.collectionView];
        }
            break;
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:
        case UIGestureRecognizerStateFailed:
        {
            [self.displayLink invalidate];
            self.displayLink = nil;
            self.scrollType = CollectionViewAutoScrollNone;
            [UIView animateWithDuration:kComeUpAnimationDuration animations:^{
                CGRect frame = cell.frame;
                self.shootImageView.frame = frame;
            } completion:^(BOOL finished) {
                [self.shootImageView removeFromSuperview];
                cell.hidden = NO;
            }];
        }
            break;
        default:
            break;
    }
}

计时器执行方法:


#pragma mark -
#pragma mark -- -- -- -- -- - Event Response - -- -- -- -- --
- (void)displayLinkAction:(CADisplayLink *)link {
    // 滚倒底了已经
    if (self.scrollType == CollectionViewAutoScrollUp && self.collectionView.contentOffset.y + self.collectionView.frame.size.height > self.collectionView.contentSize.height) {
        return;
    }
    
    // 滚到顶了
    if (self.scrollType == CollectionViewAutoScrollDown && self.collectionView.contentOffset.y < 0) {
        return;
    }
    
    if (self.isMoveing) {
        return;
    }
    
    CGFloat increaseValue;
    
    if (self.scrollType == CollectionViewAutoScrollUp) {
        // 让collectionView刚好可以滚到底
        increaseValue = MIN(kAutoScrollSpeed, self.collectionView.contentSize.height - self.collectionView.frame.size.height - self.collectionView.contentOffset.y);
    } else {
        increaseValue = MAX(-kAutoScrollSpeed, -self.collectionView.contentOffset.y);
    }
    
    CGRect frame = self.shootImageView.frame;
    frame.origin.y += increaseValue;
    self.shootImageView.frame = frame;
    
    CGPoint point = self.longPressGesLastLocation;
    point.y += increaseValue;
    self.longPressGesLastLocation = point;

    CGPoint offset = self.collectionView.contentOffset;
    offset.y += increaseValue;
    [self.collectionView setContentOffset:offset];

    /// TODO: 优化顶部的交换
    [self handlerMoveItemAction];
}

执行交换

- (void)handlerMoveItemAction {
    if (!self.isMoveing) {
        // 取到当前cell附近的cell, 判断是否可以交换
        BOOL shouldMove = NO;
        NSIndexPath *preferIndexPath;
        CollectionViewCell *preferCell;
        preferIndexPath = [NSIndexPath indexPathForItem:self.currentIndexPath.item + 1 inSection:self.currentIndexPath.section];
        preferCell = (CollectionViewCell *)[self.collectionView cellForItemAtIndexPath:preferIndexPath];

        // 间距小于`kCanMoveDistance`开始交换
        if (fabs(preferCell.frame.origin.y - self.shootImageView.frame.origin.y) < kCanMoveDistance) {
            shouldMove = YES;
        } else {
            preferIndexPath = [NSIndexPath indexPathForItem:self.currentIndexPath.item - 1 inSection:self.currentIndexPath.section];
            preferCell = (CollectionViewCell *)[self.collectionView cellForItemAtIndexPath:preferIndexPath];
            if (fabs(preferCell.frame.origin.y - self.shootImageView.frame.origin.y) < kCanMoveDistance) {
                shouldMove = YES;
            } else {
                return;
            }
        }
    
        if (shouldMove && preferCell && preferIndexPath) {
            [self.collectionView performBatchUpdates:^{
                self.isMoveing = YES;
                [self.collectionView moveItemAtIndexPath:self.currentIndexPath toIndexPath:preferIndexPath];
                
                if ([self.delagate respondsToSelector:@selector(collectionViewFlowLayout:moveItemAtIndexPath:toIndexPath:)]) {
                    // 通知vc交换数据源
                    [self.delagate collectionViewFlowLayout:self moveItemAtIndexPath:self.currentIndexPath toIndexPath:preferIndexPath];
                }
                // 完成之后更新transform
                self.shootImageView.layer.transform = CATransform3DMakeTranslation(0, 0, preferIndexPath.row * 2 + 1);
                // 这个地方需要重新设置层级,更改transform无用?
                if (preferIndexPath.item > self.currentIndexPath.item) {
                    [self.collectionView insertSubview:self.currentCell aboveSubview:preferCell];
                } else {
                    [self.collectionView insertSubview:preferCell aboveSubview:self.currentCell];
                }
                self.currentIndexPath = preferIndexPath;
                if (self.scrollType == CollectionViewAutoScrollDown) {
                    // 头部有很多的cell,交换太快会闪屏,需要有间隔
                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                        self.isMoveing = NO;
                    });
                } else {
                    self.isMoveing = NO;
                }
            } completion:^(BOOL finished) {

            }];
        }
    }
}


对cell进行截图:

- (UIImage *)screenshotWithView:(UIView *)view {
    UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, [UIScreen mainScreen].scale);
    [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

效果:

3.gif

可以看到滚动时向下交换还是很流畅的,但是向上交换是第一个cell会闪一下!

第五步: 点击cell打开

我们只需要重新设置可见范围内的cellUICollectionViewLayoutAttributes,然后调用-(void)invalidateLayout;

CollectionViewFlowLayout.m

#define kExpandBottomHeight 50 // 展开cell的bottom距离屏幕bottom的距离,称之为footer吧
#define kExpandBottomFirstCellMarginOfTop 10 // footer里面第一个cell距离顶部的距离
#define kExpandBottomCellSpace 10 // footer里面第cell的间距

添加展开时候的布局属性:

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSInteger rows = [self.collectionView numberOfItemsInSection:0];
    CGFloat offsetY = self.collectionView.contentOffset.y;
    UIEdgeInsets inset = self.collectionView.contentInset;
    NSMutableArray *attrs = [NSMutableArray arrayWithCapacity:0];\
    // 与rect相交的最大最小item值
    int minRow = MAX(0, floor((offsetY)  / kCellSpace));
    int maxRow = MIN(ceil((offsetY + self.collectionView.frame.size.height)  / kCellSpace), rows);
    int shrinkCellIndex = 0; // 缩起来cell的下标,0, 1, 2, 3
    for (int row = minRow; row < maxRow; row++) {
        // 顶部只留一个cell
        if (row * kCellSpace >= offsetY - kCellSpace) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:row inSection:0];
            UICollectionViewLayoutAttributes *att = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
            CGRect cellRect = CGRectMake(0, MAX(offsetY, row * kCellSpace), kCollectionViewWidth - inset.right, kCellHeight);
            CGFloat left = (4 - shrinkCellIndex) * 5;
            CGFloat top = shrinkCellIndex * kExpandBottomCellSpace + kExpandBottomFirstCellMarginOfTop;
            if (self.isExpand) {
                if (indexPath.item == self.currentIndexPath.item) {
                    cellRect = CGRectMake(0, offsetY, kCollectionViewWidth - inset.right, kCollectionViewHeight - kExpandBottomHeight);
                    self.currentCellShrinkMarginLeft = left;
                } else {
                    cellRect = CGRectMake(left, offsetY + (kCollectionViewHeight - kExpandBottomHeight) + top, kCollectionViewWidth - inset.right - left * 2, kCellHeight);
                    shrinkCellIndex++;
                    shrinkCellIndex = MIN(shrinkCellIndex, 3);
                }
            } else {
                if (offsetY < -inset.top) {
                    // 0.25是相对于offsetY的偏移比例,根据需要自行调节
                    cellRect.origin.y = att.indexPath.item * kCellSpace - fabs(offsetY + inset.top) + fabs(offsetY + inset.top) * att.indexPath.item * 0.25;
                }
            }
            att.frame = cellRect;
            att.center = CGPointMake(CGRectGetMinX(cellRect) + CGRectGetWidth(cellRect) / 2, CGRectGetMinY(cellRect) + CGRectGetHeight(cellRect) / 2);
            // 因为我们的cell有重叠,必须设置zIndex,否则复用时层级会有问题
            att.zIndex = att.indexPath.item * 2;
            att.transform3D = CATransform3DMakeTranslation(0, 0, att.indexPath.item * 2);
            if (CGRectIntersectsRect(cellRect, rect) || CGRectContainsRect(cellRect, rect)) {
                [attrs addObject:att];
            }
        }
    }
    return attrs;
}

点击展开cell:

#pragma mark -
#pragma mark -- -- -- -- -- - ExpandCell - -- -- -- -- --
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    if (!self.isExpand) {
        [self expandCellWithIndexPath:indexPath];
    } else {
        [self closeCellWithoutAnimation];
    }
}

- (void)expandCellWithIndexPath:(NSIndexPath *)indexPath {
    self.currentIndexPath = indexPath;
    self.currentCell = (CollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
    self.currentCell.panGes.enabled = YES;
    self.isExpand = YES;
    
    [self.collectionView performBatchUpdates:^{
        [self invalidateLayout];
    } completion:^(BOOL finished) {
        self.collectionView.scrollEnabled = NO;
    }];

}

- (void)closeCellWithoutAnimation {
    self.isExpand = NO;
    self.currentCell.panGes.enabled = NO;
    [self.collectionView performBatchUpdates:^{
        [self invalidateLayout];
    } completion:^(BOOL finished) {
        self.collectionView.scrollEnabled = YES;
    }];
}

- (void)closeCell {
    // 结束
    UIEdgeInsets inset = self.collectionView.contentInset;
    [UIView animateWithDuration:kComeUpAnimationDuration animations:^{
        self.currentCell.frame = CGRectMake(self.currentCellShrinkMarginLeft, kCollectionViewHeight, kCollectionViewWidth - inset.right - self.currentCellShrinkMarginLeft * 2, kCollectionViewHeight - kExpandBottomHeight);
    }completion:^(BOOL finished) {
        [self closeCellWithoutAnimation];
    }];
}

处理滑动手势:

- (void)collectionViewCell:(CollectionViewCell *)cell handlerPanGesture:(UIPanGestureRecognizer *)ges {
    CGFloat offsetY = self.collectionView.contentOffset.y;
    UIEdgeInsets inset = self.collectionView.contentInset;
    switch (ges.state) {
        case UIGestureRecognizerStateBegan:
        {
            
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            CGPoint translate = [ges translationInView:cell];
            CGPoint velocity = [ges velocityInView:cell];
            if (velocity.y > 1300) {
                [ges setEnabled:NO];
                [self closeCell];
                return;
            }
            CGRect frame = cell.frame;
            CGFloat pecent = (frame.origin.y - offsetY) / (kCollectionViewHeight - kExpandBottomHeight);
            CGFloat left = pecent * self.currentCellShrinkMarginLeft;
            [cell setTransform:CGAffineTransformTranslate(cell.transform, 0, translate.y)];
            frame = CGRectMake(left, frame.origin.y + translate.y, kCollectionViewWidth - inset.right - left * 2,kCollectionViewHeight - kExpandBottomHeight );
            cell.frame = frame;
            [ges setTranslation:CGPointZero inView:cell];
        }
            break;
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:
        case UIGestureRecognizerStateFailed:
        {
            
            if (cell.frame.origin.y > kCollectionViewHeight / 2 - kExpandBottomHeight / 2) {
                // 结束
                [self closeCell];
            } else {
                // 复位
                [UIView animateWithDuration:kComeUpAnimationDuration animations:^{
                    cell.frame = CGRectMake(0, offsetY, kCollectionViewWidth - inset.right, kCollectionViewHeight - kExpandBottomHeight);
                }];
            }
        }
            break;
        default:
            break;
    }
}

效果如下:

4.gif

但是展开的时候cell先全部靠左,再执行动画,比较奇怪,暂时还解决不掉,等想到了再更新吧!

文中如果有什么错误, 或者有什么好的想法,都可以留言! 我会更新!

转载请注明出处!
//www.greatytc.com/p/b0fa1daa8665

GitHub:
https://github.com/TactBoy/ios-spring-shrink-move-expand-collectionView

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容