可拖拽重排的CollectionView

可拖拽重排的CollectionView
http://www.jianshu.com/p/8f0153ce17f9

拖拽重排的CollectionView
https://github.com/henrytkirk/HTKDragAndDropCollectionViewLayout
http://www.cnblogs.com/YouXianMing/p/4156559.html

draggedIndexPath // 要拖动的cell的索引

finalIndexPath // 最终要插入的索引

在prepareLayout中,构建 itemArray 和 itemDictionary;itemArray是按顺序保存cell对应的LayoutAttributes;itemDictionary保存是,IndexPath(key):LayoutAttributes(value);

但是UICollectionViewLayout的,代理方法不怎么熟悉;

好像自己的逻辑还没有理清楚;

HTKDragAndDropCollectionViewController;

//
//  HTKDragAndDropCollectionViewLayout.m
//  HTKDragAndDropCollectionView
//
//  Created by Henry T Kirk on 11/9/14.
//  Copyright (c) 2014 Henry T. Kirk (http://www.henrytkirk.info)
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

#import "HTKDragAndDropCollectionViewLayout.h"
#import "HTKDragAndDropCollectionViewLayoutConstants.h"

@interface HTKDragAndDropCollectionViewLayout ()

/**
 * Our item array that holds the "sorted" items in the collectionView.
 * this array is re-ordered while user is dragging a cell. Our layout
 * uses this to then show the items in that sorted order.
 */
@property (nonatomic, strong) NSMutableArray *itemArray;

/**
 * Our dictionary of layout attributes where the indexPath is the key. Used
 * to retrieve the layout attributes for a particular indexPath since
 * it may be different than the order in itemArray.
 */
@property (nonatomic, strong) NSMutableDictionary *itemDictionary;

/**
 * Returns number of items that will fit per row based on fixed
 * itemSize.
 */
@property (readonly, nonatomic) NSInteger numberOfItemsPerRow;

/**
 * Resets the frames based on new position in the itemArray. Will
 * loop over all items in the new sorted order and lay them out.
 */
- (void)resetLayoutFrames;

/**
 * Applys the dragging attributes to the attributes passed. Will
 * apply dragging state if the attributes are being dragged. If not, will
 * apply default state.
 */
- (void)applyDragAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes;

/**
 * Inserts the dragged item into the indexPath passed. Will reorder
 * the items.
 */
- (void)insertDraggedItemAtIndexPath:(NSIndexPath *)intersectPath;

/**
 * Helper to determine what indexPath of the item is below the point
 * passed. Used to identify what item is below the item being dragged.
 */
- (NSIndexPath *)indexPathBelowDraggedItemAtPoint:(CGPoint)point;

/**
 * Creates and inserts layout attributes for indexPath provided. Used
 * for insertions into the collectionView.
 */
- (void)insertItemAtIndexPath:(NSIndexPath *)indexPath;

@end

@implementation HTKDragAndDropCollectionViewLayout

