I am trying to "merge" an UIImage with a UILabel into one UIImage, for that I wrote a function everything works fine except that the Label does not get added to the Current Graphics Context. I would appreciate your help!
func textToImage(drawText: String, inImage: UIImage, atPoint: CGPoint) -> UIImage{
// Setup the image context using the passed image
UIGraphicsBeginImageContext(inImage.size)
// Put the image into a rectangle as large as the original image
inImage.draw(in: CGRect(origin: CGPoint.zero, size: CGSize(width: inImage.size.width, height: inImage.size.height)))
// Create a point within the space that is as bit as the image
let rectPos = CGPoint(x: atPoint.x, y: atPoint.y)
let rectSize = CGSize(width: inImage.size.width, height: inImage.size.height)
let rect = CGRect(origin: rectPos, size: rectSize)
// Draw the text into an image
let label = UILabel()
label.text = drawText
label.textColor = .white
label.font = UIFont(name: "Helvetica Bold", size: 12)!
label.drawText(in: rect)
// Create a new image out of the images we have created
let newImage = UIGraphicsGetImageFromCurrentImageContext()
// End the context now that we have the image we need
UIGraphicsEndImageContext()
//Pass the image back up to the caller
return newImage!
}
Are u sure that you trying to draw label in correct position? Is rect which you compute as CGRect(origin: CGPoint.zero, size: CGSize(width: inImage.size.width, height: inImage.size.height)) contains atPoint?
The problem seems to be here UIGraphicsBeginImageContext(inImage.size)
The context you are creating is the size of your image. You need to create the context to the size of Image + label height. Then you can add the label accordingly and will be able to see the label inside the image appended.
Also specify the label's frame let label = UILabel().
Suppose you want to append the label at the bottom of the image.
Let the image size be 300 * 400, and you want to put the label at the bottom.
The concept is to create a context with the height of 400 + label height (suppose 40).
Then you set the label frame to be a y of CGRect(0,imagesize.height,100,40).
Hope it helps
Problem with your code is that UIKit (UILabel usage) tries to draw stuff in its own context. You need to draw text + image in same context to get the desired result.
Try this to receive the resulting image:
func textToImage(drawText text: NSString, inImage image: UIImage) -> UIImage
{
UIGraphicsBeginImageContext(image.size)
image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
let font=UIFont(name: "Helvetica-Bold", size: 8)!
let paraStyle=NSMutableParagraphStyle()
paraStyle.alignment=NSTextAlignment.center
let attributes = [NSAttributedStringKey.foregroundColor:UIColor.red, NSAttributedStringKey.font:font, NSAttributedStringKey.paragraphStyle:paraStyle]
let height = font.lineHeight
let y = (image.size.height-height) / 2
let strRect = CGRect(x: 0, y: y, width: image.size.width, height: height)
text.draw(in: strRect.integral, withAttributes: attributes)
let result=UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result!
}
#IBAction func touchTest(_ sender: Any)
{
let button = sender as! UIButton
let image = self.textToImage(drawText: "my text", inImage: UIImage.init(named: "circle")!)
button.setBackgroundImage(image, for: UIControlState.normal)
}
I have an image which is a bubble shape, and I wish to add text to the middle of the image, so I tried to use UIGraphicsBeginImageContextWithOptions to redraw the image.
The code is as following:
let imageSize = CGSize(width: 40, height: 40)
// the rect in which the image will be drawn in
let imageRect = CGRect(origin: CGPoint.zero, size: imageSize)
UIGraphicsBeginImageContextWithOptions(imageSize, true, 1.0)
// begining drawing things
// first, we draw the image in the specified rect
image.draw(in: imageRect)
let attributes = [ NSAttributedStringKey.foregroundColor: UIColor.red,
NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 20)]
let text = "55"
let size = text.size(withAttributes: attributes)
let rect = CGRect(x: 20 - size.width / 2, y: 20 - size.height / 2, width: size.width, height: size.height)
text.draw(in: rect, withAttributes: attributes)
let newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return newImage
but I get a square with a black background with the desired image inside:
the blue is the original image, the black is the re-drawn image. Anyone knows how can I draw the image as the original image?
Thanks!
You need to pass false, not true, to the 2nd argument (the opaque parameter) of the call to UIGraphicsBeginImageContextWithOptions function.
UIGraphicsBeginImageContextWithOptions(imageSize, false, 1.0)
You should also pass 0 as the 3rd argument (scale) so the image is scaled to match the current device's screen scale.
I'm trying to create a gray circle with name initials indie of it. And use it as tabBarItem image.
This is the function that I use to create the circle, and it almost works, as you can see in the image below, but it's a square not a circle:
static func createAvatarWithInitials(name: String, surname: String, size: CGFloat) -> UIImage {
let initialsLabel = UILabel()
initialsLabel.frame.size = CGSize(width: size, height: size)
initialsLabel.textColor = UIColor.white
initialsLabel.text = "\(name.characters.first!)\(surname.characters.first!)"
initialsLabel.textAlignment = .center
initialsLabel.backgroundColor = UIColor(red: 74.0/255, green: 77.0/255, blue: 78.0/255, alpha: 1.0)
initialsLabel.layer.cornerRadius = size/2
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(initialsLabel.frame.size, false, scale)
initialsLabel.layer.render(in: UIGraphicsGetCurrentContext()!)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
image.withRenderingMode(.alwaysOriginal)
return image
}
and this is how I call it inside the viewDidLoad of the tabBarController:
if index == positionForProfileItem {
tabBarItem.image = UIImage.createAvatarWithInitials(name: "John", surname: "Doe", size: CGFloat(20))
}
but I got an empty squared rectangle in the tab bar.. And I can't figure it out the reason, any help?
The UIImage withRenderingMode method returns a new image. It does not modify the sender.
Change the last two lines to just one:
return image.withRenderingMode(.alwaysOriginal)
But that is not what you wish to do. Since you want a template image of a circle with the text in the middle, you need to do the following:
static func createAvatarWithInitials(name: String, surname: String, size: CGFloat) -> UIImage {
UIGraphicsBeginImageContextWithOptions(CGSize(width: size, height: size), false, 0)
let ctx = UIGraphicsGetCurrentContext()!
// Draw an opague circle
UIColor.white.setFill()
let circle = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: size, height: size))
circle.fill()
// Setup text
let font = UIFont.systemFont(ofSize: 17)
let text = "\(name.characters.first!)\(surname.characters.first!)"
let textSize = (text as NSString).size(withAttributes: [ .font: font ])
// Erase text
ctx.setBlendMode(.clear)
let textRect = CGRect(x: (size - textSize.width) / 2, y: (size - textSize.height) / 2, width: textSize.width, height: textSize.height)
(text as NSString).draw(in: textRect, withAttributes: [ .font: font ])
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
You have to add clipsToBounds=YES on your label for cornerRadius to have efect on your screen shoot, and also try to increase a little bit the size of your image from 20 to 30.
I'm trying to draw a resizable textView onto an image kind of like snapchat does but when I draw the label onto the image, the text is significantly smaller and more towards the top left than it should be.
func drawTextToImage(){
let rect = textView.frame
let showText = textView.text
let font = UIFont.boldSystemFont(ofSize: 26)
let attr = [NSFontAttributeName: font, NSForegroundColorAttributeName:UIColor.white]
let image = UIImage(named: "3FactorPostGreen")
//First create the rotated and transparent image with text
UIGraphicsBeginImageContextWithOptions(CGSize(width: rect.size.width, height: rect.size.height), false, 0)
if let context = UIGraphicsGetCurrentContext() {
context.rotate (by: 45.0 * CGFloat.pi/180.0) //45˚
}
showText?.draw(in: CGRect(x: rect.origin.x, y: rect.origin.y, width: rect.size.width, height: rect.size.height), withAttributes: attr)
let rotatedImageWithText = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext()
//Then, draw rotated image(with text) onto the background image
UIGraphicsBeginImageContextWithOptions(CGSize(width: rect.size.width, height: rect.size.height), true, 0)
image?.draw(in: rect)
rotatedImageWithText?.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext()
self.imageView.image = newImage
}
Then this was the before and after photos
Larger Text:
Smaller Text:
I have no idea what happens when this goes on but if anyone could explain and help that would be awesome.
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.
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!
}
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
}
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
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)
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!
}
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).
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
}
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