UICollectionView詳解之自定義布局

想看UICollectionView基礎(chǔ)使用的可以先看我的另一篇文章。這篇主寫關(guān)于UIollectionViewLayout自定義布局的一些常用方法,瀑布流布局的自定義,包括頭尾試圖的添加,插入刪除動(dòng)畫,還有9.0后移動(dòng)動(dòng)態(tài)布局的使用。

UICollectionViewLayout自定義常用的幾個(gè)方法

//預(yù)布局方法 所有的布局應(yīng)該寫在這里面
- (void)prepareLayout

//此方法應(yīng)該返回當(dāng)前屏幕正在顯示的視圖(cell 頭尾視圖)的布局屬性集合(UICollectionViewLayoutAttributes 對(duì)象集合)
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

//根據(jù)indexPath去對(duì)應(yīng)的UICollectionViewLayoutAttributes  這個(gè)是取值的,要重寫,在移動(dòng)刪除的時(shí)候系統(tǒng)會(huì)調(diào)用改方法重新去UICollectionViewLayoutAttributes然后布局
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath

//返回當(dāng)前的ContentSize
- (CGSize)collectionViewContentSize
//是否重新布局 
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

//這4個(gè)方法用來處理插入、刪除和移動(dòng)cell時(shí)的一些動(dòng)畫 瀑布流代碼詳解
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
- (UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
- (void)finalizeCollectionViewUpdates
//9.0之后處理移動(dòng)相關(guān)
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForInteractivelyMovingItems:(NSArray<NSIndexPath *> *)targetIndexPaths withTargetPosition:(CGPoint)targetPosition previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths previousPosition:(CGPoint)previousPosition NS_AVAILABLE_IOS(9_0)
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:(NSArray<NSIndexPath *> *)indexPaths previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths movementCancelled:(BOOL)movementCancelled NS_AVAILABLE_IOS(9_0)

瀑布流布局詳解,先貼代碼

.h文件

#import <UIKit/UIKit.h>

UIKIT_EXTERN NSString *const AC_UICollectionElementKindSectionHeader;
UIKIT_EXTERN NSString *const AC_UICollectionElementKindSectionFooter;

@class AC_WaterCollectionViewLayout;
@protocol AC_WaterCollectionViewLayoutDelegate <NSObject>

//代理取cell 的高
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(AC_WaterCollectionViewLayout *)layout heightOfItemAtIndexPath:(NSIndexPath *)indexPath itemWidth:(CGFloat)itemWidth;

//處理移動(dòng)相關(guān)的數(shù)據(jù)源
- (void)moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath;

@end

@interface AC_WaterCollectionViewLayout : UICollectionViewLayout

@property (assign, nonatomic) NSInteger numberOfColumns;//瀑布流有列
@property (assign, nonatomic) CGFloat cellDistance;//cell之間的間距
@property (assign, nonatomic) CGFloat topAndBottomDustance;//cell 到頂部 底部的間距
@property (assign, nonatomic) CGFloat headerViewHeight;//頭視圖的高度
@property (assign, nonatomic) CGFloat footViewHeight;//尾視圖的高度

@property(nonatomic, weak) id<AC_WaterCollectionViewLayoutDelegate> delegate;

@end

.h文件沒有太多東西,看注釋應(yīng)該都清楚。跟UICollectionViewFlowLayout不同的是沒有方向設(shè)置,因?yàn)槠俨剂鳈M向基本少見,所以所以頭尾視圖也由CGSize改成CGFloat,

.m文件

#import "AC_WaterCollectionViewLayout.h"

NSString *const AC_UICollectionElementKindSectionHeader = @"AC_HeadView";
NSString *const AC_UICollectionElementKindSectionFooter = @"AC_FootView";

@interface AC_WaterCollectionViewLayout()

@property (strong, nonatomic) NSMutableDictionary *cellLayoutInfo;//保存cell的布局
@property (strong, nonatomic) NSMutableDictionary *headLayoutInfo;//保存頭視圖的布局
@property (strong, nonatomic) NSMutableDictionary *footLayoutInfo;//保存尾視圖的布局

@property (assign, nonatomic) CGFloat startY;//記錄開始的Y
@property (strong, nonatomic) NSMutableDictionary *maxYForColumn;//記錄瀑布流每列最下面那個(gè)cell的底部y值
@property (strong, nonatomic) NSMutableArray *shouldanimationArr;//記錄需要添加動(dòng)畫的NSIndexPath


@end

