Jak dodać tekst do obrazu w iOS Swift?

82

Rozejrzałem się i nie udało mi się dowiedzieć, jak wziąć tekst, nałożyć go na obraz, a następnie połączyć je w jeden UIImage.

Wyczerpałem Google przy użyciu wyszukiwanych haseł, które przychodzą mi do głowy, więc jeśli ktoś ma rozwiązanie lub przynajmniej wskazówkę, może je wskazać, byłby bardzo wdzięczny.

Christopher Wade Cantley
źródło

Odpowiedzi:

186

Ok ... wymyśliłem to:

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

}

Aby to nazwać, wystarczy przekazać obraz:

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

Poniższe linki pomogły mi w zrozumieniu tego:

Swift - Rysowanie tekstu za pomocą drawInRect: withAttributes:

Jak pisać tekst na obrazie w Objective-C (iOS)?

Pierwotnym celem było stworzenie dynamicznego obrazu, który mógłbym wykorzystać AnnotaionViewnp. Do umieszczenia ceny w danym miejscu na mapie i to wyszło świetnie. Mam nadzieję, że pomoże to komuś, kto spróbuje zrobić to samo.

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

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

Dla 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
źródło
1
superpomocne! dziękuję za wysłanie kodu. i tak dobrze skomentowane.
Aspen
czy te teksty lub ich kontenery mogą reagować na zdarzenia związane z dotknięciem?
PPrasai
1
@JadeSync, jeśli obiekt, do którego importujesz obraz, jest klikalny, to tak. Ale to nie tekst jest klikalny, to kontener, w którym używane są końcowe obrazy, musiałby być klikalny. Właściwie to właśnie zrobiłem z tym obrazem, ponieważ potrzebowałem sposobu na umieszczenie na mapie klikalnego znacznika, a marker musiał mieć tekst na obrazie. Znacznik ma delegata zdarzenia kliknięcia, więc nie jest to obraz, na który klikają, ale znacznik, który go zawiera.
Christopher Wade Cantley
2
Jeśli ustawię punkt w prawym dolnym rogu obrazu, a tekst jest za długi, wykracza poza obraz. Jakieś sugestie, jak wyrównać tekst do lewej strony punktu zamiast rozciągać się poza obraz w prawo?
kurczak,
Natknąłem się również na ten problem i szczerze mówiąc, zależy to od rodzaju i rozmiaru czcionki oraz rozmiaru obrazu, w którym chcesz, aby tekst zaczynał się na obrazie, ponieważ możesz nie chcieć nadepnąć na cień PNG. To była próba i błąd, który wymagał dodania znaku, ustalenia, o ile pikseli przesunąć punkt początkowy, aby go wprowadzić, a następnie wykonać szybkie obliczenia, aby obliczyć przesuwny punkt początkowy na podstawie liczby znaków (w górę maksymalnie w zależności od tego, jak wygląda). Nie chciałem, aby było to zbyt skomplikowane, więc odpowiedziałem na to pytanie.
Christopher Wade Cantley,
20

Moje proste rozwiązanie:

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
źródło
Dziękuję za kod, ale jak dostosować rozmiar czcionki, każdy rozmiar obrazu jest inny z inną długością tekstu. Jak mogę uczynić to dynamicznym.
iPhoneDev,
Myślę, że powinieneś użyć Autoshrink. Ustaw maksymalny rozmiar czcionki dla maksymalnej szerokości etykiety i dodaj: label.adjustsFontSizeToFitWidth = true & label.minimumScaleFactor = 0.5
Darkngs
czy istnieje sposób, aby dodać tekst etykiety wyrównany do któregoś z rogów?
Ashh
11

Możesz także zrobić 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

Osika
źródło
1
Uwielbiam rzeczy Raya. Dzięki za alternatywną metodę!
Christopher Wade Cantley
2
Dodajesz
1
Warto o tym wspomnieć, jeśli ktoś chce wyrównać tekst w pionie, patrz stackoverflow.com/questions/4765461/ ...
JKRT
5

Stworzyłem rozszerzenie do używania go wszędzie:

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

}

Użycie: W dowolnym miejscu w kontrolerze ViewController, w którym masz rozmiar i etykietę do dodania, użyj go w następujący sposób -

let newImageWithOverlay = UIImage.createImageWithLabelOverlay(label: labelToAdd, imageSize: size, image: editedImage)
Ankit Kumar Gupta
źródło
1
Niesamowite! Dzięki za wkład w to.
Christopher Wade Cantley,
Pokazuje białe tło, nawet jeśli użyłem przezroczystego obrazu
EI Captain v2.0
Co robisz, umieszczając go w widoku lub przesyłając na serwer?
Ankit Kumar Gupta
Po prostu weź zdjęcie i pokaż je w widoku obrazu. Pokazuje białe tło
EI Captain v2.0
Twój widok obrazu i jego nadzór muszą mieć czyste tło. Jeśli wyślesz go na serwer w formacie png, będzie tak, jak chcesz. Zrób to i sprawdź. Lub spróbuj zapisać go lokalnie.
Ankit Kumar Gupta
3

Dla 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
źródło
1

W twoim początkowym pytaniu nie widzę niczego, co sugerowałoby, że należy to zrobić wyłącznie w kodzie - dlaczego więc po prostu nie dodać UILabel w narzędziu do tworzenia interfejsów i dodać ograniczenia, aby nadać mu taką samą długość i szerokość jak obraz, wyśrodkować go w pionie i poziomo (lub jakkolwiek potrzebujesz), usuń tekst etykiety, ustaw czcionkę tekstu, rozmiar, kolor itp. w razie potrzeby (w tym zaznaczenie Autoshrink z jakimkolwiek minimalnym rozmiarem lub skalą, jakiej potrzebujesz) i upewnij się, że tło jest przezroczyste.

Następnie po prostu podłącz go do IBOutlet i ustaw tekst w kodzie zgodnie z potrzebami (np. W viewWillAppear lub używając podejścia ViewModel i ustawiając go podczas inicjalizacji widoku / kontrolera widoku).

Gsp
źródło
0

Wypróbowałem te podstawowe komponenty. Mam nadzieję, że to zadziała.

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
źródło