- (instancetype)init {
    self = [super init];
    if (self) {
        _itemArray = [NSMutableArray array];
        _itemDictionary = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)invalidateLayoutWithContext:(UICollectionViewLayoutInvalidationContext *)context {
    [super invalidateLayoutWithContext:context];
    // reset so we re-calc entire layout again
    if (context.invalidateEverything) {
        [self.itemArray removeAllObjects];
    }
}

- (void)prepareLayout {
    [super prepareLayout];

    // Make sure we have item size set.
    if (CGSizeEqualToSize(self.itemSize, CGSizeZero)) {
        return;
    }
    
    // If we already have our model, don't build it.
    if (self.itemArray.count > 0) {
        return;
    }
    
    // Start to build our array and dictionary of items
    self.draggedIndexPath = nil;
    self.finalIndexPath = nil;
    self.draggedCellFrame = CGRectZero;
    [self.itemArray removeAllObjects];
    [self.itemDictionary removeAllObjects];
    
    // setup values
    CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.bounds) - self.sectionInset.right;
    CGFloat xValue = self.sectionInset.left;
    CGFloat yValue = self.sectionInset.top;
    NSInteger sectionCount = [self.collectionView numberOfSections];
    
    // Now build our items array/dictionary
    // 构建 itemArray 和 itemDictionary;
    for (NSInteger section = 0; section < sectionCount; section ++) {
        NSInteger itemCount = [self.collectionView numberOfItemsInSection:section];
        for (NSInteger item = 0; item < itemCount; item ++) {
            
            // Check our xvalue
            if ((xValue + self.itemSize.width) > collectionViewWidth)   // 换行了
            {
                // reset our x, increment our y.
                xValue = self.sectionInset.left;
                yValue += self.itemSize.height + self.lineSpacing;
            }
            
            // Create IndexPath
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
            UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
            // Create frame
            attributes.frame = CGRectMake(xValue, yValue, self.itemSize.width, self.itemSize.height);
            
            // add to our dict
            self.itemDictionary[indexPath] = attributes;
            [self.itemArray addObject:attributes];
            
            // Increment our x value
            xValue += self.itemSize.width + self.minimumInteritemSpacing;
        }
    }
}

- (CGSize)collectionViewContentSize {
    
    CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.bounds);
    // Determine number of sections
    // 决定sections的数量
    NSInteger totalItems = 0;
    for (NSInteger i = 0; i < [self.collectionView numberOfSections]; i++) {
        totalItems += [self.collectionView numberOfItemsInSection:i];
    }
    // When the totalItems % 2 == 1, do this.
    if (totalItems % 2 == 1) {
        totalItems = totalItems + 1;
    }
    // Determine how many rows we will have
    // row的数量; 总共的item cell / 每行itemcell的数量;
    NSInteger rows = ceil((CGFloat)totalItems / self.numberOfItemsPerRow);
    
    // Determine height of collectionView
    // collectionView 的高度
    CGFloat height = (rows * (self.itemSize.height + self.lineSpacing)) + self.sectionInset.top + self.sectionInset.bottom;
    
    return CGSizeMake(collectionViewWidth, height);
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSMutableArray *elementArray = [NSMutableArray array];
    
    // Loop over our items and find elements that
    // intersect the rect passed.
    [[self.itemArray copy] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        UICollectionViewLayoutAttributes *attribute = (UICollectionViewLayoutAttributes *)obj;
        if (CGRectIntersectsRect(attribute.frame, rect)) {
            [self applyDragAttributes:attribute];
            [elementArray addObject:attribute];
        }
    }];

    return elementArray;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes *layoutAttributes = self.itemDictionary[indexPath];
    if (!layoutAttributes) {
        layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    }
    [self applyDragAttributes:layoutAttributes];
    
    return layoutAttributes;
}

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
    
    UICollectionViewLayoutAttributes *attributes = [self.itemDictionary[itemIndexPath] copy];
    return attributes;
}

- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
    
    UICollectionViewLayoutAttributes *attributes = [self.itemDictionary[itemIndexPath] copy];
    return attributes;
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    if (!CGSizeEqualToSize(self.collectionView.bounds.size, newBounds.size)) {
        // reset so we re-calc entire layout again
        [self.itemArray removeAllObjects];
        return YES;
    }
    return NO;
}

- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems {
    [updateItems enumerateObjectsUsingBlock:^(UICollectionViewUpdateItem *updateItem, NSUInteger idx, BOOL *stop) {
        switch (updateItem.updateAction) {
            case UICollectionUpdateActionInsert: {
                // insert new item
                [self insertItemAtIndexPath:updateItem.indexPathAfterUpdate];
                break;
            }
            case UICollectionUpdateActionDelete:
            case UICollectionUpdateActionMove:
            case UICollectionUpdateActionNone:
            case UICollectionUpdateActionReload:
            default:
                break;
        }
    }];
}

#pragma mark - Getters

/**
 * collectionView每行,itemcell的数量
 *
 */