@implementation AC_WaterCollectionViewLayout

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.numberOfColumns = 3;
        self.topAndBottomDustance = 10;
        self.cellDistance = 10;
        _headerViewHeight = 0;
        _footViewHeight = 0;
        self.startY = 0;
        self.maxYForColumn = [NSMutableDictionary dictionary];
        self.shouldanimationArr = [NSMutableArray array];
        self.cellLayoutInfo = [NSMutableDictionary dictionary];
        self.headLayoutInfo = [NSMutableDictionary dictionary];
        self.footLayoutInfo = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)prepareLayout
{
    [super prepareLayout];
    
    //重新布局需要清空
    [self.cellLayoutInfo removeAllObjects];
    [self.headLayoutInfo removeAllObjects];
    [self.footLayoutInfo removeAllObjects];
    [self.maxYForColumn removeAllObjects];
    self.startY = 0;
    
    
    CGFloat viewWidth = self.collectionView.frame.size.width;
    //代理里面只取了高度,所以cell的寬度有列數(shù)還有cell的間距計(jì)算出來
    CGFloat itemWidth = (viewWidth - self.cellDistance*(self.numberOfColumns + 1))/self.numberOfColumns;
    
    //取有多少個(gè)section
    NSInteger sectionsCount = [self.collectionView numberOfSections];
    
    for (NSInteger section = 0; section < sectionsCount; section++) {
        //存儲(chǔ)headerView屬性
        NSIndexPath *supplementaryViewIndexPath = [NSIndexPath indexPathForRow:0 inSection:section];
        //頭視圖的高度不為0并且根據(jù)代理方法能取到對(duì)應(yīng)的頭視圖的時(shí)候,添加對(duì)應(yīng)頭視圖的布局對(duì)象
        if (_headerViewHeight>0 && [self.collectionView.dataSource respondsToSelector:@selector(collectionView: viewForSupplementaryElementOfKind: atIndexPath:)]) {
            
            UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:AC_UICollectionElementKindSectionHeader withIndexPath:supplementaryViewIndexPath];
            //設(shè)置frame
            attribute.frame = CGRectMake(0, self.startY, self.collectionView.frame.size.width, _headerViewHeight);
            //保存布局對(duì)象
            self.headLayoutInfo[supplementaryViewIndexPath] = attribute;
            //設(shè)置下個(gè)布局對(duì)象的開始Y值
            self.startY = self.startY + _headerViewHeight + _topAndBottomDustance;
        }else{
            //沒有頭視圖的時(shí)候,也要設(shè)置section的第一排cell到頂部的距離
            self.startY += _topAndBottomDustance;
        }
        
        //將Section第一排cell的frame的Y值進(jìn)行設(shè)置
        for (int i = 0; i < _numberOfColumns; i++) {
            self.maxYForColumn[@(i)] = @(self.startY);
        }
        
        
        //計(jì)算cell的布局
        //取出section有多少個(gè)row
        NSInteger rowsCount = [self.collectionView numberOfItemsInSection:section];
        //分別計(jì)算設(shè)置每個(gè)cell的布局對(duì)象
        for (NSInteger row = 0; row < rowsCount; row++) {
            NSIndexPath *cellIndePath =[NSIndexPath indexPathForItem:row inSection:section];
            UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:cellIndePath];
            
            //計(jì)算當(dāng)前的cell加到哪一列(瀑布流是加載到最短的一列)
            CGFloat y = [self.maxYForColumn[@(0)] floatValue];
            NSInteger currentRow = 0;
            for (int i = 1; i < _numberOfColumns; i++) {
                if ([self.maxYForColumn[@(i)] floatValue] < y) {
                    y = [self.maxYForColumn[@(i)] floatValue];
                    currentRow = i;
                }
            }
            //計(jì)算x值
            CGFloat x = self.cellDistance + (self.cellDistance + itemWidth)*currentRow;
            //根據(jù)代理去當(dāng)前cell的高度  因?yàn)楫?dāng)前是采用通過列數(shù)計(jì)算的寬度,高度根據(jù)圖片的原始寬高比進(jìn)行設(shè)置的
            CGFloat height = [(id<AC_WaterCollectionViewLayoutDelegate>)self.delegate collectionView:self.collectionView layout:self heightOfItemAtIndexPath:cellIndePath itemWidth:itemWidth];
            //設(shè)置當(dāng)前cell布局對(duì)象的frame
            attribute.frame = CGRectMake(x, y, itemWidth, height);
            //重新設(shè)置當(dāng)前列的Y值
            y = y + self.cellDistance + height;
            self.maxYForColumn[@(currentRow)] = @(y);
            //保留cell的布局對(duì)象
            self.cellLayoutInfo[cellIndePath] = attribute;
            
            //當(dāng)是section的最后一個(gè)cell是,取出最后一排cell的底部Y值   設(shè)置startY 決定下個(gè)視圖對(duì)象的起始Y值
            if (row == rowsCount -1) {
                CGFloat maxY = [self.maxYForColumn[@(0)] floatValue];
                for (int i = 1; i < _numberOfColumns; i++) {
                    if ([self.maxYForColumn[@(i)] floatValue] > maxY) {
                        NSLog(@"%f", [self.maxYForColumn[@(i)] floatValue]);
                        maxY = [self.maxYForColumn[@(i)] floatValue];
                    }
                }
                self.startY = maxY - self.cellDistance + self.topAndBottomDustance;
            }
        }
        
        
        //存儲(chǔ)footView屬性
        //尾視圖的高度不為0并且根據(jù)代理方法能取到對(duì)應(yīng)的尾視圖的時(shí)候,添加對(duì)應(yīng)尾視圖的布局對(duì)象
        if (_footViewHeight>0 && [self.collectionView.dataSource respondsToSelector:@selector(collectionView: viewForSupplementaryElementOfKind: atIndexPath:)]) {
            
            UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:AC_UICollectionElementKindSectionFooter withIndexPath:supplementaryViewIndexPath];
            
            attribute.frame = CGRectMake(0, self.startY, self.collectionView.frame.size.width, _footViewHeight);
            self.footLayoutInfo[supplementaryViewIndexPath] = attribute;
            self.startY = self.startY + _footViewHeight;
        }
        
    }
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *allAttributes = [NSMutableArray array];
    
    //添加當(dāng)前屏幕可見的cell的布局
    [self.cellLayoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attribute, BOOL *stop) {
        if (CGRectIntersectsRect(rect, attribute.frame)) {
            [allAttributes addObject:attribute];
        }
    }];
    
    //添加當(dāng)前屏幕可見的頭視圖的布局
    [self.headLayoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attribute, BOOL *stop) {
        if (CGRectIntersectsRect(rect, attribute.frame)) {
            [allAttributes addObject:attribute];
        }
    }];
    
    //添加當(dāng)前屏幕可見的尾部的布局
    [self.footLayoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attribute, BOOL *stop) {
        if (CGRectIntersectsRect(rect, attribute.frame)) {
            [allAttributes addObject:attribute];
        }
    }];
    
    return allAttributes;
}

