GJDK
GJDK

Reputation: 883

How to change UITableViewRowAction title color?

Am using UITableViewRowAction in "editActionsForRowAtIndexPath" method. I can change the backgroundcolor of UITableViewRowAction, but am not able to change the title color. But I would like to change the color of the UITableViewRowAction. Any inputs on this regard will be appreciable.

Upvotes: 23

Views: 25238

Answers (12)

Roman Solodyashkin
Roman Solodyashkin

Reputation: 829

//
// override button title for UITableViewCell action button
// Swipe action button is a part of tableview hierarchy
// (hierarchy and class name of button) is known in debugger but can be changed at any time
// private api usage:
//
UIButton *v1 = [NSClassFromString(@"UISwipeActionStandardButton") appearance];
// when button in specific view controller
UIButton *v2 = [NSClassFromString(@"UISwipeActionStandardButton") appearanceWhenContainedInInstancesOfClasses:@[
    NSClassFromString(@"MyViewControllerClassThatContainsTableViewWithSwipeAndButton"),
]];
// when button in specific table view
UIButton *v3 = [NSClassFromString(@"UISwipeActionStandardButton") appearanceWhenContainedInInstancesOfClasses:@[
    NSClassFromString(@"MyTableClassViewWithSwipeAndButton"),
]];
[v1 setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
[v2 setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
[v3 setTitleColor:[UIColor redColor] forState:UIControlStateNormal];

Upvotes: 0

youknowbaron
youknowbaron

Reputation: 21

Thanks @Witek for sharing.

Your solution works but it's not stable. So, I try to update your code, and now it's working very well.

Put this code below in your UITableViewCell

override func layoutSubviews() {
    super.layoutSubviews()
    if let button = actionButton {
        button.setTitleColor(.black, for: .normal)
    }
}

override func layoutIfNeeded() {
    super.layoutIfNeeded()
    if let button = actionButton {
        button.setTitleColor(.black, for: .normal)
    }
}

var actionButton: UIButton? {
    superview?.subviews
        .filter({ String(describing: $0).range(of: "UISwipeActionPullView") != nil })
        .flatMap({ $0.subviews })
        .filter({ String(describing: $0).range(of: "UISwipeActionStandardButton") != nil })
        .compactMap { $0 as? UIButton }.first
}

Upvotes: 1

Witek Bobrowski
Witek Bobrowski

Reputation: 4259

So there still is no public api to change textColor or font to the cells action in the iOS 13 days.

Working solution for Swift 5.3, iOS 14

Hacks from answers in the thread have very unreliable results but I have managed to get my version of the hack working.

1. Getting the label

Here is my simplified way of accessing the actions UILabel:

extension UITableViewCell {
    var cellActionButtonLabel: UILabel? {
        superview?.subviews
            .filter { String(describing: $0).range(of: "UISwipeActionPullView") != nil }
            .flatMap { $0.subviews }
            .filter { String(describing: $0).range(of: "UISwipeActionStandardButton") != nil }
            .flatMap { $0.subviews }
            .compactMap { $0 as? UILabel }.first
    }
}

2. Updating the label on layout changes

Next, overriding layoutSubviews() in my UITableViewCell subclass wasn't enough so additionally I had to override layoutIfNeeded() for the hack to work. Note that it's important to override both of them!

override func layoutSubviews() {
    super.layoutSubviews()
    cellActionButtonLabel?.textColor = .black // you color goes here
}
override func layoutIfNeeded() {
    super.layoutIfNeeded()
    cellActionButtonLabel?.textColor = .black // you color goes here
}

3. Triggering extra layout refresh

The last piece of the puzzle is to schedule an additional refresh of the color, so we cover all of those cases where for some reason above functions would not get called. The best place for doing so is UITableViewDelegate method ..., willBeginEditingRowAt: ...

func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
        tableView.cellForRow(at: indexPath)?.layoutIfNeeded()
    }
}

A delayed layout refresh trigger does the trick of letting the UIKit configure the cell first, so we can jump right after with the customisation. 0.01 was just enough for me but i can probably vary from case to case.

4. Profit

Now you can customise the label anyway you want! Change the text, its color, font or add a subview!

It goes without saying that this can break -anytime- if Apple will decide to change their private implementation.

Upvotes: 6

Leo van der Zee
Leo van der Zee

Reputation: 1

IF someone is still looking for an alternative solution:

You can use the swipecellkit pod