- (NSInteger)numberOfItemsPerRow {
    // Determine how many items we can fit per row
    CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.bounds) - self.sectionInset.right - self.sectionInset.left;
    NSInteger numberOfItems = collectionViewWidth / (self.itemSize.width + _minimumInteritemSpacing);
    return numberOfItems;
}

/**
 * collectionView,返回minimumInteritemSpacing;
 *
 */
- (CGFloat)minimumInteritemSpacing {
    // return minimum item spacing
    CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.bounds) - self.sectionInset.right - self.sectionInset.left;
    CGFloat actualItemSpacing = MAX(_minimumInteritemSpacing, collectionViewWidth - (self.numberOfItemsPerRow * self.itemSize.width));
    return actualItemSpacing / self.numberOfItemsPerRow;
}

#pragma mark - Drag and Drop methods

- (void)resetDragging {
    
    // Set our dragged cell back to it's "home" frame
    UICollectionViewLayoutAttributes *attributes = self.itemDictionary[self.draggedIndexPath];
    attributes.frame = self.draggedCellFrame;

    self.finalIndexPath = nil;
    self.draggedIndexPath = nil;
    self.draggedCellFrame = CGRectZero;

    // Put the cell back animated.
    [UIView animateWithDuration:0.2 animations:^{
        [self invalidateLayout];
    }];
}

/**
 * 重新布局
 *
 */
- (void)resetLayoutFrames {
    
    // Get width of collectionView and adjust by section insets
    CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.bounds) - self.sectionInset.right;
    
    CGFloat xValue = self.sectionInset.left;
    CGFloat yValue = self.sectionInset.top;
    for (NSInteger i = 0; i < self.itemArray.count; i++) {
        
        // Get attributes to work with
        UICollectionViewLayoutAttributes *attributes = self.itemArray[i];

        // Check our xvalue
        // 相当于item cell换行了;
        if ((xValue + self.itemSize.width) > collectionViewWidth) {
            // reset our x, increment our y.
            xValue = self.sectionInset.left;
            yValue += self.itemSize.height + self.lineSpacing;
        }
        
        // Set new frame
        attributes.frame = CGRectMake(xValue, yValue, self.itemSize.width, self.itemSize.height);
        
        // Increment our x value
        xValue += self.itemSize.width + self.minimumInteritemSpacing;
    }
}

/**
 *  设置被拖动cell的属性;
 *
 */
- (void)applyDragAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    if ([layoutAttributes.indexPath isEqual:self.draggedIndexPath]) {
        // Set dragged attributes
        layoutAttributes.center = self.draggedCellCenter;
        layoutAttributes.zIndex = 1024;
        layoutAttributes.alpha = HTKDraggableCellInitialDragAlphaValue;
    } else {
        // Default attributes
        layoutAttributes.zIndex = 0;
        layoutAttributes.alpha = 1.0;
    }
}

- (void)setDraggedCellCenter:(CGPoint)draggedCellCenter {
    _draggedCellCenter = draggedCellCenter;
    [self invalidateLayout];
}

// draggedIndexPath, 设置新的finalIndexPath; 要拖动cell的frame设置为新的frame;
- (void)insertDraggedItemAtIndexPath:(NSIndexPath *)intersectPath {
    // Get attributes to work with
    UICollectionViewLayoutAttributes *draggedAttributes = self.itemDictionary[self.draggedIndexPath];
    UICollectionViewLayoutAttributes *intersectAttributes = self.itemDictionary[intersectPath];
    
    // get index of items
    NSUInteger draggedIndex = [self.itemArray indexOfObject:draggedAttributes];
    NSUInteger intersectIndex = [self.itemArray indexOfObject:intersectAttributes];
    
    // Move item in our array
    // 将draggedIndex从itemArray移除,再插入到intersectIndex(拖动最后要插入的位置)
    [self.itemArray removeObjectAtIndex:draggedIndex];
    [self.itemArray insertObject:draggedAttributes atIndex:intersectIndex];
    
    // Set our new final indexPath
    // 设置新的finalIndexPath; 要拖动cell的frame设置为新的frame;
    self.finalIndexPath = intersectPath;
    self.draggedCellFrame = intersectAttributes.frame;
    
    // relayout frames for items
    [self resetLayoutFrames];
    
    // Animate change
    [UIView animateWithDuration:0.10 animations:^{
        [self invalidateLayout];
    }];
}

