I made a custom UITableViewRowAction. Now I'd like to add an image instead of the text. I know that it's possible but don't know how to do it. Does someone of you knows how to do this in Swift and would like to help me? Thanks for your answers!
-
Answered here -https://stackoverflow.com/questions/44771778/how-to-add-image-in-uitableviewrowaction/45301272#45301272 – Jack Jul 25 '17 at 10:57
-
Update answered for both iOS 11 and earlier https://stackoverflow.com/questions/27740884/uitableviewrowaction-title-as-image-icon-instead-of-text/46337919#46337919 – Martin Le Sep 21 '17 at 07:50
-
the size of the image and row height should be match??? – ArgaPK May 10 '18 at 13:46
9 Answers
iOS 11.0
Swift
Apple introduced flexible way to declare row actions with great benefits.
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let askAction = UIContextualAction(style: .normal, title: nil) { action, view, complete in
print("Ask!")
complete(true)
}
// here set your image and background color
askAction.image = IMAGE
askAction.backgroundColor = .darkGray
let blockAction = UIContextualAction(style: .destructive, title: "Block") { action, view, complete in
print("Block")
complete(true)
}
return UISwipeActionsConfiguration(actions: [blockAction, askAction])
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.textLabel?.text = "row: \(indexPath.row)"
}
}
Example:
iOS 8.0
You need to set UIImage to backgroundColor of row action, concretely by:
Swift:
UIColor(patternImage: UIImage(named: "IMAGE_NAME"))
Objective-C:
[UIColor colorWithPatternImage:[UIImage imageNamed:@"IMAGE_NAME"]];

- 12,093
- 5
- 62
- 45
-
3For the images, I put together a CocoaPod that would try to solve this as elegantly as possible for the time being: https://github.com/benguild/BGTableViewRowActionWithImage – Ben Guild Aug 21 '15 at 16:54
-
3@dimpiax: Great. This works for image. But how to put both background color and image together. – Anooj Krishnan G Aug 27 '16 at 05:55
-
@AnoojKrishnanG, hi! I propose two solutions: 1. background color you set concretely in image; 2. set for UIImage imageWithRenderingMode(.AlwaysTemplate) special color. – dimpiax Aug 28 '16 at 14:11
-
2When using an image, the size of the button must be determined and applied to the image. The text must also be a certain number of "spaces". If title text is set in `rowActionWithStyle: title: handler:`, the image and text may overlap. The number of spaces in title text determines the width of the image. – David Jan 19 '17 at 14:24
-
I can't seem to make this size the image the way I want, regardless of how many spaces I use. – Gandalf458 Jun 23 '17 at 21:29
-
-
-
-
@Gandalf458 firstly get bounds of image that you put inside, secondly try like png. – dimpiax Jun 24 '17 at 06:51
-
Now your answer does not work: `Setting a pattern color as backgroundColor of UITableViewRowAction is no longer supported.` – Vergiliy Apr 22 '19 at 15:01
-
Swift 4 (iOS 11+):
iOS 11 now supports images (only) to display in action buttons. You simply have to initialize a UISwipeActionsConfiguration object in your table view delegate object:
extension MyTableViewController:UITableViewDelegate {
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .normal, title: nil, handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
debugPrint("Delete tapped")
success(true)
})
deleteAction.image = UIImage(named: "icon_delete.png")
deleteAction.backgroundColor = UIColor.red
return UISwipeActionsConfiguration(actions: [deleteAction])
}
}

- 5,432
- 4
- 22
- 24

- 7,979
- 1
- 64
- 49
I made this simple UITableViewRowAction category, in order to set the icon for my actions. You can set the image, the background color, the cell height (to manage dynamic cells) and the icon size in percentage.
extension UITableViewRowAction {
func setIcon(iconImage: UIImage, backColor: UIColor, cellHeight: CGFloat, iconSizePercentage: CGFloat)
{
let iconHeight = cellHeight * iconSizePercentage
let margin = (cellHeight - iconHeight) / 2 as CGFloat
UIGraphicsBeginImageContextWithOptions(CGSize(width: cellHeight, height: cellHeight), false, 0)
let context = UIGraphicsGetCurrentContext()
backColor.setFill()
context!.fill(CGRect(x:0, y:0, width:cellHeight, height:cellHeight))
iconImage.draw(in: CGRect(x: margin, y: margin, width: iconHeight, height: iconHeight))
let actionImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.backgroundColor = UIColor.init(patternImage: actionImage!)
}
}

- 1,940
- 5
- 27
- 41

