如何在NSTextAttachment中设置模板图片的颜色

35

如何设置属性字符串中作为附件的模板图像的颜色?

背景:

我有一个UILabel并将其attributedText设置为NSAttributedString。NSAttributedString包括一个带有小图像的NSTextAttachment。现在我想让我的图像颜色与文本颜色匹配,但我无法弄清楚如何使其工作。

通常我会通过将渲染模式设置为UIImageRenderingModeAlwaysTemplate并设置包含UIView的tintColor来着色图像。我尝试在我的UILabel上设置tintColor,但没有效果。

这是我的代码。它是用Ruby(RubyMotion)编写的,因此语法可能看起来有些奇怪,但它可以1:1地映射到Objective C。

attachment = NSTextAttachment.alloc.initWithData(nil, ofType: nil)
attachment.image = UIImage.imageNamed(icon_name).imageWithRenderingMode(UIImageRenderingModeAlwaysTemplate)

label_string = NSMutableAttributedString.attributedStringWithAttachment(attachment)
label_string.appendAttributedString(NSMutableAttributedString.alloc.initWithString('my text', attributes: { NSFontAttributeName => UIFont.preferredFontForTextStyle(UIFontTextStyleFootnote), NSForegroundColorAttributeName => foreground_color }))

label = UILabel.alloc.initWithFrame(CGRectZero)
label.tintColor = foreground_color
label.attributedText = label_string
label.textAlignment = NSTextAlignmentCenter
label.numberOfLines = 0

你有没有找到任何不需要在添加为文本附件之前绘制图像的答案?我很想有一个简单的方法来做到这一点。 - Tim Johnsen
很遗憾,我的情况不是这样的。我有一个字符串,有时是白色背景上的黑色字体,有时是黑色背景上的白色字体……最终我只好将附件图像更改为在两种背景下都能正常显示的中性灰色。 - Ghazgkull
8个回答

43

看起来UIKit有一个bug,但是有一个解决方法;]

出于某种原因,您需要在图像附件之前添加空格,以便在使用UIImageRenderingModeAlwaysTemplate时它能正常工作。

所以您的代码片段应该是这样的(我的是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;
}

这似乎是正确的,而且这是有用的信息。我想我会选择着色图像的方法,但知道这个信息很好。 - David Snabel-Caunt
1
我发现NSForegroundColorAttributeName没有给附加的图片着色。相反,它是从UILabeltextColor属性中获取的,即使在属性字符串中被覆盖。 - Samah
4
您可以使用空字符串代替空格,因为空格会被添加到布局中。[[NSMutableAttributedString alloc] initWithAttributedString:[[NSAttributedString alloc] initWithString:@"\0"]]; - railwayparade
最重要的是,必须从任何字母(但不是该附件)开始使用带有附件的着色属性字符串。@railwayparade的解决方案和评论解释了这个解决方法。 - malex
从一个空字符串("\0")开始似乎对我不起作用。我必须将其替换为实际的空格(" "),才能使图像着色。 - Rodrigo Sieiro

19
我使用UIImage+Additions库对UIImage进行着色有很好的经验。特别是要检查第四部分。

如果不能添加第三方库,这里有一些开始的东西:

- (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;
}

这将使一个UIImage从:

未着色的UIImage

变为

着色后的UIImage

更新:Swift版本:

extension UIImage {
    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
    }
}

谢谢指针。希望我不必引入整个新库来解决这个问题。 - Ghazgkull
@Ghazgkull 已添加了一个代码示例,让您开始。如果这不是您想要的着色方式,请告诉我更多信息。 - Steffen D. Sommer
通常我会通过调用UIImage.imageWithRenderingMode(UIImageRenderingModeAlwaysTemplate)并设置包含UIView的tintColor来对图像进行着色。这只需要两行代码。唯一的问题是,我的UIImage在NSAttributedString中,因此我无法访问包含它的直接UIView。 - Ghazgkull
顺便说一下,@Ghazgkull,整个链接库只有49 KB!包括它可以提供很多功能而代码量很小 :) - Ky -
4
我注意到应用这些更改后图像有点模糊。你知道如何解决吗?我正在使用PDF文件。 - C0D3

7

我使用这个Swift的NSMutableAttributedString扩展。

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)
    }
}

这是使用它的方法。

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

您还可以将addImageAttachment的参数image:UIImage更改为image:UIImage?,并在扩展中检查空值。


7
let imageAttachment = NSTextAttachment()
imageAttachment.image = UIImage(systemName: "magnifyingglass")
imageAttachment.image = imageAttachment.image?.withTintColor(UIColor(red: 1.00, green: 1.00, blue: 0.00, alpha: 1.00))

这应该可以工作(iOS 13及以上版本)


它确实有效!!! - Codetard
最好和最简单的答案。 - Rob

4
在iOS 12上,我们需要在图像之前插入一个字符,并设置该字符的前景色。但是,在iOS 13上,我们可以直接在包含NSTextAttachment的NSAttributedString上设置前景色。
我已在iOS 12.3和iOS 13.3.1上测试了以下扩展。
extension NSMutableAttributedString {
    @discardableResult
    func sbs_append(_ image: UIImage, color: UIColor? = nil) -> Self {
        let attachment = NSTextAttachment()
        attachment.image = image
        let attachmentString = NSAttributedString(attachment: attachment)
        if let color = color {
            if #available(iOS 13, *) {} else {
                // On iOS 12 we need to add a character with a foreground color before the image,
                // in order for the image to get a color.
                let colorString = NSMutableAttributedString(string: "\0")
                colorString.addAttributes([.foregroundColor: color], range: NSRange(location: 0, length: colorString.length))
                append(colorString)
            }
            let attributedString = NSMutableAttributedString(attributedString: attachmentString)
            if #available(iOS 13, *) {
                // On iOS 13 we can set the foreground color of the image.
                attributedString.addAttributes([.foregroundColor: color], range: NSRange(location: 0, length: attributedString.length))
            }
            append(attributedString)
        } else {
            append(attachmentString)
        }
        return self
    }
}

因为某些原因,在 iOS 12.4 上为我添加一个 \0 字符不起作用。任何非空字符,如空格或其他任何字符都起作用。但它会留下不必要的空格,尤其是在附件位于字符串开头时更为明显。我使用了 @6055194 答案 中的 \u{200c} 并成功解决了问题。 - NKorotkov

1
@blazejmar提供的解决方案是可行的,但不必要。您只需在属性字符串连接后设置颜色即可使其工作。这里有一个例子。
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;

1
它能够工作是因为你的字符串不是从附件开始的。请参考我的评论@blazejmar的解决方案。 - malex

0
我找到了一个更好的解决方案。请确保纯文本是第一项。如果NSTextAttachment(图像)是第一项,则可以在NSTextAttachment之前插入一个空格字符串。
// create image attachment
NSTextAttachment *imageAttachment = [[NSTextAttachment alloc] init];
imageAttachment.image = [[UIImage imageNamed:@"ImageName"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
NSAttributedString *imageAttchString = [NSAttributedString attributedStringWithAttachment:attachment];

// create 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];

-3

使用UIImageRenderingModeAlwaysOriginal以保留原始图像颜色。UIImageRenderingModeAlwaysTemplate + 设置着色颜色以自定义颜色。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接