https://github.com/SwipeCellKit/SwipeCellKit

This lets you customize the label color, image color and even the entire background while perserving the actual look and feel of the native implementation.

Upvotes: 0

Ivan Smetanin
Ivan Smetanin

Reputation: 2039

iOS 11 solution:

Create UITableViewCell extension like this:

extension UITableViewCell {

    /// Returns label of cell action button.
    ///
    /// Use this property to set cell action button label color.
    var cellActionButtonLabel: UILabel? {
        for subview in self.superview?.subviews ?? [] {
            if String(describing: subview).range(of: "UISwipeActionPullView") != nil {
                for view in subview.subviews {
                    if String(describing: view).range(of: "UISwipeActionStandardButton") != nil {
                        for sub in view.subviews {
                            if let label = sub as? UILabel {
                                return label
                            }
                        }
                    }
                }
            }
        }
        return nil
    }

}

And write this in your UITableViewCell

override func layoutSubviews() {
    super.layoutSubviews()
    cellActionButtonLabel?.textColor = .red
}

But there is a bug - if you pull the cell fast this code sometimes doesn't change the color.

Upvotes: 3

David Seek
David Seek

Reputation: 17132

Lee Andrew's answer in Swift 3 / Swift 4:

class MyCell: UITableViewCell {

    override func layoutSubviews() {
        super.layoutSubviews()

        for subview in self.subviews {

            for sub in subview.subviews {

                if String(describing: sub).range(of: "UITableViewCellActionButton") != nil {

                    for view in sub.subviews {

                        if String(describing: view).range(of: "UIButtonLabel") != nil {

                            if let label = view as? UILabel {

                                label.textColor = UIColor.black

                            }

                        }

                    }

                }

            }

        }

    }

}

Upvotes: 5

Oleksiy Kovtun
Oleksiy Kovtun

Reputation: 11

You can add these two functions in your UITableViewCell subclass and call the setActionButtonsTitleColor function to set action buttons' title color.

func setActionButtonsTitleColor(color: UIColor) {
  let actionButtons: [UIButton] = self.getActionButtons()

  for actionButton in actionButtons {
    actionButton.setTitleColor(color, for: .normal)
  }
}

func getActionButtons() -> [UIButton] {
  let actionButtons: [UIButton] = self.subviews.map {
    (view: UIView) -> [UIView] in
    return view.subviews
  }
  .joined()
  .filter {
    (view: UIView) -> Bool in
    return String(describing: view).contains("UITableViewCellActionButton")
  }.flatMap {
    (view: UIView) -> UIButton? in
    return view as? UIButton
  }

  return actionButtons
}

Upvotes: 1

JHT
JHT

Reputation: 9

-(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewRowAction *editAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"edit" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
    {
    // Action something here

}];

editAction.backgroundColor = [UIColor whiteColor];
[[UIButton appearance] setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];

return @[editAction];

Upvotes: 0

Lee Andrew
Lee Andrew

Reputation: 806

Swift

No need to mess with UIButton.appearance...

Put this in your cell's class and change UITableViewCellActionButton according to your needs.

override func layoutSubviews() {

    super.layoutSubviews()

    for subview in self.subviews {

        for subview2 in subview.subviews {

            if (String(subview2).rangeOfString("UITableViewCellActionButton") != nil) {

                for view in subview2.subviews {

                    if (String(view).rangeOfString("UIButtonLabel") != nil) {

                        if let label = view as? UILabel {

                            label.textColor = YOUR COLOUR
                        }

                    }
                }
            }
        }
    }

}

Upvotes: 8

ToddB
ToddB

Reputation: 2520

There is indeed a way to change the title color of the UITableViewRowAction. It's a button. You can use the UIButton appearance proxy:

[[UIButton appearance] setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal];

enter image description here

Upvotes: 5

Tankista
Tankista

Reputation: 1770

There is one way that can achieve what you are looking for. But it is little tricky though.

Example of result:

enter image description here

The idea of this trick is that you can actually modify background color. This means that you can set UIColor's +colorWithPatternImage: and set an bitmap image that match desired styling. This can be achieved either by creating image using graphic editor or by rendering it using for example Core Graphics. The only problem with this solution is, that you have to mimic original title length with you specific text attributes to make it work properly and also you must set title for table view row action as string of white spaces so that table view cell will prepare enough space for you "custom action button". Creating static png assets in photoshop may be inappropriate if you use variable cell rows.

