101

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.

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Christopher Wade Cantley
  • 7,122
  • 5
  • 35
  • 48

9 Answers9

218

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!
}
starball
  • 20,030
  • 7
  • 43
  • 238
Christopher Wade Cantley
  • 7,122
  • 5
  • 35
  • 48
  • 1
    superhelpful! thanks for posting your code. and so well commented. – Adrienne Apr 27 '15 at 05:19
  • can those texts or their container rects response to tap events? – PPrasai Oct 18 '16 at 08:33
  • 1
    @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. – Christopher Wade Cantley Oct 18 '16 at 18:33
  • 2
    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? – chickenparm Nov 20 '16 at 19:29
  • 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. – Christopher Wade Cantley Dec 20 '16 at 17:37
  • what about `CIImage`? how to add text into `CIImage` without converting it to `UIImage` – user924 Mar 01 '18 at 11:37
  • 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/CVPixelBuffer – user924 Mar 01 '18 at 13:44
  • @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! – rob Nov 11 '18 at 05:43
  • how do we centre align the text in the image? – user1872384 Jun 21 '19 at 07:22
  • This code generates image with text is fine, but it is not generating proper image with text in iphone xs – Ananta Prasad Nov 19 '19 at 06:06
  • 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?? – KimJitae Oct 16 '22 at 15:11
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
}
Darkngs
  • 6,381
  • 5
  • 25
  • 30
  • 1
    Thaks for code but how to adjust font size, every image size is different with different text length. How can I make this dynamic. – iPhoneDev Jun 14 '16 at 00:42
  • 1
    I think you should use Autoshrink. Set max font size for max width of label and add: label.adjustsFontSizeToFitWidth = true & label.minimumScaleFactor = 0.5 – Darkngs Jun 14 '16 at 11:20
  • is there a way to add label text aligned to any of corners? – Ashh Nov 18 '19 at 23:05
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

Pranav Kasetti
  • 8,770
  • 2
  • 50
  • 71
Adrienne
  • 2,540
  • 1
  • 29
  • 39
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)
Ankit Kumar Gupta
  • 3,994
  • 4
  • 31
  • 54
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!
}
Yasin Aktimur
  • 459
  • 6
  • 9
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).

Gsp
  • 401
  • 5
  • 10
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
    }
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://stackoverflow.com/a/68743098/12035498

FrugalResolution
  • 568
  • 4
  • 18
-2

Nowadays very easy fortunately:

"some text".draw(in: rect)

that's it.

Full example

Fattie
  • 27,874
  • 70
  • 431
  • 719