¿Cómo agrego texto a una imagen en iOS Swift?

82

He mirado a mi alrededor y no he podido averiguar cómo tomar texto, superponerlo en una imagen y luego combinar los dos en una sola UIImage.

He agotado Google usando los términos de búsqueda que se me ocurren, por lo que si alguien tiene una solución o al menos una pista que pueda señalar, sería muy apreciada.

Christopher Wade Cantley
fuente

Respuestas:

186

Ok ... lo descubrí:

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

}

Para llamarlo, simplemente pasa una imagen:

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

Los siguientes enlaces me ayudaron a aclarar esto:

Swift - Dibujar texto con drawInRect: withAttributes:

¿Cómo escribir texto en una imagen en Objective-C (iOS)?

El objetivo original era crear una imagen dinámica que pudiera usar AnnotaionView, como poner un precio en una ubicación determinada en un mapa, y esto funcionó muy bien. Espero que esto ayude a alguien que intenta hacer lo mismo.

Para 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!
 }

Para 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!
 }

Para 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!
}
Christopher Wade Cantley
fuente
1
super útil! gracias por publicar su código. y muy bien comentado.
Aspen
¿Pueden esos textos o su contenedor responder a eventos de tapping?
PPrasai
1
@JadeSync si se puede hacer clic en el objeto en el que está importando la imagen, entonces sí. Pero no es el texto en el que se puede hacer clic, es el contenedor en el que se utilizan las imágenes finales en el que se debería poder hacer clic. De hecho, eso es lo que hice con esta imagen porque necesitaba una forma de tener un marcador en el que se pueda hacer clic en un mapa, y el marcador necesitaba tener texto en una imagen. El marcador tiene un delegado de evento de clic, por lo que no es la imagen en la que están haciendo clic, sino el marcador que lo contiene.
Christopher Wade Cantley
2
Si coloco el punto en la parte inferior derecha de la imagen y el texto es demasiado largo, se extiende fuera de la imagen. ¿Alguna sugerencia sobre cómo hacer que el texto se alinee a la izquierda del punto en lugar de extenderse fuera de la imagen hacia la derecha?
Chickenparm
También me encontré con ese problema y, francamente, depende del tipo y tamaño de fuente, y del tamaño de la imagen, donde desea que comience el texto en la imagen, ya que es posible que no desee pisar una sombra PNG. Esto fue una cosa de prueba y error que requiere que agregue un carácter, averigüe cuántos píxeles mover el punto de inicio para ingresar, luego haga un cálculo rápido para calcular el punto de inicio deslizante en función de la cantidad de caracteres (arriba al máximo según su apariencia). No quería complicarme demasiado, así que abordé la solución básica para mi pregunta.
Christopher Wade Cantley
20

Mi sencilla solución:

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
fuente
Gracias por el código, pero cómo ajustar el tamaño de fuente, cada tamaño de imagen es diferente con diferente longitud de texto. ¿Cómo puedo hacer esta dinámica?
iPhoneDev
Creo que deberías usar Autoshrink. Establezca el tamaño de fuente máximo para el ancho máximo de la etiqueta y agregue: label.adjustsFontSizeToFitWidth = true & label.minimumScaleFactor = 0.5
Darkngs
¿Hay alguna forma de agregar texto de etiqueta alineado con alguna de las esquinas?
Ashh
11

También puede hacer un 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

Álamo temblón
fuente
1
Amo las cosas de Ray. ¡Gracias por el método alternativo!
Christopher Wade Cantley
2
Estás agregando una capa de texto a una vista aquí, no a una imagen
brainray
1
Vale la pena mencionar si uno está buscando alinear verticalmente el texto, consulte stackoverflow.com/questions/4765461/…
JKRT
5

He creado una extensión para usarla en todas partes:

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!
    }

}

Uso: En cualquier lugar de su ViewController donde tenga el tamaño y la etiqueta para agregar, utilícelo de la siguiente manera:

let newImageWithOverlay = UIImage.createImageWithLabelOverlay(label: labelToAdd, imageSize: size, image: editedImage)
Ankit Kumar Gupta
fuente
1
¡Increíble! Gracias por contribuir con esto.
Christopher Wade Cantley
Muestra un fondo blanco incluso si usé una imagen transparente
EI Captain v2.0
¿Qué estás haciendo al ponerlo en una vista o subirlo al servidor?
Ankit Kumar Gupta
Simplemente tome la imagen y muéstrela en vista de imagen. Muestra fondo blanco
EI Captain v2.0
Su vista de imagen y su supervista deben tener un fondo claro. Si lo publica en el servidor en formato png, será como desee. Haz eso y verifica. O intente guardarlo localmente una vez.
Ankit Kumar Gupta
3

Para 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
fuente
1

No puedo ver nada en su pregunta inicial que sugiera que esto debe hacerse exclusivamente en código, entonces, ¿por qué no simplemente agregar un UILabel en el generador de interfaces y agregar restricciones para darle la misma longitud y ancho que su imagen, centrarlo verticalmente y horizontalmente (o como lo necesite), elimine el texto de la etiqueta, establezca la fuente, el tamaño, el color, etc. del texto, según sea necesario (incluida la selección de Autoencogimiento con el tamaño mínimo o escala que necesite) y asegúrese de que el fondo sea transparente.

Luego, simplemente conéctelo a un IBOutlet y configure el texto en código según sea necesario (por ejemplo, en viewWillAppear, o usando un enfoque de ViewModel y configurándolo en la inicialización de su vista / controlador de vista).

Gsp
fuente
0

He probado estos componentes básicos. Espero que funcione.

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
    }
Muhammad Haroon Iqbal
fuente