UITableView invoke swipe actions programmatically

本秂侑毒 提交于 2019-12-17 16:06:50

问题


I have a UITableView where the user can swipe left to reveal actions (like in iOS 8 mail). That all works as expected. I want to trigger this when the user taps on a certain part of the cell. How can I invoke this slide action programmatically?

Current behavior: User must swipe the cell left to disclose the action buttons.

Desired behavior: User taps (an actions button) on the cell. Cell slides over to disclose the action buttons.


回答1:


Well I couldn't find a way to do this programmatically, but I came up with this workaround. When the user taps the cell, I animated (pan) it to the left to momentarily reveal afake "Swipe Me" button. This is quickly reversed so the cell is back to normal. This provides a visual cue to let the user know that they can swipe the cell:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];

    __block UILabel *swipeLabel = [[UILabel alloc]initWithFrame:CGRectMake(cell.bounds.size.width,
                                                                   0,
                                                                   200,
                                                                   cell.bounds.size.height)];

    swipeLabel.text = @"  Swipe Me";
    swipeLabel.backgroundColor = [UIColor greenColor];
    swipeLabel.textColor = [UIColor whiteColor];
    [cell addSubview:swipeLabel];

    [UIView animateWithDuration:0.3 animations:^{
        [cell setFrame:CGRectMake(cell.frame.origin.x - 100, cell.frame.origin.y, cell.bounds.size.width, cell.bounds.size.height)];
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:0.3 animations:^{
            [cell setFrame:CGRectMake(cell.frame.origin.x + 100, cell.frame.origin.y, cell.bounds.size.width, cell.bounds.size.height)];
        } completion:^(BOOL finished) {
            [swipeLabel removeFromSuperview];
            swipeLabel = nil;
        }];
    }];
}

Hope this helps someone.

Note that you need to set your tableViewCell's selection type to none. Else the gray bar will obscure it.

Update. I thought I'd post a more Swifty version:

func previewActions(forCellAt indexPath: IndexPath) {
    guard let cell = tableView.cellForRow(at: indexPath) else {
        return
    }

    let label: UILabel = {
        let label = UILabel(frame: CGRect.zero)
        label.text = "  Swipe Me  "
        label.backgroundColor = .blue
        label.textColor = .white
        return label
    }()

    // Figure out the best width and update label.frame
    let bestSize = label.sizeThatFits(label.frame.size)
    label.frame = CGRect(x: cell.bounds.width - bestSize.width, y: 0, width: bestSize.width, height: cell.bounds.height)
    cell.insertSubview(label, belowSubview: cell.contentView)

    UIView.animate(withDuration: 0.3, animations: {
        cell.transform = CGAffineTransform.identity.translatedBy(x: -label.bounds.width, y: 0)
        label.transform = CGAffineTransform.identity.translatedBy(x: label.bounds.width, y: 0)
    }) { (finished) in
        UIView.animateKeyframes(withDuration: 0.3, delay: 0.25, options: [], animations: {
            cell.transform = CGAffineTransform.identity
            label.transform = CGAffineTransform.identity
        }, completion: { (finished) in
            label.removeFromSuperview()
        })
    }
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    previewActions(forCellAt: indexPath)
    return
}



回答2:


For anyone in search of the Swift version of VaporwareWolf's answer, here it is:

func animateRevealHideActionForRow(tableView: UITableView, indexPath: IndexPath) {
    let cell = tableView.cellForRow(at: indexPath)

    // Should be used in a block
    var swipeLabel: UILabel? = UILabel.init(frame: CGRect(x: cell!.bounds.size.width,
                                                          y: 0,
                                                          width: 200,
                                                          height: cell!.bounds.size.height))

    swipeLabel!.text = "  Swipe Me";
    swipeLabel!.backgroundColor = UIColor.init(red: 255/255, green: 41/255, blue: 53/255, alpha: 1) // Red
    swipeLabel!.textColor = UIColor.white
    cell!.addSubview(swipeLabel!)

    UIView.animate(withDuration: 0.3, animations: {
        cell!.frame = CGRect(x: cell!.frame.origin.x - 100, y: cell!.frame.origin.y, width: cell!.bounds.size.width + 100, height: cell!.bounds.size.height)
    }) { (finished) in
        UIView.animate(withDuration: 0.3, animations: {

            cell!.frame = CGRect(x: cell!.frame.origin.x + 100, y: cell!.frame.origin.y, width: cell!.bounds.size.width - 100, height: cell!.bounds.size.height)

        }, completion: { (finished) in
            swipeLabel?.removeFromSuperview()
            swipeLabel = nil;
        })
    }
}



回答3:


And for a Swift 3 version where you want to call this function for every row.

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    let cell = tableView.cellForRow(at: indexPath);
    UIView.animate(withDuration: 0.3, animations: {

        cell!.frame = CGRect(x: cell!.frame.origin.x - 100, y: cell!.frame.origin.y, width: cell!.bounds.size.width + 100, height: cell!.bounds.size.height)

    }) { (finished) in
        UIView.animate(withDuration: 0.3, animations: {

            cell!.frame = CGRect(x: cell!.frame.origin.x + 100, y: cell!.frame.origin.y, width: cell!.bounds.size.width - 100, height: cell!.bounds.size.height)

        }, completion: { (finished) in
        })
    }
}



回答4:


Another modified version of VaporwareWolf's answer, this one shows two rectangles instead of one (works nicely with iOS 11's UISwipeActionsConfiguration API). I'm using this on for cells which have two labels, laid out with constraints, and could only get this to work right when animating the constraints instead of the actual cell or labels.

func showActions(forRow row:Int) {
    guard let cell = (tableView.cellForRow(at: IndexPath(row: row, section: 0))) as? RequestTableViewCell else {
        return
    }

    let labelWidth:CGFloat = 20

    let createLabel:(UIColor)->UILabel = {
        color in
        let label = UILabel(frame: CGRect.zero)
        label.backgroundColor = color
        label.clipsToBounds = false
        label.frame = CGRect(x: cell.bounds.width, y: 0, width: labelWidth, height: cell.bounds.height)
        return label
    }

    let greenLabel = createLabel(.green)
    let redLabel = createLabel(.red)

    //ordering of the subviews is key to get it to look right
    cell.insertSubview(greenLabel, aboveSubview: cell.contentView)
    cell.insertSubview(redLabel, belowSubview: greenLabel)

    let originalLabelSeparationContstraint = cell.labelSeparationContstraint

    UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 3, options: .curveEaseOut, animations: {
        cell.leftLabelLeadingConstraint.constant -= (labelWidth * 2)
        cell.labelSeparationContstraint.constant = cell.requestAgeLabel.frame.minX - cell.nameLabel.frame.maxX
        cell.rightLabelTrailingContstraint.constant += (labelWidth * 2)
        cell.layoutIfNeeded()

        greenLabel.transform = greenLabel.transform.translatedBy(x: -(labelWidth), y: 0)
        redLabel.transform = redLabel.transform.translatedBy(x: -(labelWidth * 2), y: 0)
    }, completion: {
        _ in
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 3, options: .curveEaseOut, animations: {
            cell.leftLabelLeadingConstraint.constant += (labelWidth * 2)
            cell.labelSeparationContstraint = originalLabelSeparationContstraint
            cell.rightLabelTrailingContstraint.constant -= (labelWidth * 2)
            cell.layoutIfNeeded()

            greenLabel.transform = CGAffineTransform.identity
            redLabel.transform = CGAffineTransform.identity
        }, completion: {
            _ in
            greenLabel.removeFromSuperview()
            redLabel.removeFromSuperview()
        })
    })
}



回答5:


Swift 3 Equivalent of Burak's answer. You can programmatically fire off this call wherever you want, I put it in a helper function so any table view could show this. And the first time a user uses my app I swipe it open (I felt a longer animation time was needed).

class func animateRevealHideActionForRow(tableView: UITableView, indexPath: NSIndexPath) {
    let cell = tableView.cellForRow(at: indexPath as IndexPath);

    // Should be used in a block
    var swipeLabel: UILabel? = UILabel.init(frame: CGRect.init(x: cell!.bounds.size.width, y: 0, width: 200, height: cell!.bounds.size.height))

    swipeLabel!.text = "  Swipe Me";
    swipeLabel!.backgroundColor = UIColor.red
    swipeLabel!.textColor = UIColor.white
    cell!.addSubview(swipeLabel!)

    UIView.animate(withDuration: 1.0, animations: {
        cell!.frame = CGRect.init(x: cell!.frame.origin.x - 100, y: cell!.frame.origin.y, width: cell!.bounds.size.width + 100, height: cell!.bounds.size.height)
    }) { (finished) in
        UIView.animate(withDuration: 1.0, animations: {
            cell!.frame = CGRect.init(x: cell!.frame.origin.x + 100, y: cell!.frame.origin.y, width: cell!.bounds.size.width - 100, height: cell!.bounds.size.height)
        }, completion: { (finished) in
            swipeLabel?.removeFromSuperview()
            swipeLabel = nil;
        })
    }
}



回答6:


Don't go with a hacky way and use a library like this until they implement the feature.

https://github.com/SwipeCellKit/SwipeCellKit

This library let you do things like: cell.showSwipe(orientation: .right, animated: true)



来源:https://stackoverflow.com/questions/26766243/uitableview-invoke-swipe-actions-programmatically

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!