美文网首页
可拖拽重排的CollectionView

可拖拽重排的CollectionView

作者: kakukeme | 来源:发表于2016-10-31 11:37 被阅读220次

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

          本文链接:https://www.haomeiwen.com/subject/yrqputtx.html