- (void)exchangeItemsIfNeeded {
    // Exchange objects if we're touching.
    // 相交cell的索引
    NSIndexPath *intersectPath = [self indexPathBelowDraggedItemAtPoint:self.draggedCellCenter];
    UICollectionViewLayoutAttributes *attributes = self.itemDictionary[intersectPath];
    
    // Create a "hit area" that's 20 pt over the center of the intersected cell center
    CGRect centerBox = CGRectMake(attributes.center.x - HTKDragAndDropCenterTriggerOffset, attributes.center.y - HTKDragAndDropCenterTriggerOffset, HTKDragAndDropCenterTriggerOffset * 2, HTKDragAndDropCenterTriggerOffset * 2);
    // Determine if we need to move items around
    if (intersectPath != nil && ![intersectPath isEqual:self.draggedIndexPath] && CGRectContainsPoint(centerBox, self.draggedCellCenter)) {
        [self insertDraggedItemAtIndexPath:intersectPath];
    }
}

- (BOOL)isDraggingCell {
    return self.draggedIndexPath != nil;
}

#pragma mark - Helper Methods


/**
 *  返回拖动cell,下面要相交cell的索引IndexPath
 *
 */
- (NSIndexPath *)indexPathBelowDraggedItemAtPoint:(CGPoint)point {
        
    __block NSIndexPath *indexPathBelow = nil;
    __weak HTKDragAndDropCollectionViewLayout *weakSelf = self;
    
    [self.collectionView.indexPathsForVisibleItems enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSIndexPath *indexPath = (NSIndexPath *)obj;
        
        // Skip our dragged cell
        if ([self.draggedIndexPath isEqual:indexPath]) {
            return;
        }
        UICollectionViewLayoutAttributes *attribute = weakSelf.itemDictionary[indexPath];
        
        // Create a "hit area" that's 20 pt over the center of the testing cell
        // 创建 类似相交区域,中心点20范围内;
        CGRect centerBox = CGRectMake(attribute.center.x - HTKDragAndDropCenterTriggerOffset, attribute.center.y - HTKDragAndDropCenterTriggerOffset, HTKDragAndDropCenterTriggerOffset * 2, HTKDragAndDropCenterTriggerOffset * 2);
        if (CGRectContainsPoint(centerBox, weakSelf.draggedCellCenter)) {
            indexPathBelow = indexPath;
            *stop = YES;
        }
    }];

    return indexPathBelow;
}

/**
 *  插入新的item cell;
 *
 */
- (void)insertItemAtIndexPath:(NSIndexPath *)indexPath {
    // get attributes of item before this inserted one
    UICollectionViewLayoutAttributes *prevAttributes = self.itemArray[indexPath.row - 1];
    
    // Check our values
    CGFloat xValue = CGRectGetMaxX(prevAttributes.frame) + self.minimumInteritemSpacing;
    CGFloat yValue = CGRectGetMinY(prevAttributes.frame);
    CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.bounds) - self.sectionInset.right;
    if ((xValue + self.itemSize.width) > collectionViewWidth) {
        // reset our x, increment our y.
        xValue = self.sectionInset.left;
        yValue += self.itemSize.height + self.lineSpacing;
    }
    
    // create attributes
    UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
    // Create frame
    attributes.frame = CGRectMake(xValue, yValue, self.itemSize.width, self.itemSize.height);
    
    // add to our dict
    self.itemDictionary[indexPath] = attributes;
    [self.itemArray addObject:attributes];
}

@end

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

推荐阅读更多精彩内容