How to set color of templated image in NSTextAttachment
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
instances. 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 a UIImage
go from:
To
Update: Swift version:
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 }}
I use this NSMutableAttributedString
extension for Swift.
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.