How do I add text to an image in iOS Swift?
Asked Answered
S

9

102

I have looked around and have been unsuccessful at figuring out how take text, overlay it on an image, and then combine the two into a single UIImage.

I have exhausted Google using the search terms I can think of so if anyone has a solution or at least a hint they can point to it would be greatly appreciated.

Sandalwood answered 6/3, 2015 at 20:16 Comment(0)
S
221

I figured it out:

func textToImage(drawText: NSString, inImage: UIImage, atPoint: CGPoint) -> UIImage{
    
    // Setup the font specific variables
    var textColor = UIColor.whiteColor()
    var textFont = UIFont(name: "Helvetica Bold", size: 12)!

    // Setup the image context using the passed image
    let scale = UIScreen.mainScreen().scale
    UIGraphicsBeginImageContextWithOptions(inImage.size, false, scale)
    
    // Setup the font attributes that will be later used to dictate how the text should be drawn
    let textFontAttributes = [
        NSFontAttributeName: textFont,
        NSForegroundColorAttributeName: textColor,
    ]
    
    // Put the image into a rectangle as large as the original image
    inImage.drawInRect(CGRectMake(0, 0, inImage.size.width, inImage.size.height))

    // Create a point within the space that is as bit as the image
    var rect = CGRectMake(atPoint.x, atPoint.y, inImage.size.width, inImage.size.height)

    // Draw the text into an image
    drawText.drawInRect(rect, withAttributes: textFontAttributes)
    
    // Create a new image out of the images we have created
    var newImage = UIGraphicsGetImageFromCurrentImageContext()
    
    // End the context now that we have the image we need
    UIGraphicsEndImageContext()
    
    //Pass the image back up to the caller
    return newImage
    
}

To call it, you just pass in an image:

textToImage("000", inImage: UIImage(named:"thisImage.png")!, atPoint: CGPointMake(20, 20))

The following links helped me get this straight:

Swift - Drawing text with drawInRect:withAttributes:

How to write text on image in Objective-C (iOS)?

The original goal was to create a dynamic image that I could use in an AnnotaionView such as putting a price at a given location on a map and this worked out great for it.

For Swift 3:

 func textToImage(drawText text: NSString, inImage image: UIImage, atPoint point: CGPoint) -> UIImage {
    let textColor = UIColor.white
    let textFont = UIFont(name: "Helvetica Bold", size: 12)!

    let scale = UIScreen.main.scale
    UIGraphicsBeginImageContextWithOptions(image.size, false, scale)

    let textFontAttributes = [
        NSFontAttributeName: textFont,
        NSForegroundColorAttributeName: textColor,
        ] as [String : Any]
    image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))

    let rect = CGRect(origin: point, size: image.size)
    text.draw(in: rect, withAttributes: textFontAttributes)

    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return newImage!
 }

For Swift 4:

 func textToImage(drawText text: String, inImage image: UIImage, atPoint point: CGPoint) -> UIImage {
    let textColor = UIColor.white
    let textFont = UIFont(name: "Helvetica Bold", size: 12)!

    let scale = UIScreen.main.scale
    UIGraphicsBeginImageContextWithOptions(image.size, false, scale)

    let textFontAttributes = [
        NSAttributedStringKey.font: textFont,
        NSAttributedStringKey.foregroundColor: textColor,
        ] as [NSAttributedStringKey : Any]
    image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))

    let rect = CGRect(origin: point, size: image.size)
    text.draw(in: rect, withAttributes: textFontAttributes)

    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return newImage!
 }

For Swift 5:

func textToImage(drawText text: String, inImage image: UIImage, atPoint point: CGPoint) -> UIImage {
    let textColor = UIColor.white
    let textFont = UIFont(name: "Helvetica Bold", size: 12)!

    let scale = UIScreen.main.scale
    UIGraphicsBeginImageContextWithOptions(image.size, false, scale)

    let textFontAttributes = [
        NSAttributedString.Key.font: textFont,
        NSAttributedString.Key.foregroundColor: textColor,
        ] as [NSAttributedString.Key : Any]
    image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))

    let rect = CGRect(origin: point, size: image.size)
    text.draw(in: rect, withAttributes: textFontAttributes)

    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return newImage!
}
Sandalwood answered 6/3, 2015 at 21:18 Comment(11)
superhelpful! thanks for posting your code. and so well commented.Birgit
can those texts or their container rects response to tap events?Program
@JadeSync if the object you are importing the image into is clickable, then yes. But it isn't the text that is clickable, it is the container the final images is used within that would need to be clickable. In fact, that is what I did with this image because I needed a way to have a clickable marker on a map, and the marker needed to have text in an image. The marker has a click event delegate so it isn't the image they are clicking on but the marker that contains it.Sandalwood
If i set the point to the bottom right of the image and the text is too long it extends off the image. Any suggestions on how to get the text to align to the left of the point instead of extending off of the image to the right?Grass
I ran into that issue as well and frankly, it depends on the font type and size, and the size of the image, where you want the text to start in the image since you may not want to step on a PNG shadow. This was a trial-&-error thing that require me adding a character, figuring out how many pixels to move the start point to get it in, then do a quick calculation to figure the sliding start point based on the number of characters (up to a max based on how it looks). I didn't want to get too complicated, so I addressed the bare solution for my question.Sandalwood
what about CIImage? how to add text into CIImage without converting it to UIImageVictorie
anyway it's very slow, won't be good to use while recording a video and adding text on each frame, need a solution without UIImage, but directly adding text to CMSampleBuffer/CVPixelBufferVictorie
@ChristopherWadeCantley do you have a full example by any chance? I am trying your proposed texttoImage function but when it runs, it loads nothing. Let me know, thanks!Filtrate
how do we centre align the text in the image?Heeler
This code generates image with text is fine, but it is not generating proper image with text in iphone xsAx
Why use 'UIScreen.main.scale'? When I run the code, the scale comes out to 3. In this case the image is tripled, why not set it to 1??Celiotomy
C
23

My simple solution:

func generateImageWithText(text: String) -> UIImage? {
    let image = UIImage(named: "imageWithoutText")!

    let imageView = UIImageView(image: image)
    imageView.backgroundColor = UIColor.clear
    imageView.frame = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)

    let label = UILabel(frame: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
    label.backgroundColor = UIColor.clear
    label.textAlignment = .center
    label.textColor = UIColor.white
    label.text = text

    UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0)
    imageView.layer.render(in: UIGraphicsGetCurrentContext()!)
    label.layer.render(in: UIGraphicsGetCurrentContext()!)
    let imageWithText = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return imageWithText
}
Charie answered 6/5, 2016 at 16:6 Comment(3)
Thaks for code but how to adjust font size, every image size is different with different text length. How can I make this dynamic.Stanhope
I think you should use Autoshrink. Set max font size for max width of label and add: label.adjustsFontSizeToFitWidth = true & label.minimumScaleFactor = 0.5Charie
is there a way to add label text aligned to any of corners?Holds
B
11

You can also do a CATextLayer.

    // 1
let textLayer = CATextLayer()
textLayer.frame = someView.bounds

// 2
let string = String(
  repeating: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce auctor arcu quis velit congue dictum. ", 
  count: 20
)

textLayer.string = string

// 3
let fontName: CFStringRef = "Noteworthy-Light"
textLayer.font = CTFontCreateWithName(fontName, fontSize, nil)

// 4
textLayer.foregroundColor = UIColor.darkGray.cgColor
textLayer.isWrapped = true
textLayer.alignmentMode = kCAAlignmentLeft
textLayer.contentsScale = UIScreen.main.scale
someView.layer.addSublayer(textLayer)

https://www.raywenderlich.com/402-calayer-tutorial-for-ios-getting-started

Birgit answered 29/4, 2015 at 1:15 Comment(3)
Love Ray's stuff. Thanks for the alternative method!Sandalwood
Your adding a text layer to a view here, not to an imageFitzhugh
Worth mentioning if one is looking to vertically align the text see #4765961Swellhead
S
7

I have created an extension for using it everywhere :

import Foundation
import UIKit
extension UIImage {