This is category for NSString that creates string of empty spaces that will create space for your custom button and second will generate bitmap image that will be placed as background pattern image. For parameters you must set text attributes for original title, that is basically @{NSFontAttributeName: [UIFont systemFontOfSize:18]}, than your desired text attributes. Maybe there is better way to achieve this :)

@implementation NSString (WhiteSpace)

- (NSString *)whitespaceReplacementWithSystemAttributes:(NSDictionary *)systemAttributes newAttributes:(NSDictionary *)newAttributes
{
    NSString *stringTitle = self;
    NSMutableString *stringTitleWS = [[NSMutableString alloc] initWithString:@""];

    CGFloat diff = 0;
    CGSize  stringTitleSize = [stringTitle sizeWithAttributes:newAttributes];
    CGSize stringTitleWSSize;
    NSDictionary *originalAttributes = systemAttributes;
    do {
        [stringTitleWS appendString:@" "];
        stringTitleWSSize = [stringTitleWS sizeWithAttributes:originalAttributes];
        diff = (stringTitleSize.width - stringTitleWSSize.width);
        if (diff <= 1.5) {
            break;
        }
    }
    while (diff > 0);

    return [stringTitleWS copy];
}

@end

Second important part is code that renders bitmap that can be used as pattern image for UITableViewRowAction's backgroundColor.

- (UIImage *)imageForTableViewRowActionWithTitle:(NSString *)title textAttributes:(NSDictionary *)attributes backgroundColor:(UIColor *)color cellHeight:(CGFloat)cellHeight
{
    NSString *titleString = title;
    NSDictionary *originalAttributes = @{NSFontAttributeName: [UIFont systemFontOfSize:18]};
    CGSize originalSize = [titleString sizeWithAttributes:originalAttributes];
    CGSize newSize = CGSizeMake(originalSize.width + kSystemTextPadding + kSystemTextPadding, originalSize.height);
    CGRect drawingRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, cellHeight));
    UIGraphicsBeginImageContextWithOptions(drawingRect.size, YES, [UIScreen mainScreen].nativeScale);
    CGContextRef contextRef = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(contextRef, color.CGColor);
    CGContextFillRect(contextRef, drawingRect);

    UILabel *label = [[UILabel alloc] initWithFrame:drawingRect];
    label.textAlignment = NSTextAlignmentCenter;
    label.attributedText = [[NSAttributedString alloc] initWithString:title attributes:attributes];
    [label drawTextInRect:drawingRect];

    //This is other way how to render string 
    //    CGSize size = [titleString sizeWithAttributes:attributes];
    //    CGFloat x =  (drawingRect.size.width - size.width)/2;
    //    CGFloat y =  (drawingRect.size.height - size.height)/2;
    //    drawingRect.origin = CGPointMake(x, y);
    //    [titleString drawInRect:drawingRect withAttributes:attributes];

    UIImage *returningImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return returningImage;
}

And then when you are creating your row action you can do something like this:

NSDictionary *systemAttributes = @{ NSFontAttributeName: [UIFont systemFontOfSize:18] };
NSDictionary *newAttributes = @{NSFontAttributeName: [UIFont fontWithName:@"Any font" size:15]};
NSString *actionTitle = @"Delete";
NSString *titleWhiteSpaced = [actionTitle whitespaceReplacementWithSystemAttributes:systemTextFontAttributes newAttributes:newAttributes];
UITableViewRowAction *rowAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:titleWhiteSpaced handle:NULL];
UIImage *patternImage = [self imageForTableViewRowActionWithTitle:actionTitle textAttributes:newAttributes backgroundColor:[UIColor redColor] cellHeight:50];
rowAction.backgroundColor = [UIColor colorWithPatternImage:patternImage]; 

This solution is obviously hacky but you can achieve desired results without using private APIs and in future if something breaks, this will not break your app. Only button will look inappropriate.

Example of result:

enter image description here

This code has of course a lot of space for improvements, any suggestions are appreciated.

Upvotes: 19

Thomas Keuleers
Thomas Keuleers

Reputation: 6115

I'm afraid that there's no way to change the title color of the UITableViewRowAction.

The only things you can change on the action are:

  • backgroundColor
  • style (destructive (red backgroundcolor, ...)
  • title

For more info, please refer to the Apple Doc UITableViewRowAction

Upvotes: 13

Related Questions