在iOS应用程序中拖动UITableViewCells时,可以顺畅地自动滚动UITableView

时间:2015-09-11 10:52:31

标签: ios objective-c uitableview scroll

我已经实现了一个系统来重新排序UITableViews中的单元格。一切都很好,除了我不能将单元格重新排序到iPhone屏幕上没有显示的位置。

所以我已经实现了一个条件来检查我是否需要滚动

NSArray *indexVisibles = [_tableView indexPathsForVisibleRows];
NSInteger indexForObject = [indexVisibles indexOfObject:indexPath];
if (indexForObject == NSNotFound){
     [_tableView scrollToRowAtIndexPath:indexPath
                                          atScrollPosition:UITableViewScrollPositionTop
                                                  animated:YES];
}

我的问题是动画不甜而干净。

enter image description here

我认为检查细胞是否显示的操作对于我的系统来说非常巨大,并且当我移动细胞时会产生一个小延迟,但是,我不确定为什么当细胞隐藏时滚动是如此困难

我已将UITableViewScrollPositionTop更改为UITableViewScrollPositionMiddle,现在它更好但速度非常大,所以始终滚动到我的UITableView的顶部。

我想慢慢来。

其他失败尝试:

选项1:

[UIView animateWithDuration:0.2
                                             animations:^{_tableView.contentOffset = CGPointMake(0.0, _tableView.contentOffset.y - 50);}
                                             completion:^(BOOL finished){ }];

但这有两个问题:

  1. 运动仍然很重
  2. 当拖动到第一个元素时,此元素为中间隐藏
  3. enter image description here

    选项2:

    [UIView animateWithDuration: 1.0
                                                 animations: ^{
                                                     [_tableView scrollToRowAtIndexPath:indexPath
                                                                       atScrollPosition:UITableViewScrollPositionMiddle
                                                                               animated:NO];
                                                 }completion: ^(BOOL finished){
    
                                                 }
                                 ];
    

3 个答案:

答案 0 :(得分:11)

我用一个非常漂亮的解决方案解决了我的问题,所以我将通过三个简单的步骤解释如何做到这一点。

我使用https://github.com/hpique/HPReorderTableView的一些灵感并在我自己的资源库https://github.com/enrimr/EMRReorderTableCells中分享

:一种。管理gestureRecognition

<强> longPressGestureRecognized:

- (IBAction)longPressGestureRecognized:(id)sender {

    _reorderGestureRecognizer = (UILongPressGestureRecognizer *)sender;

    CGPoint location = [_reorderGestureRecognizer locationInView:_tableView];
    NSIndexPath *indexPath = [self getCellIndexPathWithPoint:location];

    UIGestureRecognizerState state = _reorderGestureRecognizer.state;
    switch (state) {
        case UIGestureRecognizerStateBegan: {

            NSIndexPath *indexPath = [_tableView indexPathForRowAtPoint:location];
            if (indexPath == nil)
            {
                [self gestureRecognizerCancel:_reorderGestureRecognizer];
                break;
            }

            // For scrolling while dragging
            _scrollDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(scrollTableWithCell:)];
            [_scrollDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

            // Check for the right indexes (between margins of offset
            if (indexPath.row >=_elementsOffset && indexPath.row < [_elements count]+_elementsOffset){

                if (indexPath) {
                    sourceIndexPath = indexPath;

                    id sourceElement = [_elements objectAtIndex:sourceIndexPath.row-_elementsOffset];

                    snapshot = [self createSnapshotForCellAtIndexPath:indexPath withPosition:location];
                }
            } else {
                sourceIndexPath = nil;
                snapshot = nil;
            }
            break;
        }

        case UIGestureRecognizerStateChanged: {

            [self calculateScroll:_reorderGestureRecognizer];

            if (sourceIndexPath != nil && indexPath.row >=_elementsOffset && indexPath.row < [_elements count]+_elementsOffset){
                [self updateSnapshotWithPosition:location];

                // Is destination valid and is it different from source?
                if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
                    if (indexPath.row - sourceIndexPath.row <= 1){

                        id sourceElement = [_elements objectAtIndex:sourceIndexPath.row-_elementsOffset];
                        id targetElement = [_elements objectAtIndex:indexPath.row-_elementsOffset];

                        sourceIndexPath = [self exchangeElement:sourceElement byElement:targetElement];
                    }

                }
            }
            break;
        }

        case UIGestureRecognizerStateEnded:
        {
            // For scrolling while dragging
            [_scrollDisplayLink invalidate];
            _scrollDisplayLink = nil;
            _scrollRate = 0;


            // Check if it is the last element
            if (sourceIndexPath != nil){
                id element;
                if (indexPath.row <=_elementsOffset){
                    element = [_elements firstObject];
                } else if (indexPath.row > [_elements count]-1+_elementsOffset){
                    element = [_elements lastObject];
                } else {
                    element = [_elements objectAtIndex:indexPath.row-_elementsOffset];
                }  
            }

        }

        default: {
            // Clean up.
            [self deleteSnapshotForRowAtIndexPath:sourceIndexPath];

            [appDelegate startTimer];

            break;
        }
    }
}

