iOS - Allow default row movement in `UITableView`

2019-03-15 12:12发布

I want to allow the default row movement in a UITableView without it being in editing mode, and without compromising the default behaviour of the UITableView.

movable cell

The image above displays a cell in editing mode, with movement enabled.

I tried simply running for (UIView *subview in cell.subviews) (while my UITableView was in editing mode), but the button didn't turn up:

<UITableViewCellScrollView: 0x8cabd80; frame = (0 0; 320 44); autoresize = W+H; gestureRecognizers = <NSArray: 0x8c9ba20>; layer = <CALayer: 0x8ca14b0>; contentOffset: {0, 0}>

How can allow/add the movement "button" without enabling editing mode in my UITableView?

Creating and adding a UIButton with the default function for movement is also an option.

3条回答
Viruses.
2楼-- · 2019-03-15 12:24

I actually do something similar for one of my apps. It uses the delegate methods for table editing and a bit of 'tricking' the user. 100% built-in Apple functionality.

1 - Set the table to editing (I do it in viewWillAppear)

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [self.tableView setEditing:YES];
}

2 - Hide the default accessory icon:

-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView 
        editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
        //remove any editing accessories (AKA) delete button 
        return UITableViewCellAccessoryNone;
       }

3 - Keep editing mode from moving everything to the right (in the cell)

 -(BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath{
return NO;
}

4 - At this point you should be able to drag the cells around without it looking like it is in editing mode. Here we trick the user. Create your own "move" icon (three lines in the default case, whatever icon you want in your case) and add the imageView right where it would normally go on the cell.

5 - Finally, implement the delegate method to actually rearrange your underlying datasource.

-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{

    //Get original id
    NSMutableArray *originalArray = [self.model.items objectAtIndex:sourceIndexPath.section];
    Item * original = [originalArray objectAtIndex:sourceIndexPath.row];

    //Get destination id
    NSMutableArray *destinationArray = [self.model.items objectAtIndex:destinationIndexPath.section];
    Item * destination = [destinationArray objectAtIndex:destinationIndexPath.row];

    CGPoint temp = CGPointMake([original.section intValue], [original.row intValue]);

    original.row = destination.row;
    original.section = destination.section;

    destination.section = @(temp.x);
    destination.row = @(temp.y);

    //Put destination value in original array
    [originalArray replaceObjectAtIndex:sourceIndexPath.row withObject:destination];

    //put original value in destination array
    [destinationArray replaceObjectAtIndex:destinationIndexPath.row withObject:original];

    //reload tableview smoothly to reflect changes
    dispatch_async(dispatch_get_main_queue(), ^{
        [UIView transitionWithView:tableView duration:duration options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
            [tableView reloadData];
        } completion:NULL];
    });
 }
查看更多
够拽才男人
3楼-- · 2019-03-15 12:46

William Falcon's answer in swift 3

1 - Set the table to editing (I do it in viewWillAppear)

override func viewWillAppear(_ animated: Bool) {
   super.viewWillAppear(animated: animated)
   tableView.setEditing(true, animated: false)
}

2 - Hide the default accessory icon:

override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
    return .none
}

3 - Keep editing mode from moving everything to the right (in the cell)

override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
    return false
}

4 - Not required in swift 3

5 - Reorder your array

Extra Note

If you want to have your table cells selectable, add the following code within viewWillAppear() function.

tableView.allowsSelectionDuringEditing = true
查看更多
Evening l夕情丶
4楼-- · 2019-03-15 12:48

If you don't want to set the UITableView into editing mode then you will need to reinvent the capability for dragging cells around.

I present a fairly complete solution that enables the user to move rows around within the visible area of the UITableView. It works whether the UITableView is in editing mode or not. You'll need to extend it if you want the table to scroll when a row is dragged near the top or bottom of the visible area. There are likely some failure and edge cases you'll need to ferret out as well.

@implementation TSTableViewController
{
    NSMutableArray* _dataSource;
}

- (void) viewDidLoad
{
    [super viewDidLoad];

    _dataSource = [NSMutableArray new];
    for ( int i = 0 ; i < 10 ; i++ )
    {
        [_dataSource addObject: [NSString stringWithFormat: @"cell %d", i]];
    }
}

