Jens Schwarzer
Jens Schwarzer

Reputation: 2870

Set custom font for UITableView swipe action (UIContextualAction)

How do you set a custom font for the title in UIContextualAction?

I have tried UIAppearance but without any luck...

Cheers! :)

Upvotes: 3

Views: 7163

Answers (5)

Denim
Denim

Reputation: 21

I found a very simple method that helped me All you have to do is create your own View and add a label, image or whatever you want

Outwardly customize everything in this view and then convert it to an image using the code below Next, add an image to the action button and that's it

deleteAction.image = createDeleteImage() /// this is my function that creates a View and then using the extension below, it converts it into an image

Before doing this, don't forget to make the title of the button equal to the empty string ""

extension UIView {

    // Using a function since `var image` might conflict with an existing variable
    // (like on `UIImageView`)
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

How to convert a UIView to an image

Upvotes: 1

Brandon Stillitano
Brandon Stillitano

Reputation: 1638

Updated for Swift 5 & iOS 13

The following function allows you to set a font as well as a tint color to your swipe actions.

To get started, add an extension to UITableView.

extension UITableView {
    /// Iterates over all subviews of a `UITableView` instance and applies the supplied font to all labels withing the UISwipeAction's array.
    /// - Parameter font: The font that should be applied to the labels.
    /// - Parameter tintColor: The tint color that should be applied to image views and labels
    /// - Parameter ignoreFirst: Whether or not the first swipe action should be ignored when applying tints
    public func setSwipeActionFont(_ font: UIFont, withTintColor tintColor: UIColor? = nil, andIgnoreFirst ignoreFirst: Bool = false) {
        for subview in self.subviews {
            //Confirm that the view being touched is within a swipe container
            guard NSStringFromClass(type(of: subview)) == "_UITableViewCellSwipeContainerView" else {
                continue
            }

            //Re-iterate subviews and confirm that we are touching a swipe view
            for swipeContainerSubview in subview.subviews {
                guard NSStringFromClass(type(of: swipeContainerSubview)) == "UISwipeActionPullView" else {
                    continue
                }

                //Enumerate subviews and confirm that we are touching a button
                for (index, view) in swipeContainerSubview.subviews.filter({ $0 is UIButton }).enumerated() {
                    //Set Font
                    guard let button = view as? UIButton else {
                        continue
                    }
                    button.titleLabel?.font = font
                    
                    //Set Tint Conditionally (based on index)
                    guard index > 0 || !ignoreFirst else {
                        continue
                    }
                    button.setTitleColor(tintColor, for: .normal)
                    button.imageView?.tintColor = tintColor
                }
            }
        }
    }
}

Within your delegate, add the following functionality to your UITableViewDataSource.WillBeginEditing(UITableView, IndexPath) method. Replace the font and colour params as you need. Tint colour is optional.

self?.tableView.setSwipeActionFont(.systemFont(ofSize: 24.0, withTintColor: .systemRed)

Upvotes: 2

Pratik
Pratik

Reputation: 2399

FOR OBJECTIVE C

Create a method

- (UIImage *)createImageFromLabel:(UILabel *)label
{
    UIGraphicsBeginImageContext(label.bounds.size);

    [label.layer renderInContext:UIGraphicsGetCurrentContext()];;

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

    return image;
}

This method will return image from your label.

Now in tableview datasource method

- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UIContextualAction *action = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:nil handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
        
        //Your code here
    }];

    action.backgroundColor = [UIColor redColor];

    // Create label as you want view for delete button
    UILabel *label = [[UILabel alloc] init];
    label.font = [UIFont systemFontSize:14];
    label.text = @"Your Text";
    label.textColor = [UIColor whiteColor];
    [label sizeToFit];
    UIImage *image =  [self createImageFromLabel:label];

    action.image = image;

    UISwipeActionsConfiguration *actions = [UISwipeActionsConfiguration configurationWithActions:@[action]];

    return actions;
}

Upvotes: -1

David
David

Reputation: 147

I recently found out a way to do this by using the button titleLabel instead of the image property, so that you keep the ability to have an action with text and image.

As you will see, we need to do something awkward...


func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {

    if #available(iOS 13.0, *) {
        for subview in tableView.subviews {
            if NSStringFromClass(type(of: subview)) == "_UITableViewCellSwipeContainerView" {
                for swipeContainerSubview in subview.subviews {
                    if NSStringFromClass(type(of: swipeContainerSubview)) == "UISwipeActionPullView" {
                        for case let button as UIButton in swipeContainerSubview.subviews {
                            button.titleLabel?.font = .systemFont(ofSize: 12)
                        }
                    }
                }
            }
        }
    } else {
        for subview in tableView.subviews {
            if NSStringFromClass(type(of: subview)) == "UISwipeActionPullView" {
                for case let button as UIButton in subview.subviews {
                    button.titleLabel?.font = .systemFont(ofSize: 12)
                }
            }
        }
    }
 }

Upvotes: 8

Jens Schwarzer
Jens Schwarzer

Reputation: 2870

I have found a way to do this by using the image property instead of the title...

Standard font (Remove/Rename)

Before/standard font

Custom font (Remove/Rename)

After/custom font

To create an image of a label I have this extension:

extension UIImage {

    /// This method creates an image of a view
    convenience init?(view: UIView) {

        // Based on https://stackoverflow.com/a/41288197/1118398
        let renderer = UIGraphicsImageRenderer(bounds: view.bounds)
        let image = renderer.image { rendererContext in
            view.layer.render(in: rendererContext.cgContext)
        }

        if let cgImage = image.cgImage {
            self.init(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .up)
        } else {
            return nil
        }
    }
}

And then I simply have:

override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

    let action = UIContextualAction(style: .destructive, title: nil) { action, view, completion in
        // Your swipe action code!
    }
    let label = UILabel()
    label.text = // Your swipe action text!
    label.font = // Your custom font!
    label.sizeToFit()
    action.image = UIImage(view: label)

    return UISwipeActionsConfiguration(actions: [action])
}

Upvotes: 15

Related Questions