//插入cell的時(shí)候系統(tǒng)會(huì)調(diào)用改方法
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attribute = self.cellLayoutInfo[indexPath];
    return attribute;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attribute = nil;
    if ([elementKind isEqualToString:AC_UICollectionElementKindSectionHeader]) {
        attribute = self.headLayoutInfo[indexPath];
    }else if ([elementKind isEqualToString:AC_UICollectionElementKindSectionFooter]){
        attribute = self.footLayoutInfo[indexPath];
    }
    return attribute;
}

- (CGSize)collectionViewContentSize
{
    return CGSizeMake(self.collectionView.frame.size.width, MAX(self.startY, self.collectionView.frame.size.height));
}

- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
{
    [super prepareForCollectionViewUpdates:updateItems];
    NSMutableArray *indexPaths = [NSMutableArray array];
    for (UICollectionViewUpdateItem *updateItem in updateItems) {
        switch (updateItem.updateAction) {
            case UICollectionUpdateActionInsert:
                [indexPaths addObject:updateItem.indexPathAfterUpdate];
                break;
            case UICollectionUpdateActionDelete:
                [indexPaths addObject:updateItem.indexPathBeforeUpdate];
                break;
            case UICollectionUpdateActionMove:
                //                [indexPaths addObject:updateItem.indexPathBeforeUpdate];
                //                [indexPaths addObject:updateItem.indexPathAfterUpdate];
                break;
            default:
                NSLog(@"unhandled case: %@", updateItem);
                break;
        }
    }
    self.shouldanimationArr = indexPaths;
}

