问题
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