UICollectionView'互动运动'不要让单元格作为目标部分中的最后一个单元格

时间:2017-08-31 09:13:40

标签: drag-and-drop uicollectionview ios9

我正在使用UICollectionView,其最低开发目标为iOS9,并发现可以支持interactive movement的拖放。一切正常但是将单元格作为目标部分中的最后一个单元格而不能正常工作。

注意:我不想要iOS11拖放解决方案,因为我的应用程序的目标是iOS9。在每个部分的末尾创建一个虚拟单元格并不是一个直接的解决方案。

请查看图片以直观地解决问题。

enter image description here

提前致谢。

1 个答案:

答案 0 :(得分:1)

我遇到了同样的问题,最终设法完成了它。我使用Apple文档和多篇关于UICollectionView的博客文章在各部分甚至无效的indexPaths之间构建一个功能齐全的Drag'N Drop功能。

实施默认的Drag'N Drop

首先,您必须禁用Apple提供的自动Drag'N Drop功能,因为您将使用自己的GestureRecognizer。

因此,请在viewDidLoad

中添加此内容
// deactivate default gesture recognizer for drag'n drop
self.installsStandardGestureForInteractiveMovement = NO;

longPressGestureRecognizer添加collectionView,将UIGestureRecognizerDelegate添加到您的viewController协议,并在viewDidLoad中设置代理:

_longPressGestureRecognizer.delegate = self;

您现在必须实施longPressGestureRecognizer操作:

// static variables to manage drag'n drop on invalid spaces
static NSIndexPath * longPressIndexPath = nil;
static CGPoint longPressLocation = (CGPoint) {0, 0};

- (IBAction)longPress:(UILongPressGestureRecognizer *)sender {
    @autoreleasepool {
        static BOOL isDragNDropping = NO;

        longPressLocation = [sender locationInView:self.collectionView];
        longPressIndexPath = [self.collectionView indexPathForItemAtPoint:longPressLocation];


        // get the cell at indexPath (the one you long pressed)
        UICollectionViewCell* cell = nil;            

        if (sender.state == UIGestureRecognizerStateChanged) {
            NSLog(@"%s : STATE CHANGED", __FUNCTION__);
            // Do stuff with cell

            // Update the cell movement if the longPress location changes
            if (isDragNDropping) {

                [self.collectionView updateInteractiveMovementTargetPosition:longPressLocation];

            } else {
            // It's the first longPress movement, start the drag'n drop
                if (longPressIndexPath) {
                    [self.collectionView beginInteractiveMovementForItemAtIndexPath:longPressIndexPath];
                    isDragNDropping = YES;
                }

            }

            return;
        }

        if (sender.state == UIGestureRecognizerStateRecognized) {
            NSLog(@"%s : STATE RECOGNIZED", __FUNCTION__);
            // do stuff with the cell


            // We were long pressing on a cell
            if (isDragNDropping) {

                [self.collectionView endInteractiveMovement];

            }

            isDragNDropping = NO;

            return;
        }

        if (sender.state == UIGestureRecognizerStateBegan) {
            NSLog(@"%s : STATE BEGAN", __FUNCTION__);

            // do stuff with the newly selected cell
            if (longPressIndexPath) {
                cell = [self.collectionView cellForItemAtIndexPath: longPressIndexPath];


                // You can add custom animations on the cell
                [UIView animateWithDuration:0.35f animations:^{
                    [cell setTransform:CGAffineTransformMakeScale(1.2f, 1.2f)];
                    cell.layer.shadowColor = [UIColor blackColor].CGColor;
                    cell.layer.shadowRadius = 6.f;
                    cell.layer.shadowOpacity = 0.6f;
                } completion:^(BOOL finished) {
                }];

            }


            isDragNDropping = NO;

            return;
        }

    }
}

基本上,此时你已经完成了Apple正在做的默认Drag'N Drop。

绕过无效的indexPaths

  • 主要是实现这个回调:

(NSIndexPath *)collectionView:(UICollectionView *)collectionView targetIndexPathForMoveFromItemAtIndexPath:(NSIndexPath *)originalIndexPath toProposedIndexPath:(NSIndexPath *)proposedIndexPath

它允许您绕过建议的indexPath到您选择的一个。

  • 您还必须实现此操作以跟踪重新排序:

(如果您使用CoreData和NSFetchedResultsController作为您的数据源,您需要使用排名来对您的项目进行排序,并且此排名必须在此更改)

-(void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath

  • 现在主要的问题是你想插入一个无效的indexPath(因为它还不存在)。

要解决此问题,您必须保留longPressGestureRecognizer位置和indexPath的记录。我已将这两个变量声明为静态,但ViewController的属性也可以。

这里的关键是在longPressGestureRecognizer位置生成虚拟indexPath,而不是像这样建议的indexPath:

- (NSIndexPath *)collectionView:(UICollectionView *)collectionView targetIndexPathForMoveFromItemAtIndexPath:(NSIndexPath *)originalIndexPath toProposedIndexPath:(NSIndexPath *)proposedIndexPath
{
    NSLog(@"%s", __FUNCTION__);
    NSLog(@"original idxPath : %li - %li | proposed idxPath : %li - %li", originalIndexPath.section, originalIndexPath.item, proposedIndexPath.section, proposedIndexPath.item);

    // The longPressIndexPath is valid, we are drag'n dropping on an valid location, keep the proposedIndexPath
    if (longPressIndexPath != nil) {
        return proposedIndexPath;
    }

    // The longPressIndexPath is nil, which means we are drag'n dropping on a new location (at the end of a section in most cases)

    if (longPressIndexPath == nil) {
        NSLog(@"%s virtualIndexPath", __FUNCTION__);


        // This part is not actually needed, 
        // I have added a way to accept drops on header/footer, and add the item to the corresponding section

        // Build an set with each sectionIndexPaths

        NSMutableSet<NSIndexPath*>* visiblesSectionIndexPaths = [NSMutableSet setWithArray:[self.collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionHeader]];
        [visiblesSectionIndexPaths addObjectsFromArray:[self.collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionFooter]];

        // Find the section containing the gesture location

        // Custom method that return the index of the section containing the given point, -1 if no section were found
        NSInteger sectionIndex = [self sectionIndexContainingPoint:longPressLocation];
        if (sectionIndex > -1) {
            // If so, generate a virtual indexPath, where we can drop this item
            // The management of the indexPath in the dataSource will happen in the moveItemAtIndexPath callback

            // We return a new indexPath 

            int itemIndex = [self.collectionView numberOfItemsInSection:sectionIndex];
            if (itemIndex<0) {
                itemIndex=0;
            }
            // Generate a virtual indexPath
            NSIndexPath * virtualIndexPath;
            virtualIndexPath = [NSIndexPath indexPathForItem:itemIndex inSection:sectionIndex];

            NSLog(@"virtual idxPath : %li - %li", virtualIndexPath.section, virtualIndexPath.item);
            return virtualIndexPath;

        }
        return proposedIndexPath;
    }
}
  • 最后,您实施了moveItemAtIndexPath方法

您必须在备份数据容器中更改项目所属的部分,并调用reloadData更新您的collectionView。这部分完全依赖于你的dataSource,我自己用CoreData和NSFetchedResultsController完成了。

希望这会有所帮助。

N.B。:这是从我自己的实现中提取的大部分代码,我不确定它是否会按原样运行,但是你有关键可以使它滚动。