9

I have a trailingSwipeAction in a UITableViewCell, whose background color must be clear.

This is the code where I set the action :

func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
    let myAction = UIContextualAction.init(style: .normal, title: nil) {
        // My action code
    }
    myAction.backgroundColor = .clear
    myAction.image = UIImage.init(named: "my-icon")
    return UISwipeActionsConfiguration.init(actions: [myAction])
}

But I am getting gray background for the action, when no color was expected:

enter image description here

pkamb
  • 33,281
  • 23
  • 160
  • 191
Sergio
  • 1,610
  • 14
  • 28
  • Clear color - means fully transparent. So you need to check background colors of views which are under your button (TableViewCell, TableView etc...) – Dmitry Aug 20 '18 at 21:03
  • Still gray, thank you anyway. – Sergio Aug 20 '18 at 21:10
  • have you set the tableView.backgroundColor = .clear in the viewdidload method ? – Alejandro Viquez Aug 20 '18 at 22:05
  • Yes, tableview background is clear and everything behind the cell and the cell to clear too. And it still appears gray – Sergio Aug 20 '18 at 22:12
  • and the cell.contentView.backgroundColor = .clear ? – Alejandro Viquez Aug 20 '18 at 22:40
  • Yes, everything. I will try what Carpsen90 says, here I have found some info: https://stackoverflow.com/questions/46716229/not-able-find-uitableviewcelldeleteconfirmationview-in-ios-11-layoutsubview-subv/46721607#46721607 – Sergio Aug 20 '18 at 22:42
  • @Sergio don’t use UILabel in the last if-condition. Use UIImage or UIImageView. Please read the code and comments carefully. – ielyamani Aug 21 '18 at 00:35

7 Answers7

21

You can just set the alpha value to 0 for background color of the action:

let modifyAction = UIContextualAction(style: .normal, title:  "", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
    print("Update action ...")
    success(true)
})
modifyAction.backgroundColor = UIColor.init(red: 0/255.0, green: 0/255.0, blue: 0/255.0, alpha: 0.0)
pkamb
  • 33,281
  • 23
  • 160
  • 191
Shubham Goel
  • 1,962
  • 17
  • 25
  • 3
    It's true. Doesn't work with UIColor.clear but it does with UIColor.init(red: 0/255.0, green: 0/255.0, blue: 0/255.0, alpha: 0.0) and also with UIColor.init(white: 1, alpha: 0). Thanks, I accept your answer as valid! :) – Sergio May 02 '19 at 13:09
6

You'll have to access the UIView inside the UIActionStandardButton inside the UISwipeActionPullView. and then change its background color.

pull view hierarchy

You can see the view hierarchy of your app swiping on a cell, then going the Debug menu in Xcode, then View Debugging, and choose Capture View Hierarchy.

First of all, let add this useful extension that gets all subviews and their subviews in an array:

extension UIView {
    var allSubViews : [UIView] {
        var array = [self.subviews].flatMap {$0}
        array.forEach { array.append(contentsOf: $0.allSubViews) }
        return array
    }
}

And then in viewWillLayoutSubviews():

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    let btn = tableView
              .allSubViews //get all the subviews
              .first(where: {String(describing:type(of: $0)) ==  "UISwipeActionStandardButton"}) // get the first one that is a UISwipeActionStandardButton

    //This UISwipeActionStandardButton has two subviews, I'm getting the one that is not a UILabel, in your case, since you've set the image,  you should get the one that is not an imageView
    if let view = btn?.subviews.first(where: { !($0 is UILabel)})
    {
        view.backgroundColor = .clear //Change the background color of the gray uiview
    }
}

I am using viewWillLayoutSubviews() since it's called to notify the view controller that its view is about to layout its subviews. Have a look here for more details.

This solution is optimized for one swipe action button. If you have more than one button, the code would look like this:

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    let buttons = tableView
        .allSubViews //get all the subviews
        .filter {String(describing:type(of: $0)) ==  "UISwipeActionStandardButton"}

    buttons.forEach { btn in
        if let view = btn.subviews.first(where: { !($0 is UIImageView)}) //If you're sure that other than the uiview there is a UIImageView in the subviews of the UISwipeActionStandardButton
        {
            view.backgroundColor = .clear //Change the background color of the gray uiview
        }
    }
}
ielyamani
  • 17,807
  • 10
  • 55
  • 90
  • Excuse me, I can't find the way of doing this. Thanks. – Sergio Aug 20 '18 at 22:35
  • I will try with this: https://stackoverflow.com/questions/46716229/not-able-find-uitableviewcelldeleteconfirmationview-in-ios-11-layoutsubview-subv/46721607#46721607 – Sergio Aug 20 '18 at 22:42
  • @Sergio The post that you are referring to uses Xamarin. I've updated my answer. I hope helps. – ielyamani Aug 20 '18 at 23:41
  • Still not working, we better continue the conversation by chat. – Sergio Aug 21 '18 at 00:13
  • @Sergio what did you use instead of UILabel? – ielyamani Aug 21 '18 at 00:14
  • I have a custom cell class that extends UITableViewCell, that only contains a UILabel. – Sergio Aug 21 '18 at 00:21
  • @Sergio That’s not what I meant. Please have a look at the hierarchy of the UISwipeActionPullView. In my test project it contains a UISwipeActionStandardButton. Which contains a UIView (that is responsible for the grey background) and a UILabel. The code snippet above looks for the UIView (not the UILabel). I’m not talking about the rest of the cell. In your case since you’re setting the image for the action, you should use UIImageView or UIImage in the last if-condition. Please read my answer carefully, I hope it helps. – ielyamani Aug 21 '18 at 00:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/178470/discussion-between-sergio-and-carpsen90). – Sergio Aug 21 '18 at 19:08
  • @Sergio I did join the chat – ielyamani Aug 24 '18 at 13:48
5

Apparently if you set the background color to white with zero alpha, it will stay clear but without the gray default color.

Try this:

func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
    let deleteAction = UIContextualAction(style: .destructive, title: nil) { [weak self] (action, view, completion) in
        // weak self to prevent memory leak if needed
        guard let self = self else { return }
        
        // do your nasty stuff here 
        
        completion(true)
    }
    
    deleteAction.backgroundColor = UIColor(white: 1, alpha: 0)
    deleteAction.image = UIImage(systemName: "trash")
    
    return UISwipeActionsConfiguration(actions: [deleteAction])
}

enter image description here

John Conde
  • 217,595
  • 99
  • 455
  • 496
wizard_of_oz
  • 101
  • 1
  • 2
4

SOLUTION

Thanks to Carpsen90

enter image description here

You have to set the backgroundColor of that UIImageView to .clear, but it doesn't exist in the time of viewWillLayoutSubviews. It is created after you swipe.

A posible solution is to have a Timer:

var timerCellSwipeButtons: Timer?

Launched when the swipe is done:

func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
    let editAction = UIContextualAction.init(style: .normal, title: nil) { [weak self] (action, view, completion) in
        // editAction code
    }
    let deleteAction = UIContextualAction.init(style: .normal, title: nil) { [weak self] (action, view, completion) in
        // deleteAction code
    }
    // Set the button's images
    editAction.image = UIImage.init(named: "editIcon")
    deleteAction.image = UIImage.init(named: "deleteIcon")
    // You also must set the background color of the actions to .clear
    editAction.backgroundColor = .clear
    deleteAction.backgroundColor = .clear
    // Launch the timer, that will run a function every 10 milliseconds
    self.timerCellSwipeButtons = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(timerCellSwipeButtonsFunction), userInfo: nil, repeats: true)
    // Return the actions
    return UISwipeActionsConfiguration.init(actions: [deleteAction, editAction])
}

Now every 10 milliseconds (you can increase the frequency if you want), this function checks the tableView subviews looking for all the UISwipeActionStandardButton and setting to .clear the backgroundColor of their UIView:

@objc func timerCellSwipeButtonsFunction() {
    // Gets all the buttons, maybe we have more than one in a row
    let buttons = tableView.allSubViews.filter { (view) -> Bool in
        String(describing: type(of: view)) == "UISwipeActionStandardButton"
    }
    // Loops through all the buttons
    for button in buttons {
        if let view = button.subviews.first(where: { !($0 is UIImageView)})
        {
            // We are interested in the UIView that isn't a UIImageView
            view.backgroundColor = .clear
        }
    }
    // When finish, timer is invalidated because we don't need it anymore.
    // A new one will be launched with every swipe
    self.timerCellSwipeButtons?.invalidate()
}

To get all the subviews of a UIView, I used the function given by Carpsen90:

extension UIView {
    var allSubViews : [UIView] {
        var array = [self.subviews].flatMap {$0}
        array.forEach { array.append(contentsOf: $0.allSubViews) }
        return array
    }
}

For safety reasons, you should also invalidate the timer in the viewWillDisappear method:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    self.timerCellSwipeButtons?.invalidate()
}

And this is the result:

enter image description here

But as you can see, when you have more than one action in the same side of the cell and you swipe completely, doesn't look very nice:

enter image description here

To avoid icons overlapping I put only one action in each side:

// Remember to launch the timer in both swipe functions, like in the example above

// Function to add actions to the leading side of the cell
tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
// Function to add actions to the trailing side of the cell
tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?

enter image description here

Sergio
  • 1,610
  • 14
  • 28
2

100% working in iOS swift for change swipe button image color and background color change.

func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
    if isInbox {
        let action =  UIContextualAction(style: .normal, title: "", handler: { (action,view,completionHandler ) in
            self.selectedIndex  = indexPath.row
            self.deleteNotification()
            completionHandler(true)

        })
        let cgImageX =  UIImage(named: "delete-1")?.cgImage
        action.image = OriginalImageRender(cgImage: cgImageX!)
        action.backgroundColor = UIColor.init(hex: "F7F7F7")
        let confrigation = UISwipeActionsConfiguration(actions: [action])

        return confrigation
    }
    return nil
}

Add this Class also for original image color display otherwise its showing only white image

class OriginalImageRender: UIImage {
    override func withRenderingMode(_ renderingMode: UIImage.RenderingMode) -> UIImage {
        return self
     }
}

enter image description here

pkamb
  • 33,281
  • 23
  • 160
  • 191
Maulik Patel
  • 2,045
  • 17
  • 24
  • I only a black icon that was larger. action.backgroundColor just changed the background color...not unexpected. – Tilo Delau Oct 28 '20 at 16:17
  • @TiloDelau which type output you want please tell me i can help you – Maulik Patel Oct 29 '20 at 09:27
  • Well, I want to change the icon color. This is my (modified) code from you, but I just get the bigger sized black icon with a blue background (actually want a clear background). I guess because its not actually an image?: let cgImageX = UIImage(systemName: "minus.square")?.cgImage delete.image = OriginalImageRender(cgImage: cgImageX!) delete.backgroundColor = UIColor.blue let confrigation = UISwipeActionsConfiguration(actions: [delete]) return confrigation – Tilo Delau Oct 29 '20 at 15:36
  • please check @TiloDelau if #available(iOS 13.0, *) { action.image = UIGraphicsImageRenderer(size: CGSize(width: 30, height: 30)).image { _ in UIImage(systemName: "minus.square")?.draw(in: CGRect(x: 0, y: 0, width: 30, height: 30)) } action.backgroundColor = UIColor.init(red: 0/255.0, green: 0/255.0, blue: 0/255.0, alpha: 0.0) let confrigation = UISwipeActionsConfiguration(actions: [action]) return confrigation } else { } – Maulik Patel Oct 30 '20 at 04:53
  • Can adjust the size, but icon color is still black:if #available(iOS 13.0, *) { delete.image = UIGraphicsImageRenderer(size: CGSize(width: 20, height: 20)).image { _ in UIImage(systemName: "minus.square")?.draw(in: CGRect(x: 0, y: 0, width: 20, height: 20)) } delete.backgroundColor = UIColor.init(red: 0/255.0, green: 0/255.0, blue: 244/255.0, alpha: 0) let confrigation = UISwipeActionsConfiguration(actions: [delete]) return confrigation } else { ... – Tilo Delau Oct 30 '20 at 16:23
  • And Im on iOS 14. I added blue: 244/255.0 because I wanted it blue. But 0/255 doesn't matter either. Icon same black color. – Tilo Delau Oct 30 '20 at 16:28
0

What I have done in my project is

func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
    let subViews = tableView.subviews.filter { (view) -> Bool in
      if NSStringFromClass(view.classForCoder) == "UISwipeActionPullView" {
            return true
        }
        return false
    }
    if subViews.count > 0 {
        let bgView = subViews[0]
        bgView.backgroundColor = bgColor
    }
}

And my project's target is iOS 9.0 and above

pkamb
  • 33,281
  • 23
  • 160
  • 191
Smit Shah
  • 209
  • 2
  • 9
0

Following the solution from @Maulik Patel I just added a tint color option to the action.image:

let imageDelete = UIImage(systemName: "trash")?.cgImage
deleteAction.image = OriginalImageRender(cgImage: imageDelete!).withTintColor(UIColor(named: ("colorButton"))!)
deleteAction.backgroundColor = UIColor.init(red: 0/255.0, green: 0/255.0, blue: 0/255.0, alpha: 0.0)      
        
let configuration = UISwipeActionsConfiguration(actions: [deleteAction])
        
return configuration
pkamb
  • 33,281
  • 23
  • 160
  • 191
Miguel P.
  • 17
  • 8