<强> gestureRecognizerCancel:

用于取消手势识别以完成重新排序操作。

-(void) gestureRecognizerCancel:(UIGestureRecognizer *) gestureRecognizer
{ // See: http://stackoverflow.com/a/4167471/143378
    gestureRecognizer.enabled = NO;
    gestureRecognizer.enabled = YES;
}

<强> scrollTableWithCell:

当你在桌子的极限(上下)时,它被调用来进行滚动运动的方法

- (void)scrollTableWithCell:(NSTimer *)timer
{
    UILongPressGestureRecognizer *gesture = _reorderGestureRecognizer;
    const CGPoint location = [gesture locationInView:_tableView];

    CGPoint currentOffset = _tableView.contentOffset;
    CGPoint newOffset = CGPointMake(currentOffset.x, currentOffset.y + _scrollRate * 10);

    if (newOffset.y < -_tableView.contentInset.top)
    {
        newOffset.y = -_tableView.contentInset.top;
    }
    else if (_tableView.contentSize.height + _tableView.contentInset.bottom < _tableView.frame.size.height)
    {
        newOffset = currentOffset;
    }
    else if (newOffset.y > (_tableView.contentSize.height + _tableView.contentInset.bottom) - _tableView.frame.size.height)
    {
        newOffset.y = (_tableView.contentSize.height + _tableView.contentInset.bottom) - _tableView.frame.size.height;
    }

    [_tableView setContentOffset:newOffset];

    if (location.y >= 0 && location.y <= _tableView.contentSize.height + 50)
    {

        [self updateSnapshotWithPosition:location];
        NSIndexPath *indexPath = [self getCellIndexPathWithPoint:location];

        // CHeck if element is between offset limits.
        if (![indexPath isEqual:sourceIndexPath] &&
            indexPath.row >= _elementsOffset &&
            indexPath.row - _elementsOffset < [_elements count] &&
            sourceIndexPath.row >= _elementsOffset &&
            sourceIndexPath.row - _elementsOffset < [_elements count])
        {
            id sourceElement = [_elements objectAtIndex:sourceIndexPath.row-_elementsOffset];
            id targetElement = [_elements objectAtIndex:indexPath.row-_elementsOffset];
            [self exchangeElement:sourceElement byElement:targetElement];
            sourceIndexPath = indexPath;
        }
    }
}

<强> B中。快照管理

<强> createSnapshotForCellAtIndexPath:withPosition

创建正在移动的单元格的快照(图像副本)的方法

-(UIView *)createSnapshotForCellAtIndexPath:(NSIndexPath *)indexPath withPosition:(CGPoint)location{
    UITableViewCell *cell = [_tableView cellForRowAtIndexPath: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;

    [_tableView addSubview:snapshot];
    [UIView animateWithDuration:0.25 animations:^{

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

    } completion:^(BOOL finished) {

        cell.hidden = YES;
    }];

    return snapshot;
}

<强> customSnapshoFromView:

返回给定视图的自定义快照。 * /

- (UIView *)customSnapshoFromView:(UIView *)inputView {

    // Make an image from the input view.
    UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, NO, 0);
    [inputView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // Create an image view.
    snapshot = [[UIImageView alloc] initWithImage:image];
    snapshot.layer.masksToBounds = NO;
    snapshot.layer.cornerRadius = 0.0;
    snapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0);
    snapshot.layer.shadowRadius = 5.0;
    snapshot.layer.shadowOpacity = 0.4;

    return snapshot;
}

