Search code examples
swiftswiftuidrawdrawtext

How to draw a long string on UIImage In SwiftUI


I am trying to draw long string on UIImage but unable to achieve . Currently when i draw the text on image only one line shows .


struct ContentView: View {
    @State var bottomImage = UIImage(named: "2")
    let text = "SwiftUI helps you build great-looking apps across all Apple platforms with the power of Swift — and surprisingly little code."
    var body: some View {
            VStack{
                Image(uiImage: bottomImage!)
                    .resizable()
                    .scaledToFit()
                Button {
                    drawTextOnImage()
                } label: {
                    Text("DrawText")
                }
            }
    }
    func drawTextOnImage(){
        let newSize = CGSize(width: 660, height: 700)
        let topNew = UIImage(named: "3")

        UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
        topNew!.draw(in: CGRect(origin: .zero, size: newSize))
        
        let font = UIFont.systemFont(ofSize: 40)
        let text_style=NSMutableParagraphStyle()
        text_style.lineBreakMode = .byTruncatingTail
//        text_style.numberOfLines = 0
        text_style.alignment=NSTextAlignment.center
        let text_color=UIColor.yellow
        let attributes=[NSAttributedString.Key.font:font, NSAttributedString.Key.paragraphStyle:text_style, NSAttributedString.Key.foregroundColor:text_color]
        let text_h=font.lineHeight
        let text_y=((topNew?.size.height)!-text_h)/1.2
        let text_rect=CGRect(x: 0, y: text_y, width: (topNew?.size.width)!, height: text_h)
        text.draw(in: text_rect.integral, withAttributes: attributes)
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        DispatchQueue.main.async {
            self.bottomImage = newImage
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Achieved Result :

Only One line text is drawn on the image Expected Result : i want to show the whole string saved in text variable on the top of UIImage


Solution

  • There's a few things going on here, but the main issue you're facing is in let text_h=font.lineHeight. By limiting the height of the drawable rectangle to the line height, there's not enough vertical space to wrap the text. Additionally, since you've already defined a size for the new image in let newSize = CGSize(width: 660, height: 700), referencing the asset's size will give you bounds that are different than your drawable area. You might also want to use .byWordWrapping instead of .byTruncatingTail for your line break mode if you want to see every word on the image instead of "...".

    Here's a modification of your drawTextOnImage() function addressing those points:

    func drawTextOnImage() {
        let newSize = CGSize(width: 660, height: 700)
        let topNew = UIImage(named: "3")
    
        UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
        topNew!.draw(in: CGRect(origin: .zero, size: newSize))
        
        let font = UIFont.systemFont(ofSize: 40)
        let text_style=NSMutableParagraphStyle()
        text_style.lineBreakMode = .byWordWrapping
        text_style.alignment=NSTextAlignment.center
        let text_color=UIColor.yellow
        let attributes=[NSAttributedString.Key.font:font, NSAttributedString.Key.paragraphStyle:text_style, NSAttributedString.Key.foregroundColor:text_color]
        
        // Center the text on the finished image
        let text_y=((newSize.height - font.pointSize)/2)
        let text_rect=CGRect(x: 0, y: text_y, width: newSize.width, height: newSize.height)
        text.draw(in: text_rect, withAttributes: attributes)
        
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        DispatchQueue.main.async {
            self.bottomImage = newImage
        }
    }
    

    What's changed? We're calculating the center based on the font's size (thanks to this SO Answer), referencing your variable defined image dimensions, and are instructing the text to wrap instead of truncate.

    Result:

    Simulator Screenshot of code result

    As a bonus, if UIImage(named: "2") and UIImage(named: "3") are the same image, you can just call UIImage(named: "2") both times. Drawing doesn't mutate the image in the asset catalog in any way.