//對(duì)應(yīng)UICollectionViewUpdateItem 的indexPathBeforeUpdate 設(shè)置調(diào)用
- (UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{

    if ([self.shouldanimationArr containsObject:itemIndexPath]) {
        UICollectionViewLayoutAttributes *attr = self.cellLayoutInfo[itemIndexPath];
        
        attr.transform = CGAffineTransformRotate(CGAffineTransformMakeScale(0.2, 0.2), M_PI);
        attr.center = CGPointMake(CGRectGetMidX(self.collectionView.bounds), CGRectGetMaxY(self.collectionView.bounds));
        attr.alpha = 1;
        [self.shouldanimationArr removeObject:itemIndexPath];
        return attr;
    }
    return nil;
}

//對(duì)應(yīng)UICollectionViewUpdateItem 的indexPathAfterUpdate 設(shè)置調(diào)用
- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    if ([self.shouldanimationArr containsObject:itemIndexPath]) {
        UICollectionViewLayoutAttributes *attr = self.cellLayoutInfo[itemIndexPath];
        
        attr.transform = CGAffineTransformRotate(CGAffineTransformMakeScale(2, 2), 0);
//        attr.center = CGPointMake(CGRectGetMidX(self.collectionView.bounds), CGRectGetMaxY(self.collectionView.bounds));
        attr.alpha = 0;
        [self.shouldanimationArr removeObject:itemIndexPath];
        return attr;
    }
    return nil;
}

- (void)finalizeCollectionViewUpdates
{
    self.shouldanimationArr = nil;
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{

    CGRect oldBounds = self.collectionView.bounds;
    if (!CGSizeEqualToSize(oldBounds.size, newBounds.size)) {
        return YES;
    }
    return NO;
    
//    
//    return YES;
}

//移動(dòng)相關(guān)
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForInteractivelyMovingItems:(NSArray<NSIndexPath *> *)targetIndexPaths withTargetPosition:(CGPoint)targetPosition previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths previousPosition:(CGPoint)previousPosition NS_AVAILABLE_IOS(9_0)
{
    UICollectionViewLayoutInvalidationContext *context = [super invalidationContextForInteractivelyMovingItems:targetIndexPaths withTargetPosition:targetPosition previousIndexPaths:previousIndexPaths previousPosition:previousPosition];

    if([self.delegate respondsToSelector:@selector(moveItemAtIndexPath: toIndexPath:)]){
        [self.delegate moveItemAtIndexPath:previousIndexPaths[0] toIndexPath:targetIndexPaths[0]];
    }
    return context;
}

- (UICollectionViewLayoutInvalidationContext *)invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:(NSArray<NSIndexPath *> *)indexPaths previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths movementCancelled:(BOOL)movementCancelled NS_AVAILABLE_IOS(9_0)
{
    UICollectionViewLayoutInvalidationContext *context = [super invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:indexPaths previousIndexPaths:previousIndexPaths movementCancelled:movementCancelled];
    
    if(!movementCancelled){

    }
    return context;
}

@end
  • (void)prepareLayout
    方法里面的布局注釋我應(yīng)該寫的很詳細(xì)了,看不懂的多看2遍。這里我再詳細(xì)說一下startY跟maxYForColumn這兩個(gè)屬性。startY值主要處理下一個(gè)視圖對(duì)象的Y值。maxYForColumn保存當(dāng)前已經(jīng)計(jì)算了的最下一列的cell的bottom值。布局cell的時(shí)候,cell的Y值
    取maxYForColumn里面的最小值。當(dāng)section里面的cell全部布局完的時(shí)候,接下來布局尾視圖的時(shí)候,startY應(yīng)該取maxYForColumn里面的最大值。

  • (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
    這個(gè)方法需要返回當(dāng)前界面可見的視圖的布局對(duì)象集合,很多線性布局的效果都是在這個(gè)方法里面處理,在下面的UIollectionViewFlowLayout會(huì)有一些常見效果的處理代碼。

  • (void)prepareForCollectionViewUpdates:(NSArray )updateItems
    當(dāng)調(diào)用插入、刪除和移動(dòng)相關(guān)的api的時(shí)候回調(diào)用該方法(對(duì)照上面的代碼看)其中的indexPathBeforeUpdate跟indexPathAfterUpdat分別對(duì)應(yīng)
    (UICollectionViewLayoutAttributes
    )initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath )itemIndexPath
    (UICollectionViewLayoutAttributes
    )finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
    處理相對(duì)應(yīng)的UICollectionViewLayoutAttributes屬性變動(dòng),我的代碼中插入是添加的indexPathAfterUpdate,刪除是添加的indexPathBeforeUpdate。

關(guān)于移動(dòng)相關(guān)的,系統(tǒng)提供的只能9.0之后,如果想9.0之前使用必須的自定義,可以查看這篇文章可拖拽重排的CollectionView自己研究。添加移動(dòng)相關(guān)的代碼在ctr處理,回調(diào)也在ctr里面處理,先貼上代碼

//添加cell長(zhǎng)按手勢(shì)
    UILongPressGestureRecognizer *longGest = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longGest:)];
    [self.waterCollectionView addGestureRecognizer:longGest];

