UICollectionView 在 UILongpress 手势下滚动

UICollectionView scroll when in UILongpress gesture

更新问题。当视图滚动时,我很难重新排序集合视图中的项目。这样做的原因是用于重新排序项目的代码在 UILongpress 手势的 UIGestureRecognizerStateChanged 中。因此,当视图滚动并且用户的手指在屏幕边缘静止时,不会调用 UILongpress 手势中的重新排序代码。如果我只是简单地插入重新订购代码:

// Is destination valid and is it different from source?
            if (indexPath && ![indexPath isEqual:sourceIndexPath]) {

                // ... update data source.
                [self.imageArray exchangeObjectAtIndex:indexPath.row withObjectAtIndex:sourceIndexPath.row];

                // ... move the rows.
                [self.collectionView  moveItemAtIndexPath:sourceIndexPath toIndexPath:indexPath];

                // ... and update source so it is in sync with UI changes.
                sourceIndexPath = indexPath;

进入 autoscrollTimerFired 方法后,项目的索引路径将不再正确,因为集合视图已滚动到用户静态手指下的新位置。

完整代码如下。有什么建议吗?

- (void)longPressGestureRecognized:(id)sender {

    UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
    UIGestureRecognizerState state = longPress.state;

    CGPoint location = [longPress locationInView:self.collectionView];
    NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:location];

    static UIView       *snapshot = nil;        ///< A snapshot of the row user is moving.
    static NSIndexPath  *sourceIndexPath = nil; ///< Initial index path, where gesture begins.

    switch (state) {
        case UIGestureRecognizerStateBegan: {
            if (indexPath) {

                [self.deleteButton removeFromSuperview];
                self.isDeleteActive = NO;

                sourceIndexPath = indexPath;

                UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];

                // Take a snapshot of the selected row using helper method.
                snapshot = [self customSnapshoFromView:cell];

                // Add the snapshot as subview, centered at cell's center...
                __block CGPoint center = cell.center;
                snapshot.center = center;
                snapshot.alpha = 0.0;
                [self.collectionView addSubview:snapshot];
                [UIView animateWithDuration:0.25 animations:^{

                    // Offset for gesture location.
                    center.x = location.x;
                    snapshot.center = center;
                    snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05);
                    snapshot.alpha = 0.98;
                    cell.alpha = 0.0;

                } completion:^(BOOL finished) {

                cell.hidden = YES;

                }];
            }
            break;
        }

        case UIGestureRecognizerStateChanged: {

            CGPoint center = snapshot.center;
            center.x = location.x;
            snapshot.center = center;

            [self maybeAutoscrollForSnapshot:snapshot];

            // Is destination valid and is it different from source?
            if (indexPath && ![indexPath isEqual:sourceIndexPath]) {

                // ... update data source.
                [self.imageArray exchangeObjectAtIndex:indexPath.row withObjectAtIndex:sourceIndexPath.row];

                // ... move the rows.
                [self.collectionView  moveItemAtIndexPath:sourceIndexPath toIndexPath:indexPath];

                // ... and update source so it is in sync with UI changes.
                sourceIndexPath = indexPath;
            }
            break;
        }

        default: {
            // Clean up.

            if(_autoscrollTimer)
            {
                [_autoscrollTimer invalidate]; _autoscrollTimer = nil;
            }

            UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:sourceIndexPath];
            cell.hidden = NO;
            cell.alpha = 0.0;

            [UIView animateWithDuration:0.25 animations:^{

                snapshot.center = cell.center;
                snapshot.transform = CGAffineTransformIdentity;
                snapshot.alpha = 0.0;
                cell.alpha = 1.0;

            } completion:^(BOOL finished) {

                sourceIndexPath = nil;
                [snapshot removeFromSuperview];
                snapshot = nil;

            }];

            break;
        }
    }
}

- (void)maybeAutoscrollForSnapshot:(UIImageView *)snapshot
{
    _autoscrollDistance = 0;

    if (CGRectGetMaxX(snapshot.frame) < self.collectionView.collectionViewLayout.collectionViewContentSize.width)
    {
        // only autoscroll if the content is larger than the view
        if (self.collectionView.collectionViewLayout.collectionViewContentSize.width > self.view.frame.size.width)
        {
            float distanceFromTop = snapshot.center.x - CGRectGetMinX(self.collectionView.bounds);
            float distanceFromBottom = CGRectGetMaxX(self.collectionView.bounds) - snapshot.center.x;

            //NSLog(@"dist from left %f", distanceFromTop);

            if (distanceFromTop < kAutoScrollingThreshold) {
                _autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromTop] * -1; // if scrolling up distance is negative
                //NSLog(@"left dist %f", _autoscrollDistance);
            } else if (distanceFromBottom < kAutoScrollingThreshold) {
                _autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromBottom];
                //NSLog(@"right dist %f", _autoscrollDistance);
            }
        }
    }

    // if no autoscrolling, stop and clear timer
    if (_autoscrollDistance == 0) {
        [_autoscrollTimer invalidate];
        _autoscrollTimer = nil;
    }
    // otherwise create and start timer (if we don't already have a timer going)
    else if (_autoscrollTimer == nil) {
        _autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0)
                                                            target:self
                                                          selector:@selector(autoscrollTimerFired:)
                                                          userInfo:snapshot
                                                           repeats:YES];
    }
}

- (float)autoscrollDistanceForProximityToEdge:(float)proximity {
    // the scroll distance grows as the proximity to the edge decreases, so that moving the thumb
    // further over results in faster scrolling.
    return ceilf((kAutoScrollingThreshold - proximity) / 5.0);
}

- (void)legalizeAutoscrollDistance {
    // makes sure the autoscroll distance won't result in scrolling past the content of the scroll view
    float minimumLegalDistance = (self.collectionView.contentOffset.x + self.collectionView.contentInset.left) * -1;

    float maximumLegalDistance = self.collectionView.collectionViewLayout.collectionViewContentSize.width - (self.view.frame.size.width + self.collectionView.contentOffset.x);

    //NSLog(@"min dist %f", minimumLegalDistance);
    //NSLog(@"max dist %f", maximumLegalDistance);


    _autoscrollDistance = MAX(_autoscrollDistance, minimumLegalDistance);
    _autoscrollDistance = MIN(_autoscrollDistance, maximumLegalDistance);

    //NSLog(@"autoscroll distance %f", _autoscrollDistance);
}

- (void)autoscrollTimerFired:(NSTimer*)timer {

    //    NSLog(@"autoscrolling: %.2f",_autoscrollDistance);
    [self legalizeAutoscrollDistance];
    // autoscroll by changing content offset
    CGPoint contentOffset = [self.collectionView contentOffset];
    contentOffset.x += _autoscrollDistance;

    //NSLog(@"%f",contentOffset.x);

    [self.collectionView setContentOffset:contentOffset];

    // adjust thumb position so it appears to stay still
    UIImageView *snapshot = (UIImageView *)[timer userInfo];
    snapshot.center = CGPointMake(snapshot.center.x + _autoscrollDistance, snapshot.center.y);

}

在调用 maybeAutoscrollForSnapshot 函数时将发送者 (UILongPressGestureRecognizer) 保存在 UIGestureRecognizerStateChanged 中,然后让计时器使用您保存的手势识别器对象调用 longPressGestureRecognized。手势识别器相对于 UIWindow 的位置相同,但是当您计算 indexPath 它应该是正确的,因为 scrollView 有滚动条。

附带说明一下,当您到达边缘时,您真的需要让视图滚动吗?
我实现了同样的事情,但没有自动滚动部分,它工作正常。

您的列表是否很长,如果自动滚动会有帮助?