可拖拽重排的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

你可能感兴趣的:(可拖拽重排的CollectionView)