//對(duì)應(yīng)的action
- (void)longGest:(UILongPressGestureRecognizer *)gest
{
    switch (gest.state) {
        case UIGestureRecognizerStateBegan:
        {
            NSIndexPath *touchIndexPath = [self.waterCollectionView indexPathForItemAtPoint:[gest locationInView:self.waterCollectionView]];
            if (touchIndexPath) {
                [self.waterCollectionView beginInteractiveMovementForItemAtIndexPath:touchIndexPath];
            }else{
                break;
            }
            
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            [self.waterCollectionView updateInteractiveMovementTargetPosition:[gest locationInView:gest.view]];
        }
            break;
        case UIGestureRecognizerStateEnded:
        {
            [self.waterCollectionView endInteractiveMovement];
        }
            break;
        default:
            break;
    }
}

//移動(dòng)對(duì)應(yīng)的回調(diào)
//系統(tǒng)的 
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0)
{
    
//    if(sourceIndexPath.row != destinationIndexPath.row){
//        NSString *value = self.imageArr[sourceIndexPath.row] ;
//        [self.imageArr removeObjectAtIndex:sourceIndexPath.row];
//        [self.imageArr insertObject:value atIndex:destinationIndexPath.row];
//        NSLog(@"from:%ld      to:%ld", sourceIndexPath.row, destinationIndexPath.row);
//    }
   
}
//自定義的回調(diào)
- (void)moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath
{
    if(sourceIndexPath.row != destinationIndexPath.row){
        NSString *value = self.imageArr[sourceIndexPath.row];
        [self.imageArr removeObjectAtIndex:sourceIndexPath.row];
        [self.imageArr insertObject:value atIndex:destinationIndexPath.row];
        NSLog(@"from:%ld      to:%ld", sourceIndexPath.row, destinationIndexPath.row);
    }
}

當(dāng)長(zhǎng)按后移動(dòng)手指的時(shí)候系統(tǒng)會(huì)一直調(diào)用
invalidationContextForInteractivelyMovingItems:withTargetPosition:previousIndexPaths: previousPosition:因?yàn)槠俨剂鞯拿總€(gè)cell的frame大小不相同所以要通過代理方法不斷的更新數(shù)據(jù)源的順序,然后系統(tǒng)不斷調(diào)用prepareLayout方法進(jìn)行重新布局,之前我是采用的系統(tǒng)提供的代理collectionView moveItemAtIndexPath: toIndexPath:來處理數(shù)據(jù)源的,但是發(fā)現(xiàn)只有布局的時(shí)候是正常的,然是松開手指后,從新加載數(shù)據(jù)發(fā)現(xiàn)亂了,然后打印數(shù)據(jù)源。發(fā)現(xiàn)數(shù)據(jù)源的順序并沒有改變,還是之前的順序。
后來發(fā)現(xiàn)問題出現(xiàn)在當(dāng)移動(dòng)手勢(shì)結(jié)束的時(shí)候調(diào)用的方法 [self.waterCollectionView endInteractiveMovement];
以下xcode對(duì)該方法的介紹
Ends interactive movement tracking and moves the target item to its new location.
Call this method upon the successful completion of movement tracking for a item. For example, when using a gesture recognizer to track user interactions, call this method upon the successful completion of the gesture. Calling this method lets the collection view know to end tracking and move the item to its new location permanently. The collection view responds by calling the collectionView:moveItemAtIndexPath:toIndexPath: method of its data source to ensure that your data structures are updated.
也就是說當(dāng)手勢(shì)結(jié)束的時(shí)候系統(tǒng)會(huì)掉一次collectionView:moveItemAtIndexPath:toIndexPath:,該操作導(dǎo)致移動(dòng)的時(shí)候進(jìn)行的變換的順序又變回來了,所以只好自己寫了一個(gè)代理方法處理數(shù)據(jù)源,沒管系統(tǒng)的回調(diào)。

運(yùn)行效果

瀑布流.gif

相關(guān)代碼下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,401評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,069評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,635評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,128評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,365評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,881評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,733評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,935評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,172評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評(píng)論 1 282
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,595評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,908評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容