How can I set the color of a templated image that is an attachment on an attributed string?
Background:
I've got a UILabel and I'm setting its attributedText to an NSAttributedString. The NSAttributedString includes an NSTextAttachment with a small image. Now I want to make my image color match the text color and I can't figure out how to make it work.
I would normally expect to color the image by setting its rendering mode to UIImageRenderingModeAlwaysTemplate and then setting the tintColor on the containing UIView. I've tried setting the tintColor on my UILabel but that has no effect.
Here's my code. It's in Ruby (RubyMotion) so the syntax might look a little funny, but it maps 1:1 with 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
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;
}
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.
来源:https://stackoverflow.com/questions/29041458/how-to-set-color-of-templated-image-in-nstextattachment