How to set color of templated image in NSTextAttachment

五迷三道 提交于 2019-12-03 09:37:05

It seems that there's a bug in UIKit. There's a workaround for that ;]

For some reason you need to append empty space before image attachment to make it work properly with UIImageRenderingModeAlwaysTemplate.

So your snippet would look like that (mine is in ObjC):

- (NSAttributedString *)attributedStringWithValue:(NSString *)string image:(UIImage *)image {
    NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
    attachment.image = image;

    NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:attachment];
    NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[[NSAttributedString alloc] initWithString:@" "]];
    [mutableAttributedString appendAttributedString:attachmentString];
    [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:0] range:NSMakeRange(0, mutableAttributedString.length)]; // Put font size 0 to prevent offset
    [mutableAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, mutableAttributedString.length)];
    [mutableAttributedString appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]];

    NSAttributedString *ratingText = [[NSAttributedString alloc] initWithString:string];
    [mutableAttributedString appendAttributedString:ratingText];
    return mutableAttributedString;
}
Steffen D. Sommer

I have good experience with using the library UIImage+Additions when tinting UIImage's. You can find it here: https://github.com/vilanovi/UIImage-Additions. Specially check section IV.

If adding a third-party library is not an option, here is something to get you started:

- (UIImage *)colorImage:(UIImage *)image color:(UIColor *)color
{
    UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextTranslateCTM(context, 0, image.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);

    CGContextSetBlendMode(context, kCGBlendModeNormal);
    CGContextDrawImage(context, rect, image.CGImage);
    CGContextSetBlendMode(context, kCGBlendModeSourceIn);
    [color setFill];
    CGContextFillRect(context, rect);


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

    return coloredImage;
}

This will make an UIImage go from:

To

Update: Swift version:

func colorImage(with color: UIColor) -> UIImage? {
    guard let cgImage = self.cgImage else { return nil }
    UIGraphicsBeginImageContext(self.size)
    let contextRef = UIGraphicsGetCurrentContext()

    contextRef?.translateBy(x: 0, y: self.size.height)
    contextRef?.scaleBy(x: 1.0, y: -1.0)
    let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)

    contextRef?.setBlendMode(CGBlendMode.normal)
    contextRef?.draw(cgImage, in: rect)
    contextRef?.setBlendMode(CGBlendMode.sourceIn)
    color.setFill()
    contextRef?.fill(rect)

    let coloredImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return coloredImage
}

The solution by @blazejmar works, but is unnecessary. All you need to do for this to work is set the color after the attributed strings have been connected. Here's an example.

NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
attachment.image = [[UIImage imageNamed:@"ImageName"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];

NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:attachment];

NSString *string = @"Some text ";
NSRange range2 = NSMakeRange(string.length - 1, attachmentString.length);

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string];
[attributedString appendAttributedString:attachmentString];
[attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:range2];
self.label.attributedText = attributedString;

I found a better solution. Make sure that plain text is in the first item。If the NSTextAttachment(image) is the first item, you can insert a space string before the NSTextAttachment. Code like this:

// creat image attachment
NSTextAttachment *imagettachment = [[NSTextAttachment alloc] init];
imagettachment.image = [[UIImage imageNamed:@"ImageName"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
NSAttributedString *imageAttchString = [NSAttributedString attributedStringWithAttachment:attachment];

// creat attributedString
NSString *string = @"Some text ";
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string];

// insert image 
[attributedString insertAttributedString:imageAttchString atIndex:0];
[attributedString insertAttributedString:[[NSAttributedString alloc] initWithString:@" "] atIndex:0];

label.attributedText =  attributedString;

// used
label.textColor = [UIColor redColor];
// or
label.textColor = [UIColor greenColor];

I use this NSMutableAttributedString extension for Swift.

import UIKit

extension NSMutableAttributedString {
    func addImageAttachment(image: UIImage, font: UIFont, textColor: UIColor, size: CGSize? = nil) {
        let textAttributes: [NSAttributedString.Key: Any] = [
            .strokeColor: textColor,
            .foregroundColor: textColor,
            .font: font
        ]

        self.append(
            NSAttributedString.init(
                //U+200C (zero-width non-joiner) is a non-printing character. It will not paste unnecessary space.
                string: "\u{200c}",
                attributes: textAttributes
            )
        )

        let attachment = NSTextAttachment()
        attachment.image = image.withRenderingMode(.alwaysTemplate)
        //Uncomment to set size of image. 
        //P.S. font.capHeight sets height of image equal to font size.
        //let imageSize = size ?? CGSize.init(width: font.capHeight, height: font.capHeight)
        //attachment.bounds = CGRect(
        //    x: 0,
        //    y: 0,
        //    width: imageSize.width,
        //    height: imageSize.height
        //)
        let attachmentString = NSMutableAttributedString(attachment: attachment)
        attachmentString.addAttributes(
            textAttributes,
            range: NSMakeRange(
                0,
                attachmentString.length
            )
        )
        self.append(attachmentString)
    }
}

This is how to use it.

let attributedString = NSMutableAttributedString()
if let image = UIImage.init(named: "image") {
    attributedString.addImageAttachment(image: image, font: .systemFont(ofSize: 14), textColor: .red)
}

You can also change addImageAttachment's parameter image: UIImage to image: UIImage? and check the nullability in extension.

use UIImageRenderingModeAlwaysOriginal for original image color. UIImageRenderingModeAlwaysTemplate + set tint color for custom color.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!