    class func createImageWithLabelOverlay(label: UILabel,imageSize: CGSize, image: UIImage) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(CGSize(width: imageSize.width, height: imageSize.height), false, 2.0)
        let currentView = UIView.init(frame: CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height))
        let currentImage = UIImageView.init(image: image)
        currentImage.frame = CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)
        currentView.addSubview(currentImage)
        currentView.addSubview(label)
        currentView.layer.render(in: UIGraphicsGetCurrentContext()!)
        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img!
    }

}

Usage : Anywhere on your ViewController where you have the size and the label to add use it as follows -

let newImageWithOverlay = UIImage.createImageWithLabelOverlay(label: labelToAdd, imageSize: size, image: editedImage)
Shipshape answered 13/9, 2018 at 9:5 Comment(7)
Awesome! Thanks for contributing this.Sandalwood
It shows white background even if I used transparent imageEclat
What are you doing putting it on a view or uploading to server?Shipshape
Just take the image and shows it in image view. It shows white backgroundEclat
Your image view and its superview must have clear background. If you post it to server in png format then it will be as you want. Do that and check. Or try saving it locally once.Shipshape
doesn't work with clear background. set background color to .clear but doesn't wotkEclat
The reason is the superview of your superview will always have a color in the hierarchy so you wont be able to see that. Try putting it over any other image like a watermark then you must see the effectsShipshape
K
3

For swift 4:

func textToImage(drawText text: NSString, inImage image: UIImage, atPoint point: CGPoint) -> UIImage {


    let scale = UIScreen.main.scale
    UIGraphicsBeginImageContextWithOptions(image.size, false, scale)

    image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))

    let rect = CGRect(origin: point, size: image.size)

    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.alignment = .center



    let attrs = [NSAttributedStringKey.font: UIFont(name: "Helvetica Bold", size: 12)!,NSAttributedStringKey.foregroundColor : UIColor.white , NSAttributedStringKey.paragraphStyle: paragraphStyle]


    text.draw(with: rect, options: .usesLineFragmentOrigin, attributes: attrs, context: nil)



    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return newImage!
}
Kletter answered 19/10, 2017 at 21:3 Comment(0)
C
1

I can't see anything in your initial question suggesting that this must be done exclusively in code - so why not simply add a UILabel in interface builder, and add constraints to give it the same length and width as your image, center it vertically and horizontally (or however you need it placed), delete the label text, set the text font, size, colour, etc. as needed (including ticking Autoshrink with whatever minimum size or scale you need), and ensure it's background is transparent.

Then just connect it to an IBOutlet, and set the text in code as needed (e.g. in viewWillAppear, or by using a ViewModel approach and setting it on initialisation of your view/viewcontroller).

Cully answered 29/11, 2018 at 16:1 Comment(0)
P
0

I have tried this basic components. Hope it will work.

func imageWithText(image : UIImage, text : String) -> UIImage {

        let outerView = UIView(frame: CGRect(x: 0, y: 0, width: image.size.width / 2, height: image.size.height / 2))
        let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: outerView.frame.width, height: outerView.frame.height))
        imgView.image = image
        outerView.addSubview(imgView)

        let lbl = UILabel(frame: CGRect(x: 5, y: 5, width: outerView.frame.width, height: 200))
        lbl.font = UIFont(name: "HelveticaNeue-Bold", size: 70)
        lbl.text = text
        lbl.textAlignment = .left
        lbl.textColor = UIColor.blue

        outerView.addSubview(lbl)

        let renderer = UIGraphicsImageRenderer(size: outerView.bounds.size)
        let convertedImage = renderer.image { ctx in
            outerView.drawHierarchy(in: outerView.bounds, afterScreenUpdates: true)

        }
        return convertedImage
    }
Propriety answered 14/4, 2020 at 11:18 Comment(0)
S
0

It's also possible to use the QLPreviewController. Just save the imageFile to an url like the applicationsDocuments directory under the .userDomainMask and open the apple' editor. You can draw, add shapes, arrow and even your signature. I explained the implementation in detail in the following post: https://mcmap.net/q/212067/-can-we-add-text-shape-and-signature-in-photo-markup-with-pencil-kit

Suction answered 11/8, 2021 at 13:50 Comment(0)
C
-2

Nowadays very easy fortunately:

"some text".draw(in: rect)

that's it.

Full example

Council answered 2/7, 2023 at 23:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.