- 117
- 2
- 4
-
-
please can you show some code sample of how to use this, plus what value should i put for the iconSizePercentage is it 0-100%? – Oluwatobi Omotayo Sep 06 '18 at 15:47
-
Sorry for the delay. You should use this helper in the tableView editActionsForRowAt delegate. You create an UITableViewRowAction and you apply the setIcon to it. The percentage is from 0 to 1.0 – Il Pisanello Sep 18 '18 at 13:23
class TableViewRowAction: UITableViewRowAction
{
var image: UIImage?
func _setButton(button: UIButton)
{
if let image = image, let titleLabel = button.titleLabel
{
let labelString = NSString(string: titleLabel.text!)
let titleSize = labelString.sizeWithAttributes([NSFontAttributeName: titleLabel.font])
button.tintColor = UIColor.whiteColor()
button.setImage(image.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
button.imageEdgeInsets.right = -titleSize.width
}
}
}
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]?
{
let delete = TableViewRowAction(style: UITableViewRowActionStyle.Default, title: " ") { action, indexPath in }
delete.image = UIImage(named: "trashImg")
let sharing = TableViewRowAction(style: UITableViewRowActionStyle.Default, title: " ") { action, indexPath in }
sharing.backgroundColor = UIColor.lightGrayColor()
sharing.image = UIImage(named: "sharingImg")
return [delete, sharing]
}

- 3,279
- 2
- 25
- 22
-
FYI this won't let you just add an image, there has to be text as well. It was crashing when I put "" into the title argument (due to force unwrap of titleLabel.text). I had to put " " into the title argument to get an image only swipe action...on the plus side you can control the width of the button with the number of blank spaces though...:) – Natalia Sep 09 '16 at 18:57
-
1@Jayesh Miruliya, how did you know that UITableViewRowAction had a button property? This is NOT in the documentation: https://developer.apple.com/reference/uikit/uitableviewrowaction. Is this technically a hack ? – etayluz Sep 19 '16 at 16:02
-
-
4No one should use this solution. You have no way of knowing if Apple stops calling setButton. – mattsson May 03 '17 at 09:21
-
For those who want to create this effect;
I have created an extension for UISwipeActionsConfiguration
which you can use without any third-party library. Basically, the idea is to create an Attributed String from the image and the text and set it to label and create an image from that label. And append it to UIContextualAction
's image
property.
extension UISwipeActionsConfiguration {
public static func makeTitledImage(
image: UIImage?,
title: String,
textColor: UIColor = .white,
font: UIFont = .systemFont(ofSize: 14),
size: CGSize = .init(width: 50, height: 50)
) -> UIImage? {
/// Create attributed string attachment with image
let attachment = NSTextAttachment()
attachment.image = image
let imageString = NSAttributedString(attachment: attachment)
/// Create attributed string with title
let text = NSAttributedString(
string: "\n\(title)",
attributes: [
.foregroundColor: textColor,
.font: font
]
)
/// Merge two attributed strings
let mergedText = NSMutableAttributedString()
mergedText.append(imageString)
mergedText.append(text)
/// Create label and append that merged attributed string
let label = UILabel(frame: CGRect(x: 0, y: 0, width: size.width, height: size.height))
label.textAlignment = .center
label.numberOfLines = 2
label.attributedText = mergedText
/// Create image from that label
let renderer = UIGraphicsImageRenderer(bounds: label.bounds)
let image = renderer.image { rendererContext in
label.layer.render(in: rendererContext.cgContext)
}
/// Convert it to UIImage and return
if let cgImage = image.cgImage {
return UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .up)
}
return nil
}
}
And you can use it like this;
public func tableView(
_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration?
{
let deleteAction = UIContextualAction(
style: .normal,
title: nil,
handler: { [weak self] (_, _, success: (Bool) -> Void) in
success(true)
print("Your action in here")
}
)
deleteAction.image = UISwipeActionsConfiguration.makeTitledImage(
image: UIImage(named: "delete_icon"),
title: "Delete")
)
deleteAction.backgroundColor = .orange
return UISwipeActionsConfiguration(actions: [deleteAction])
}

- 558
- 6
- 18
I just implement better version of https://stackoverflow.com/a/48122210 1. In majority of cases height of cell and width of custom swipe part are different, you have to calculate this in your function 2. Image can have different size than square, so you have to calculate proportions not just for height. So, code with my fixes
func setIcon(iconImage: UIImage, backColor: UIColor, cellHeight: CGFloat, customSwipPartWidth: CGFloat, iconSizePercentage: CGFloat) {
let iconWidth = customSwipPartWidth * iconSizePercentage
let iconHeight = iconImage.size.height / iconImage.size.width * iconWidth
let marginY = (cellHeight - iconHeight) / 2 as CGFloat
let marginX = (customSwipPartWidth - iconWidth) / 2 as CGFloat
UIGraphicsBeginImageContextWithOptions(CGSize(width: customSwipPartWidth, height: cellHeight), false, 0)
let context = UIGraphicsGetCurrentContext()
backColor.setFill()
context!.fill(CGRect(x:0, y:0, width:customSwipPartWidth, height:cellHeight))
iconImage.draw(in: CGRect(x: marginX, y: marginY, width: iconWidth, height: iconHeight))
let actionImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.backgroundColor = UIColor.init(patternImage: actionImage!)
}