- (void) longPress: (UILongPressGestureRecognizer*) lpgr
{
    static NSString* dragCellData = nil;
    static UIView*   dragCellView = nil;
    static NSInteger dragCellOffset = 0;

    // determine the cell we're hovering over, etc:
    CGPoint pt = [lpgr locationInView: self.tableView];
    NSIndexPath* ip = [self.tableView indexPathForRowAtPoint: pt];
    UITableViewCell* cell = [self.tableView cellForRowAtIndexPath: ip];
    CGPoint ptInCell = [lpgr locationInView: cell];

    // where the current placeholder cell is, if any:
    NSInteger placeholderIndex = [_dataSource indexOfObject: @"placeholder"];

    switch ( lpgr.state )
    {
        case UIGestureRecognizerStateBegan:
        {
            // get a snapshot-view of the cell we're going to drag:
            cell.selected = cell.highlighted = NO;
            dragCellView = [cell snapshotViewAfterScreenUpdates: YES];
            dragCellView.clipsToBounds       = NO;
            dragCellView.layer.shadowRadius  = 10;
            dragCellView.layer.shadowColor   = [UIColor blackColor].CGColor;
            dragCellView.layer.masksToBounds = NO;
            dragCellView.frame = [cell convertRect: cell.bounds
                                        toView: self.tableView.window];

            // used to position the dragCellView nicely:
            dragCellOffset = ptInCell.y;

            // the cell will be removed from the view hierarchy by the tableview, so transfer the gesture recognizer to our drag view, and add it into the view hierarchy:
            [dragCellView addGestureRecognizer: lpgr];
            [self.tableView.window addSubview: dragCellView];


            // swap out the cell for a placeholder:
            dragCellData = _dataSource[ip.row];
            _dataSource[ip.row] = @"placeholder";

            [self.tableView reloadRowsAtIndexPaths: @[ip]
                                  withRowAnimation: UITableViewRowAnimationNone];

            break;
        }

        case UIGestureRecognizerStateChanged:
        {
            // where should we move the placeholder to?
            NSInteger insertIndex = ptInCell.y < cell.bounds.size.height / 2.0 ? ip.row : ip.row + 1;
            if ( insertIndex != placeholderIndex )
            {
                // remove from the datasource and the tableview:
                [_dataSource removeObjectAtIndex: placeholderIndex];
                [self.tableView deleteRowsAtIndexPaths: @[ [NSIndexPath indexPathForRow: placeholderIndex inSection: 0] ]
                                      withRowAnimation: UITableViewRowAnimationFade];
                // adjust:
                if ( placeholderIndex < insertIndex )
                {
                    insertIndex--;
                }

                // insert to the datasource and tableview:
                [_dataSource insertObject: @"placeholder"
                                  atIndex: insertIndex];
                [self.tableView insertRowsAtIndexPaths: @[ [NSIndexPath indexPathForRow: insertIndex inSection: 0] ]
                                      withRowAnimation: UITableViewRowAnimationFade];
            }

            // move our dragCellView
            CGRect f = dragCellView.frame;
            f.origin.y = pt.y - dragCellOffset;
            dragCellView.frame = f;

            break;
        }

        case UIGestureRecognizerStateEnded:
        {
            // replace the placeholdercell with the cell we were dragging
            [_dataSource replaceObjectAtIndex: placeholderIndex
                                   withObject: dragCellData];
            [self.tableView reloadRowsAtIndexPaths: @[ [NSIndexPath indexPathForRow: placeholderIndex inSection: 0] ]
                                  withRowAnimation: UITableViewRowAnimationFade];

            // reset state
            [dragCellView removeFromSuperview];
            dragCellView = nil;
            dragCellData = nil;

            break;
        }

        default:
        {
            break;
        }
    }
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _dataSource.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString* cellData = _dataSource[indexPath.row];
    if ( [cellData isEqualToString: @"placeholder" ] )
    {
        // an empty cell to denote where the "drop" would go
        return [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
                                      reuseIdentifier: nil];
    }

    // a cell...
    UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
                                                   reuseIdentifier: nil];

    // our "fake" move handle & gesture recognizer

    UILongPressGestureRecognizer* lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget: self action: @selector( longPress:) ];
    lpgr.minimumPressDuration = 0.3;

    UILabel* dragLabelView = [UILabel new];
    dragLabelView.text = @"☰";
    dragLabelView.userInteractionEnabled = YES;
    [dragLabelView addGestureRecognizer: lpgr];
    [dragLabelView sizeToFit];

    cell.textLabel.text = cellData;

    if ( tableView.isEditing )
    {
        cell.editingAccessoryView = dragLabelView;
    }
    else
    {
        cell.accessoryView = dragLabelView;
    }

    return cell;
}

@end
查看更多
登录 后发表回答