How to change UITableViewRowAction title color?
Asked Answered
D

11

23

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.

Dido answered 12/11, 2014 at 12:56 Comment(3)
RowAction means what? You can use cell.titlelabe.color for it. is it helpful for you?Kingbolt
From iOS 8, sdk allows us to use a new delegate method "editActionsForRowAtIndexPath". This method will be triggered once we swipe the tableviewcell. So using UITableViewAction we will access the "delete button" on swiping.Dido
ohk...are you not able to change tiltelable text color?Kingbolt
S
13

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

Sleeve answered 12/11, 2014 at 13:20 Comment(2)
I checked that even. But still, I would like to know is there any other way of customising it. Thank you in advance.Dido
See my answer below that relies on UITableViewRowAction being a UIButton. There is indeed a way.Roughrider
O
19

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.

Obreption answered 28/11, 2014 at 0:51 Comment(3)
what size I need to set for the image ?Burdett
Image size must be exactly the size of the button.Obreption
That is brilliant.Eliath
S
13

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

Sleeve answered 12/11, 2014 at 13:20 Comment(2)
I checked that even. But still, I would like to know is there any other way of customising it. Thank you in advance.Dido
See my answer below that relies on UITableViewRowAction being a UIButton. There is indeed a way.Roughrider
C
8

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
                        }

                    }
                }
            }
        }
    }

}
Coventry answered 22/3, 2016 at 3:40 Comment(3)
You could also configure the button's label one level above, instead of for view in subview2.subviews, something like if let button = subview2 as? UIButton { button.setTitleColor(yourColour, forState: .Normal) }. Or button.titleLabel?.font = yourCustomFont :]Eloyelreath
Solution confirmed on iOS 10.3Iceboat
@HassanTaleb I just tried this in Xcode 9 in iOS 11 simulator and this hack no longer works. So updating to Swift 4 isn't going to help you much.Incommunicado
M
6

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.

Maecenas answered 19/5, 2020 at 18:13 Comment(5)
Thank you for sharing this. I have adopted your solution and can confirm that it is working (Swift 5.3, Xcode 12.2)... still I am confused that you cannot "officially" change the text color, especially when supporting dark mode in an app.Majolica
There is a lot of things in UIKit that I wish would be customisable, but unfortunately apple left us with nothing in many cases.Maecenas
Seemed promising but it does not really work. With Swift 5, it causes text color to revert to the default color when quickly swiping the same cell multiple times. Also, I noticed this does not change a second UIContextualAction text color.Garvey
cellActionButtonLabel is always nil in Xcode 12.3Tarbox
@Tarbox its only nil in the default state of the cell, put on a breakpoint on the line when you call it and the swipe the cell. It will return a proper value. I have just tested that on Xcode 12.4Maecenas
R
5

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

Roughrider answered 10/12, 2014 at 18:50 Comment(5)
Thanks, This is working for me. I have done below code UITableViewRowAction *button = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@" " handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) { NSLog(@"Action to perform with Button 1"); }]; button.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"XYZ_PNG"]]; [[UIButton appearance] setTitleColor:[UIColor redColor] forState:UIControlStateNormal];Skiascope
This is a fun trick, but it changes the appearance for ALL UIButtons from then on. As a result, the results are often undesirable as many other buttons in your UI are changed.Sulfaguanidine
if i have 2 buttons and need 2 differents color for each, how work this?Ashleeashleigh
@Ashleeashleigh I don't think it is possible using this techniqueRoughrider
@Sulfaguanidine The results may not suit all applications, but it is exactly the behavior that my application requires. For me it is more than a fun trick.Roughrider
H
5

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

                            }

                        }

                    }

                }

            }

        }

    }

}
Hermaphroditus answered 22/4, 2017 at 20:21 Comment(5)
perfect answer !!Vishinsky
Can you update this code to Swift 4 please? @david-seekVishinsky
okay will do within the next 72 hours. need to get xcode 9 first. on my way across the earth right now. will do as soon as i have decent wifi @HassanTalebHermaphroditus
Using String(describing: sub) for identification is not that reliable since Apple, or event your colleage, can change an object's describing anytime. Read more about String.init(describing:) Here is the case that Apple has changed the description of NSData.Sheepshanks
Of course it's not perfect. But as there is no API that's the way we have to do it to have a workaround and be able to accomplish our goal.Hermaphroditus
C
3

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.

Continuum answered 9/4, 2018 at 15:49 Comment(0)
K
1

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
}
Keturahkeung answered 8/6, 2017 at 7:54 Comment(0)
W
1

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
}
Wingding answered 22/10, 2021 at 5:17 Comment(0)
D
0
-(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];
Delamare answered 10/1, 2017 at 15:29 Comment(1)
"[[UIButton appearance] setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];" code change your all button text color which are in the appCrud
R
0

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.

Rhodian answered 22/11, 2018 at 14:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.