- 869
- 7
- 4
My variation tries to use UImageView's contentMode behavior and when that was not enough, I found some good use for a couple of the Core Geometry Rect methods because they keep the target frame centered within the cell frame. Using an eye test, It appears that swiping was removing half of my normal cell width so that's where the magic numbers show up.
Swift 3.0
extension UITableViewRowAction {
func setIcon(iconImage: UIImage, backColor: UIColor, cellHeight: CGFloat, cellWidth:CGFloat) ///, iconSizePercentage: CGFloat)
{
let cellFrame = CGRect(origin: .zero, size: CGSize(width: cellWidth*0.5, height: cellHeight))
let imageFrame = CGRect(x:0, y:0,width:iconImage.size.width, height: iconImage.size.height)
let insetFrame = cellFrame.insetBy(dx: ((cellFrame.size.width - imageFrame.size.width) / 2), dy: ((cellFrame.size.height - imageFrame.size.height) / 2))
let targetFrame = insetFrame.offsetBy(dx: -(insetFrame.width / 2.0), dy: 0.0)
let imageView = UIImageView(frame: imageFrame)
imageView.image = iconImage
imageView.contentMode = .left
guard let resizedImage = imageView.image else { return }
UIGraphicsBeginImageContextWithOptions(CGSize(width: cellWidth, height: cellHeight), false, 0)
guard let context = UIGraphicsGetCurrentContext() else { return }
backColor.setFill()
context.fill(CGRect(x:0, y:0, width:cellWidth, height:cellHeight))
resizedImage.draw(in: CGRect(x:(targetFrame.origin.x / 2), y: targetFrame.origin.y, width:targetFrame.width, height:targetFrame.height))
guard let actionImage = UIGraphicsGetImageFromCurrentImageContext() else { return }
UIGraphicsEndImageContext()
self.backgroundColor = UIColor.init(patternImage: actionImage)
}
}
Usage: from the editActions... method of the tableview delegate.
let cellHeight = (self.tableView(tableView, cellForRowAt: indexPath)).frame.size.height
let cellWidth = (self.tableView(tableView, cellForRowAt: indexPath)).frame.size.width
let favorite = UITableViewRowAction(style: .normal, title: nil) { action, index in
//perform action
debugPrint("Test")
}
favorite.setIcon(iconImage: #imageLiteral(resourceName: "favorite"), backColor: .green, cellHeight: cellHeight, cellWidth:cellWidth)

- 12,895
- 5
- 82
- 100
The problem with the basic pattern-color approach is that your image need to be of the same size than the action button, or at least to have some more flat background at the bottom to prevent the image repetition (even though it will be anchored on top, which is not really nice).
I had to deal with dynamic-height cells so I implemented the following:
- (UIColor *)backgroundImageForActionAtIndexPath:(NSIndexPath *)indexPath withImage:(UIImage *)image tintColor:(UIColor *)tintColor backgroundColor:(UIColor *)backgrounfColor expectedWith:(CGFloat)width {
//
CGRect cellFrame = [self.tableView rectForRowAtIndexPath:indexPath];
CGSize expectedSize = CGSizeMake(width, cellFrame.size.height);
UIGraphicsBeginImageContextWithOptions(expectedSize, NO, 0.0);
CGContextRef ctx = UIGraphicsGetCurrentContext ();
if (ctx) {
// set the background
CGContextSetFillColorWithColor(ctx, backgrounfColor.CGColor);
CGRect fillRect = CGRectZero;
fillRect.size = expectedSize;
CGContextFillRect(ctx, fillRect);
// put the image
CGContextSetFillColorWithColor(ctx, tintColor.CGColor);
CGRect rect = CGRectMake((expectedSize.width - image.size.width) / 2., (expectedSize.height - image.size.height) / 2., image.size.width, image.size.height);
[image drawInRect:rect];
}
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return [UIColor colorWithPatternImage:newImage];
}
You still have to set the expectedWidth so it matches the empty label you put in the action's title. e.g: @" " -> 64.f
As you see with this approach you can set the button's background and tint color in code and not in the artwork itself.

- 737
- 8
- 13