Skip to content

Set tint color on an image in a NSAttributedString

Posted in Development, and Tutorials

(2018-08-23 EDIT) Updated the post and re-created the code from almost scratch using XCode 10.0 beta 6 and iOS 12. Code written using Swift 4. (/EDIT).

Introduction

Hi everyone, I have been working, for a few days now, on a project that requires to make an app fully customisable from a configuration file. I was coding and coding and coding, extracting color definitions, applying tints on images, when I ran into an issue. I could not apply tint over a mutable attributed string, nor simple attributed string for that matter. So I was there, looking at my NSAttributedString and my NSTextAttachment without any property allowing me to change only the image color.

It turns out that you actually can apply a color on your attributed string, I don’t know about the underlying magic when using attributed but it seems that you can apply your text color on an image embedded via your text attachment.

Let’s get started

Setting up the UI

First you start by creating a new single view app project.

Create single view application

Next, you need to untick all checkboxes as we do not need UI nor unit tests for this tutorial.

setting up project
Setting up the project.

Then you go on your main storyboard (Main.storyboard) and add a label, that you will link to your view controller code (here as labelWithColoredImage).

create label
Create label

Here is what your code should look like:

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var labelWithColoredImage: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
}

And that’s it terms of UI setup for now. Now you can run your app to be sure it’s behaving properly and that the label appears.

first run
Yay it runs

Now the basic NSAttributedString implementation

Now let’s get our hands dirty with the coding part. First let’s update the text and change its color only using the textColor property only. Let’s add the following lines in the viewDidLoad function and run the app again.

labelWithColoredImage.text = "That text written at 4:20AM for reasons.";
labelWithColoredImage.textColor = .redColor();
First attempt with red color with no magic of NSAttributedString
First attempt with red color 

(2018-08-23 EDIT) I left the original image and text despite updating everything else because I like the singularity of when that happened (/EDIT)

Then we can observe that the text is red. One minor issue is that it doesn’t fit the screen so we can add contraints to the label to center it and make it responsive. 

(2018-08-23 EDIT) I am realising now that I had been a complete tool by not helping with a screenshot showing how to set constraints. Luckily for you it is much more intuitive to have the constraints you want in 2018 than back in 2016. (/EDIT) 

Let’s add the text attachment magic now. It’s time to add an image to the project, I just added the image “swift_logo.png” in the project, drag-n-drop works as well as right clicking in the project and selecting “Add files to “.

Image added to the project
Image added to the project

(2018-08-23 EDIT) That bit is the same, what I did not know then is that I could just drag the image file straight into the assets of the app. I don’t think I bothered but it was probably already a thing in 2016. (/EDIT) 

Let’s create our attributed string now with the text attachment, I will have the text over two lines to show how the image is now part of it and due to the image color I change the text color to blue so you can see the difference.

//set the label text color as red
labelWithColoredImage.textColor=.blueColor()

//load the image
let image:UIImage=UIImage(named:"swift_logo")!

//create the text attachment
let textAttachment:NSTextAttachment=NSTextAttachment()

//assign the loaded image to the text attachment
textAttachment.image=image

//create an attributed string with our text attachment
let attributedStringWithImage:NSAttributedString=NSAttributedString(attachment:textAttachment);

//now create the full string to display
let stringToDisplay="That text written at 4:20AM\nfor reasons."
let fullAttributedString:NSMutableAttributedString=
NSMutableAttributedString(string:"\(stringToDisplay)")

//append space the string to display with our string containing the image
fullAttributedString.appendAttributedString(attributedStringWithImage)

//color the image part of the string

//assign it to the label
labelWithColoredImage.attributedText=fullAttributedString;

(2018-08-23 EDIT) Hahaha wow that is so bad. The comments, the spacing, the noise etc. Please do not use any of the previous bit of code. Especially if you want it to compile. I did compile and work back in 2016 but boy I must have been really tired not to notice the first comment being an outright lie. Since then I read Clean Code as mentioned in a more recent post and do not care for comments as much. The comments part was true for some time but even more since that book read. Well, long as the code is clear and self-explanatory. Which it is. Let me refresh that below. (/EDIT)

labelWithColoredImage.textColor = .blue  
labelWithColoredImage.textAlignment = .center
        
if let image = UIImage(named:  "swift_logo", in: Bundle.main, compatibleWith: nil) {
       let textAttachment = NSTextAttachment()
       textAttachment.image = image
            
       let attributedStringWithImage = NSAttributedString(attachment: textAttachment);
            
       let stringToDisplay = "That text written at 4:20AM\nfor reasons."
            
       let fullAttributedString = NSMutableAttributedString(string:"\(stringToDisplay) ")
       fullAttributedString.append(attributedStringWithImage)
            
       labelWithColoredImage.attributedText = fullAttributedString
}

(2018-08-23 EDIT) Ok, I had a little dinner break but then I got stuck about an hour for the dumbest reason. I missed that the default for labels is 1 line, so the second line “for reasons” was missing. Missing as in I thought the image was not loading. Googled around and stuff until realising that a part of the string was missing. A whooooole hour later. To be fair I have been travelling a lot over the past few weeks for work, the flights definitely take their toll on me. And yes I am writing this post from a hotel in central Glasgow. (/EDIT).

Another run!

NSAttributedString before color
Blue text, original image. The full power of a NSAttributedString. Full? Almost.

There you have it! Your image integrated within text. Now let’s check what happens if you render your image as a template. 

(2018-08-23 EDIT) You need to replace this line `let image: =UIImage(named:”swift_logo”)!  with let image: UIImage = UIImage(named: “swift_logo”)!.imageWithRenderingMode(.AlwaysTemplate) then run the app again.  Since the structure changed a bit since last time, you need to add a new variable templateImage like below. Next, you will use it as the text attachment image and run your app again. (/EDIT)

let templateImage = image.withRenderingMode(.alwaysTemplate)
let textAttachment = NSTextAttachment()
textAttachment.image = templateImage
blue
Bluuuuue like that song from Chaos Chaos

Giving the image a color of its own different from the source one

Now the image is the same color as the text, for some of you it could be enough but we can go a little bit further by giving specifically to the image a color of its own. You can add the following lines before the assignment of the NSAttributedString to labelWithColoredImage.

fullAttributedString.addAttribute(
     NSAttributedString.Key.foregroundColor,
     value: UIColor.green,
     range: NSMakeRange(stringToDisplay.count, attributedStringWithImage.length))

Basically these lines say to color in green the character from the index matching the character after stringToDisplay for as long as the text attachment length. Now let’s run the app again.

green and blue nsattributedstring color
Adding some green to ligthen your blue

And there you are, you know know how to apply a different color to an image through a NSAttributedString.

Conclusion

The source code is available on Github

There is one thing to keep in mind though, there must always be a space before the text attachment containing the image in you NSAttributedString otherwise it will not work. I am not quite sure wether the need to add a space is a bug or if it is actually given access to a hidden feature. Either way, happy coding !

(2018-08-23 EDIT) Here we are, this is the first post I update a while after the first publication. It was pretty fun an exercise to take onon and I hope you enjoy it and find it as relevant as a couple years back. (/EDIT).

One Comment

    Leave a Reply

    This site uses Akismet to reduce spam. Learn how your comment data is processed.

    %d bloggers like this: