6

I am trying to overlay some text on an image using Swift and am looking at this code here: (src: How do I add text to an image in iOS Swift)

This places the text right in the center. I have been changing the values in

 var rect = CGRectMake(10,150, inImage.size.width,    
   inImage.size.height)

but I am unable to get the text to show in the lower left corner. Can someone help and show what I am missing here ?

I am adding the modified image using this line:

modImage = self.textToImage("000", inImage: UIImage(named:"thisImage.png")!, atPoint: CGPointMake(10, 400))

Below is the function...

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

}
Community
  • 1
  • 1
NetCod
  • 191
  • 1
  • 2
  • 12
  • Btw, the orig. thread would not let me post any comments, hence the separate thread. Thanks. – NetCod Sep 12 '16 at 19:51
  • Please include the code where you use this function to add the text to an image. – Swifty Sep 12 '16 at 20:02
  • You shouldn't change the variable inside the function, it uses the point that you pass to it when calling the function. Do you get the text on the upper left corner of your image if you use this line `modImage = self.textToImage("000", inImage: UIImage(named:"thisImage.png")!, atPoint: CGPointMake(0, 0))`? – Swifty Sep 12 '16 at 20:27
  • Nope, still shows up in the center of the image. – NetCod Sep 12 '16 at 20:54
  • Thats odd! For me it's drawing the text at the point that I pass to it as it should. add `print(rect)` after `var rect = CGRectMake(atPoint.x, atPoint.y, inImage.size.width, inImage.size.height)`. What is the output? – Swifty Sep 12 '16 at 21:26
  • rect is (0.0, 0.0, 3024.0, 4032.0) – NetCod Sep 13 '16 at 05:46
  • well then I'm out of guesses. if you like you can put your project on github and I'll take a look at it and try to figure it out. – Swifty Sep 13 '16 at 06:10
  • Thanks Sam. Let me debug a bit further and if it doesn't work I will take up your offer. – NetCod Sep 13 '16 at 17:43
  • modImage = self.addTextToImage("What is this", inImage: image, atPoint: CGPointMake(400,2800)) data = UIImageJPEGRepresentation(modImage,0.1)! This is what finally got the text at the lower left. Wondering if the scaling/conversion is doing something change or it could be the constraints too. – NetCod Sep 13 '16 at 18:10

2 Answers2

13

Details

xCode 9.1, Swift 4

Solution

extension UIView

extension UIView {

    func copyObject<T: UIView> () -> T? {
        let archivedData = NSKeyedArchiver.archivedData(withRootObject: self)
        return NSKeyedUnarchiver.unarchiveObject(with: archivedData) as? T
    }
}

extension UIImage

 extension UIImage {

    typealias EditSubviewClosure<T: UIView> = (_ parentSize: CGSize, _ viewToAdd: T)->()

    func with<T: UIView>(view: T, editSubviewClosure: EditSubviewClosure<T>) -> UIImage {

        if let copiedView = view.copyObject() as? T {
            UIGraphicsBeginImageContext(size)

            let basicSize = CGRect(origin: .zero, size: size)
            draw(in: basicSize)
            editSubviewClosure(size, copiedView)
            copiedView.draw(basicSize)

            let newImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return newImage!
        }
        return self

    }
}

extension UIImageView

 extension UIImageView {

    enum ImageAddingMode {
        case changeOriginalImage
        case addSubview
        case addCopiedSubview
    }

    func drawOnCurrentImage<T: UIView>(view: T, mode: ImageAddingMode, editSubviewClosure: @escaping UIImage.EditSubviewClosure<T>) {

        guard let image = image else {
            return
        }

        let addSubView: (T) -> () = { view in
            editSubviewClosure(self.frame.size, view)
            self.addSubview(view)
        }

        switch mode {
            case .changeOriginalImage:
                self.image = image.with(view: view, editSubviewClosure: editSubviewClosure)

            case .addSubview:
                addSubView(view)

            case .addCopiedSubview:
                if let copiedView = view.copyObject() as? T {
                    addSubView(copiedView)
                }
        }
    }
}

Usage

Sample 1

func sample1(label: UILabel) {
    imageView.contentMode = .scaleAspectFit
    imageView.image = UIImage(named: "wall")?.with(view: label) { (parentSize, viewToAdd) in
        print("parentSize: \(parentSize)")
        viewToAdd.font = UIFont.systemFont(ofSize: 40)
        viewToAdd.textColor = .yellow
        viewToAdd.bounds = CGRect(x: 40, y: 40, width: 200, height: 40)
    }
}

enter image description here

Sample 2

func sample2(label: UILabel) {
    imageView.image = UIImage(named: "wall")
    imageView.contentMode = .scaleAspectFit
    imageView.drawOnCurrentImage(view: label, mode: .changeOriginalImage) { (parentSize, viewToAdd) in
        print("parentSize: \(parentSize)")
        viewToAdd.font = UIFont.systemFont(ofSize: 40)
        viewToAdd.textColor = .yellow
        viewToAdd.textAlignment = .right
        let width: CGFloat = 200
        let height: CGFloat = 30
        let indent: CGFloat = 40
        viewToAdd.bounds = CGRect(x: parentSize.width - width - indent, y: parentSize.height - height - indent, width: width, height: height)
    }
}

enter image description here

Sample 3

func sample3(label: UILabel) {
    imageView.image = UIImage(named: "wall")
    imageView.contentMode = .scaleAspectFill
    imageView.drawOnCurrentImage(view: label, mode: .addSubview) { (parentSize, viewToAdd) in
        print("parentSize: \(parentSize)")
        viewToAdd.font = UIFont.systemFont(ofSize: 16)
        viewToAdd.textColor = .yellow
        viewToAdd.frame = CGRect(x: 40, y: 40, width: 200, height: 20)
    }
}

enter image description here

Sample 4

func sample4(label: UILabel) {
    imageView.image = UIImage(named: "wall")
    imageView.contentMode = .scaleAspectFill
    imageView.drawOnCurrentImage(view: label, mode: .addCopiedSubview) { (parentSize, viewToAdd) in
        print("parentSize: \(parentSize)")
        viewToAdd.font = UIFont.systemFont(ofSize: 16)
        viewToAdd.textColor = .yellow
        viewToAdd.frame = CGRect(x: 40, y: 40, width: 200, height: 20)
    }
}

enter image description here

Full sample

Do not forget to add the solution code here

import UIKit

class ViewController: UIViewController {

    let imageView = UIImageView(frame: CGRect(x: 50, y: 50, width: 300, height: 300))

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let label = UILabel(frame: CGRect(x: 20, y: 20, width: 80, height: 30))
        label.text = "Blablabla"
        label.font = UIFont.systemFont(ofSize: 20)
        label.textColor = .black
        view.addSubview(label)

        sample1(label: label)
        //sample2(label: label)
        //sample3(label: label)
        //sample4(label: label)

