I am trying to add a border to a UIImage I have, not UIImageView.
Here is my code:
let textView = UITextView(frame: CGRectMake(10, 20, self.view.frame.width - 20, self.view.frame.height - 20))
let attributedString = NSMutableAttributedString(string: "")
let image1 = NSTextAttachment()
image1.image = UIImage(named: "image.jpg")
let oldWidth1 = image1.image!.size.width;
//I'm subtracting 10px to make the image display nicely, accounting
//for the padding inside the textView
let scaleFactor1 = oldWidth1 / (textView.frame.size.width - 10 )
image1.image = UIImage(CGImage: image1.image!.CGImage!, scale: scaleFactor1, orientation: .Up)
let attrStringWithImage1 = NSAttributedString(attachment: image1)
attributedString.replaceCharactersInRange(NSMakeRange(0, 0), withAttributedString: attrStringWithImage1)
textView.attributedText = attributedString;
self.view.addSubview(textView)
I have that UIImage which is displaying nicely in a textView, but now I would like to add a border or some padding to the image so that the text is not so close to the image!
You could easily use Core Graphics to do this
You just have to re-draw the image in a bigger context to account for the border width – and offset the image by the border width.
Something like this should do the trick:
extension UIImage {
func imageWithBorder(borderWidth:CGFloat) -> UIImage {
// the bigger size to account for the border width
let newSize = CGSize(width: size.width+borderWidth*2.0, height: size.height+borderWidth*2.0)
// create new image context
UIGraphicsBeginImageContextWithOptions(newSize, false, scale)
// draw image with offset of the border width
self.drawInRect(CGRect(x: borderWidth, y: borderWidth, width: size.width, height: size.height))
// get image and end context
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img
}
}
If you want to give the border a fill color, you can always do that by setting the fill color, and filling the context before drawing the image.
You can then use it like this:
image1.image = UIImage(named: "image.jpg")?.imageWithBorder(10)
Side Note
You seem to be force unwrapping optionals quite a bit. Please don't. Please always safely unwrap them. My answer here might be useful for that.
Related
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 am cropping a UIImage by using UIGraphicsGetCurrentContext and then saving it to cache. I then show that UIImage as the image of one of my tab bar items. This works, however, the scale factor of the image is an issue. If I use UIGraphicsBeginImageContextWithOptions with the scale set to zero, this correctly crops it using the scale factor of the screen. However, when I set the UITabBarItem image, it seems to ignore the fact that the image should be scaled.
My code for scaling:
extension UIImage {
func scaledImage(withSize size: CGSize, withBorder: Bool) -> UIImage {
let imageView = UIImageView(image: self)
imageView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
imageView.contentMode = .scaleAspectFit
let layer = imageView.layer
layer.masksToBounds = true
layer.cornerRadius = size.width/2
if withBorder {
layer.borderColor = Styles.Colours.blue.colour.cgColor
layer.borderWidth = 2
}
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, 1) // Notice I've set the scale factor to 1 here for now. If I set it to 0 the image is too large on the tab bar
defer { UIGraphicsEndImageContext() }
layer.render(in: UIGraphicsGetCurrentContext()!)
return UIGraphicsGetImageFromCurrentImageContext()!
}
}
Used like this:
let defaultImage = image?.scaledImage(withSize: CGSize(width: 25, height: 25), withBorder: false)
Then I set the tab bar item like this:
self.tabBar.items?.last?.image = defaultImage.withRenderingMode(.alwaysOriginal)
If I was to set a UIImage from my Assets, then it would take into account the scale factor. How do I fix this? Thanks!
Solved it by converting the UIImage to one that specifically defines the scale like this:
let scaledSelectedImage = UIImage(data: UIImagePNGRepresentation(image)!, scale: UIScreen.main.scale)
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, 1) is saying that you want the image context to be set at a scale of #1x. If you set it to 0 it uses the screen's native scale:
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, 0)
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 want to draw a text in the top or bottom right corner of an UIImage.
I have an extension which works great to draw the text but the problem is to positioned the text at the right of the screen (because text is cut).
Here is the extension :
extension UIImage {
func addText(drawText: NSString, atPoint: CGPoint, textColor: UIColor?, textFont: UIFont?) -> UIImage{
// Setup the font specific variables
var _textColor: UIColor
if textColor == nil {
_textColor = UIColor.white
} else {
_textColor = textColor!
}
var _textFont: UIFont
if textFont == nil {
_textFont = UIFont.systemFont(ofSize: 50)
} else {
_textFont = textFont!
}
// Setup the image context using the passed image
UIGraphicsBeginImageContext(size)
// Setup the font attributes that will be later used to dictate how the text should be drawn
let textFontAttributes = [
NSFontAttributeName: _textFont,
NSForegroundColorAttributeName: _textColor,
] as [String : Any]
// Put the image into a rectangle as large as the original image
draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
// Create a point within the space that is as bit as the image
let rect = CGRect(x: atPoint.x, y: atPoint.y, width: size.width, height: size.height)
// Draw the text into an image
drawText.draw(in: rect, withAttributes: textFontAttributes)
// 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!
}
}
Problem is if the text is too long or too big it will be out of the screen. I would need to change the origin point to write the text from the right to the left so it will take space only on the left and not on the right out of the screen. How could I do that ?
There is a simple function call to get the "bounding box" for a string. You can use that to position where the text should be drawn into the image:
// get the bounding-box for the string
let stringSize = drawText.size(attributes: textFontAttributes)
// position the bounding-box at the bottom-right corner of the image
let x = self.size.width - ceil(stringSize.width)
let y = self.size.height - ceil(stringSize.height)
let rect = CGRect(x: x, y: y, width: stringSize.width, height: stringSize.height)
// Draw the text into an image
drawText.draw(in: rect, withAttributes: textFontAttributes)
Note that this sample code positions the text at bottom-right corner - ignoring the atPoint parameter. You would likely change that to a whichCorner type parameter, and then calculate the x and y position appropriately.
By the way... drawText is a terrible name for a variable - it sounds like a function. Much more readable to use something like textToDraw.
Here is a modified function, using a atCorner parameter, where the values are:
+-----------+
| 0 1 |
| |
| |
| 3 2 |
+-----------+
Edit: Using right-aligned paragraph style (as suggested by Thilina Chamin Hewagama) has some advantages. This edited version will even handle text with "\n" embedded line breaks.
extension UIImage {
func addText(textToDraw: NSString, atCorner: Int, textColor: UIColor?, textFont: UIFont?) -> UIImage {
// Setup the font specific variables
var _textColor: UIColor
if textColor == nil {
_textColor = UIColor.white
} else {
_textColor = textColor!
}
var _textFont: UIFont
if textFont == nil {
_textFont = UIFont.systemFont(ofSize: 50)
} else {
_textFont = textFont!
}
// Setup the image context using the passed image
UIGraphicsBeginImageContext(size)
// Put the image into a rectangle as large as the original image
draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
let titleParagraphStyle = NSMutableParagraphStyle()
// Setup the font attributes that will be later used to dictate how the text should be drawn
let textFontAttributes = [
NSFontAttributeName: _textFont,
NSForegroundColorAttributeName: _textColor,
NSParagraphStyleAttributeName: titleParagraphStyle
] as [String : Any]
// get the bounding-box for the string
var stringSize = textToDraw.size(attributes: textFontAttributes)
// draw in rect functions like whole numbers
stringSize.width = ceil(stringSize.width)
stringSize.height = ceil(stringSize.height)
var rect = CGRect(origin: CGPoint.zero, size: self.size)
switch atCorner {
case 1:
// top-right
titleParagraphStyle.alignment = .right
case 2:
// bottom-right
rect.origin.y = self.size.height - stringSize.height
titleParagraphStyle.alignment = .right
case 3:
// bottom-left
rect.origin.y = self.size.height - stringSize.height
default:
// top-left
// don't need to change anything here
break
}
// Draw the text into an image
textToDraw.draw(in: rect, withAttributes: textFontAttributes)
// 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!
}
}
I currently have in image in a nav bar but it's overlapping the edge:
Here is the code I use in viewDidLoad:
let logo = UIImage(named: "holy-grail-pub-logo-header-logo")
let imageView = UIImageView(image:logo)
imageView.contentMode = .ScaleAspectFit
self.navigationItem.titleView = imageView
I've tried setting the position manually using CGRECT but it wasn't changing anything:
let imageView = UIImageView(frame: CGRect(x: 0, y: -30, width: 100, height: 60))
imageView.contentMode = .ScaleAspectFit
let logo = UIImage(named: "holy-grail-pub-logo-header-logo")
imageView.image = logo
self.navigationItem.titleView = imageView
Any help will be appreciated!
Basically I hope you need to adjust the edgeInsets for your imageView which we cannot do directly on UIImageView as of now.
Using the method given here:
I did convert it to Swift(for your ref):
extension UIImage {
class func imageWith(image: UIImage, scaledToSize: CGSize) -> UIImage {
//UIGraphicsBeginImageContext(newSize);
// In next line, pass 0.0 to use the current device's pixel scaling factor (and thus account for Retina resolution).
// Pass 1.0 to force exact pixel size.
UIGraphicsBeginImageContextWithOptions(scaledToSize, false, 0.0)
image.drawInRect(CGRectMake(0, 0, scaledToSize.width, scaledToSize.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
Using above method:
let image = UIImage.imageWith(UIImage(named: "holy-grail-pub-logo-header-logo")!, scaledToSize: CGSizeMake(80,80))
let imageView = UIImageView()
imageView.frame = CGRectMake(0, 0, 100, 100)
imageView.contentMode = .ScaleAspectFit
imageView.image = image
Now your insets become 100-80 i.e. 20. I guess this workaround would help you fixing your issue. Try this and let us know if it works.