<强> updateSnapshotWithPosition:

给定CGPoint,它会更改快照位置以显示您正在_tableView正确位置移动的单元格

-(void)updateSnapshotWithPosition:(CGPoint)location{
    CGPoint center = snapshot.center;
    center.y = location.y;
    snapshot.center = center;
}

<强> deleteSnapshotForRowAtIndexPath:

拖动完成后,您需要从_tableView

中删除快照
-(void)deleteSnapshotForRowAtIndexPath:(NSIndexPath *)sourceIndexPath{
    UITableViewCell *cell = [_tableView cellForRowAtIndexPath: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) {
        [snapshot removeFromSuperview];
    }];
}

<强> calculateScroll

-(void)calculateScroll:(UIGestureRecognizer *)gestureRecognizer{

    const CGPoint location = [gestureRecognizer locationInView:_tableView];

    CGRect rect = _tableView.bounds;
    // adjust rect for content inset as we will use it below for calculating scroll zones
    rect.size.height -= _tableView.contentInset.top;

    //[self updateCurrentLocation:gestureRecognizer];

    // tell us if we should scroll and which direction
    CGFloat scrollZoneHeight = rect.size.height / 6;
    CGFloat bottomScrollBeginning = _tableView.contentOffset.y + _tableView.contentInset.top + rect.size.height - scrollZoneHeight;
    CGFloat topScrollBeginning = _tableView.contentOffset.y + _tableView.contentInset.top  + scrollZoneHeight;

    // we're in the bottom zone
    if (location.y >= bottomScrollBeginning)
    {
        _scrollRate = (location.y - bottomScrollBeginning) / scrollZoneHeight;
    }
    // we're in the top zone
    else if (location.y <= topScrollBeginning)
    {
        _scrollRate = (location.y - topScrollBeginning) / scrollZoneHeight;
    }
    else
    {
        _scrollRate = 0;
    }

}

<强>℃。如何使用

在init方法中,将手势识别器分配给表格视图。将方法longPressGestureRecognized:指定为操作,如下所示:

    _reorderGestureRecognizer = [[UILongPressGestureRecognizer alloc]
                                               initWithTarget:self action:@selector(longPressGestureRecognized:)];

    [_tableView addGestureRecognizer:_reorderGestureRecognizer];

声明使用上述代码所需的变量

@implementation YourClassName{

    CADisplayLink *_scrollDisplayLink;
    CGFloat _scrollRate;
    UIView *snapshot; // A snapshot of the row user is moving.
    NSIndexPath *sourceIndexPath; // Initial index path, where gesture begins.
}

这就是解决我遇到的问题所需的一切。

答案 1 :(得分:1)

我也在寻找一种通过长按它们来渲染tableview单元格的方法。 我找到了一个用于此目的的存储库。如果您不介意将第三方库添加到您的项目中,请查看它! :)

LPRTableView

我个人将此代码用于我的应用程序Cheetah Note。他们的工作就像一个魅力!强烈推荐!

答案 2 :(得分:0)

使用选项一尝试滚动到拖动的单元格位置 - 当拖动的单元格位于顶部时为1:

NSArray *indexVisibles = [_tableView indexPathsForVisibleRows];
NSInteger indexForObject = [indexVisibles indexOfObject:indexPath];
if (indexForObject == 0){
     [_tableView scrollToRowAtIndexPath:indexPathForCellBefore
                                          atScrollPosition:UITableViewScrollPositionTop
                                                  animated:YES];
}

要平滑滚动tableview,请使用以下代码段:

 [UIView animateWithDuration: 1.0 animations: ^{
     [_tableView scrollToRowAtIndexPath:indexPathForCellBefore atScrollPosition:UITableViewScrollPositionTop animated:NO];
 } completion: ^(BOOL finished){
 }];

当然,您需要在向下滚动时修改它。