在部分之间移动 uitableviewcells 时添加长按手势

Adding longpress gesture on moving uitableviewcells between sections

继我之前的问题之后

我已经能够执行传统的 moveRowAtIndexPath 在部分之间移动单元格,即:将 Mark 从销售移动到市场营销。

但是现在我想更进一步,在 table 视图上添加长按手势,并允许我通过简单地在单元格上长按来移动单元格。

作为参考,我的 table 是在不同 Departments 工作的 Employees 的列表;其中模型是自定义的,但只包含简单的名称字符串。

我的行设置是这样的;

-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
    return [_objects count];
}

-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    Department *department = [_objects objectAtIndex:section];
    return [department.employees count];
}

我当前的 moveRowAtIndexPath 是:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
    if (fromIndexPath != toIndexPath ) {

        Department *departmentFrom = [_objects objectAtIndex:fromIndexPath.section];
        Department *departmentTo = [_objects objectAtIndex:toIndexPath.section];

        Employee *employee = [departmentFrom.employees objectAtIndex:fromIndexPath.row];

        [departmentFrom.employees removeObjectAtIndex:fromIndexPath.row];
        [departmentTo.employees insertObject:employee atIndex:toIndexPath.row];
        [tableView reloadData];
    }
}

所以现在我在 viewDidLoad 期间将长按添加到 table,ala;

UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGestureRecognized:)];
[self.tableView addGestureRecognizer:longPress];

然后终于有了我的长按代码本身;

- (IBAction)longPressGestureRecognized:(id)sender {

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

    CGPoint location = [longPress locationInView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: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) {
                sourceIndexPath = indexPath;

                UITableViewCell *cell = [self.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;
                [self.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;

                }];
            }
            break;
        }

        case UIGestureRecognizerStateChanged: {
            CGPoint center = snapshot.center;
            center.y = location.y;
            snapshot.center = center;

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

                // ... update data source.

                // ... move the rows.
                [self.tableView moveRowAtIndexPath:sourceIndexPath toIndexPath:indexPath];

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

        default: {
            // Clean up.
            UITableViewCell *cell = [self.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) {

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

            }];

            break;
        }
    }
}

#pragma mark - Helper methods

/** @brief Returns a customized snapshot of a given view. */
- (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.
    UIView *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;
}

遗憾的是,它在试图移动单元格的 UIGestureRecognizerStateChanged 中崩溃了;它与数组 after/before 移动的不一致有关。

另外,我不确定我这样做是否正确;此模式适用于基于部分的部门和员工列表吗?

无论如何,我的问题是:如何将长按手势添加到由部分组成的 table 视图单元格,或者 .. 我想使用长按将 Tom 从销售转移到市场营销。

根据上面的代码,这可能吗?

编辑;

我遇到的崩溃是:

'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (3) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 1 moved out).' *** First throw call st


进一步更新:@14-Jan-2016

原来接受的答案仍然导致我崩溃。原因是它假设交换正在发生;但事实并非如此。

我通过重复我的代码删除一个项目然后重新添加它来解决问题。

这是在下面发布的;

 case UIGestureRecognizerStateChanged: {
            CGPoint center = snapshot.center;
            center.y = location.y;
            center.x = location.x;
            snapshot.center = center;

            // Is destination valid and is it different from source?
            if (indexPath && ![indexPath isEqual:sourceIndexPath])
            {
                Department *departmentFrom = [_objects objectAtIndex:sourceIndexPath.section];
                Department *departmentTo = [_objects objectAtIndex:indexPath.section];

                [self.tableView beginUpdates];

                Employee *employee = [departmentFrom.employees objectAtIndex:sourceIndexPath.row];
                [departmentFrom.employees removeObjectAtIndex:sourceIndexPath.row];
                [departmentTo.employees insertObject:employee atIndex:indexPath.row];

                [self.tableView moveRowAtIndexPath:sourceIndexPath toIndexPath:indexPath];

                [self.tableView endUpdates];

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

我能看到的唯一问题是 tableView:moveRowAtIndexPath 和此代码都在重复相同的代码。

不过我可以稍后清理它。

谢谢

case UIGestureRecognizerStateChanged: {
        CGPoint center = snapshot.center;
        center.y = location.y;
        center.x = location.x;
        snapshot.center = center;

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

            [self.stackTableView beginUpdates];
            // ... update data source.
            NSLog(@"exchanging");
            [self.stackObjectArray exchangeObjectAtIndex:indexPath.section withObjectAtIndex:sourceIndexPath.section];

            // ... move the rows.
            NSLog(@"moving the rows");
            [self.stackTableView moveRowAtIndexPath:indexPath toIndexPath:sourceIndexPath];
            [self.stackTableView moveRowAtIndexPath:sourceIndexPath toIndexPath:indexPath];

            NSLog(@"ending updates");
            [self.stackTableView endUpdates];

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

好啦!

您需要做的是阻止 table 视图立即更新并使用 beginUpdate 和 endUpdates 同时进行所有更改。这样你就不会得到这样的错误。您还需要在同一移动中交换行,而不仅仅是移动它们。

我知道在 Ray Wenderlich 的示例中他只引用移动的行,所以开始吧!

http://www.raywenderlich.com/63089/cookbook-moving-table-view-cells-with-a-long-press-gesture