How to set color of templated image in NSTextAttachment

后端 未结 7 1052
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-29 20:30

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 attributedTe

相关标签:
7条回答
  • 2020-12-29 20:45

    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;
    
    0 讨论(0)
  • 2020-12-29 20:47

    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.

    0 讨论(0)
  • 2020-12-29 20:58

    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;
    }
    
    0 讨论(0)
  • 2020-12-29 21:00

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

    0 讨论(0)
  • 2020-12-29 21:05

    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.

    // 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];
    
    0 讨论(0)
  • 2020-12-29 21:09

    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
        }
    }
    
    0 讨论(0)
提交回复
热议问题