        imageView.clipsToBounds = true
        view.addSubview(imageView)
    }

    func sample1(label: UILabel) {
        imageView.contentMode = .scaleAspectFit
        imageView.image = UIImage(named: "wall")?.with(view: label) { (parentSize, viewToAdd) in
            print("parentSize: \(parentSize)")
            viewToAdd.font = UIFont.systemFont(ofSize: 40)
            viewToAdd.textColor = .yellow
            viewToAdd.bounds = CGRect(x: 40, y: 40, width: 200, height: 20)
        }
    }

    func sample2(label: UILabel) {
        imageView.image = UIImage(named: "wall")
        imageView.contentMode = .scaleAspectFit
        imageView.drawOnCurrentImage(view: label, mode: .changeOriginalImage) { (parentSize, viewToAdd) in
            print("parentSize: \(parentSize)")
            viewToAdd.font = UIFont.systemFont(ofSize: 40)
            viewToAdd.textColor = .yellow
            viewToAdd.textAlignment = .right
            let width: CGFloat = 200
            let height: CGFloat = 30
            let indent: CGFloat = 40
            viewToAdd.bounds = CGRect(x: parentSize.width - width - indent, y: parentSize.height - height - indent, width: width, height: height)
        }
    }

    func sample3(label: UILabel) {
        imageView.image = UIImage(named: "wall")
        imageView.contentMode = .scaleAspectFill
        imageView.drawOnCurrentImage(view: label, mode: .addSubview) { (parentSize, viewToAdd) in
            print("parentSize: \(parentSize)")
            viewToAdd.font = UIFont.systemFont(ofSize: 16)
            viewToAdd.textColor = .yellow
            viewToAdd.frame = CGRect(x: 40, y: 40, width: 200, height: 20)
        }
    }

    func sample4(label: UILabel) {
        imageView.image = UIImage(named: "wall")
        imageView.contentMode = .scaleAspectFill
        imageView.drawOnCurrentImage(view: label, mode: .addCopiedSubview) { (parentSize, viewToAdd) in
            print("parentSize: \(parentSize)")
            viewToAdd.font = UIFont.systemFont(ofSize: 16)
            viewToAdd.textColor = .yellow
            viewToAdd.frame = CGRect(x: 40, y: 40, width: 200, height: 20)
        }
    }
}
Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
  • thanks, yes this works for me too. But in my original project though it doesn't help. I'll debug further, but thanks for this. NEver used extensions before so thats good to know too. Thanks. – NetCod Sep 13 '16 at 17:42
  • If you need more advice, I'll be happy to help you. I need more of your code, or image that you are getting and how it should be. – Vasily Bodnarchuk Sep 13 '16 at 17:46
  • Your code works well the first time, but if I edit again the same image I got memory leaked : "CGBitmapContextInfoCreate: unable to allocate 2687385600 bytes for bitmap data" – user3722523 Mar 30 '17 at 16:03
  • This is very strange. I create circle for n=1000 where I add text on 4k photo. Maximum memory usage = 100mb. I need more details. Or create new question with your sample. I will help you. – Vasily Bodnarchuk Mar 30 '17 at 16:18
  • 1
    I found the dimension and size of the output image are increased by x3. So when I edit twice the image the dimension and size are increased by 9, I guess this explain the memory leaked and crashed. – user3722523 Mar 30 '17 at 17:07
  • 1
    FIXED : the problem was the constant "let scale". So I just replaced with "UIGraphicsBeginImageContext(size)" – user3722523 Mar 30 '17 at 17:13
  • No text added to my image. Here is my code: let image = UIImage(named: "4HV.jpg")!.addText("swift", atPoint: CGPoint(x: 20, y: 20), textColor:UIColor.white, textFont:UIFont.systemFont(ofSize: 20)) – Bagusflyer Jul 22 '17 at 09:37
  • @VasilyBodnarchuk How do I add a background color to the label? Adding it in the completion handler of the `with` function does not help – Vaibhav Jhaveri Mar 28 '19 at 10:10
0

Great answer Vasily!

but in my situation I needed to add these fixes.

TEXT IS VERY SMALL

I ran a situation where the text was super small, maybe because the UIImage size was too small for the selected font of 14, so I fixed this by giving a huge font of 120.

TEXT IS NOT DISPLAYED AT THE GIVEN CGPOINT

In the same context of a big UIImage size, the position given to the extension was misplacing the text, the fix: added these 2 lets with the ratio of height and width computed and then multiplied for x,y points.

let widthRatio = size.width/UIScreen.main.bounds.width
let heightRatio = size.height/UIScreen.main.bounds.height

Hope that helps anybody!

Maysam
  • 7,246
  • 13
  • 68
  • 106
Buhaescu Vlad
  • 78
  • 1
  • 6