Related
I'm trying to share my PKDrawing results via PDF file for email or print. I'm creating the PDF using 2 other PDF files plus the PKDrawing on the top layer. The 2 PDF layers look clear. The PKDrawing however is somewhat grainy, especially compared to a printed screen shot.
Here's my current code:
UIGraphicsBeginPDFContextToFile(filePath, CGRect.zero, nil)
var drawing: PKDrawing = PKDrawing.init()
let markup: Data = self.pages![pagenum].value(forKey: "markup") as! Data
let drawingHeight = self.pages![pagenum].value(forKey: "height") as! CGFloat
let drawingWidth = self.pages![pagenum].value(forKey: "width") as! CGFloat
do {
try drawing = PKDrawing.init(data: markup)
} catch {
print("Error loading drawing")
}
let pageSize = CGSize(width: 0, height: 0)
//let scale: CGFloat = 2.0 THIS VALUE DOESN'T SEEM TO MATTER
let scale: CGFloat = 1.0
if landscapeOrientation {
pageSize = CGSize(width:2048, height: 1408)
} else {
pageSize = CGSize(width: 1536, height: 1920)
}
let canvas = PKCanvasView(frame: CGRect(x: 0.0, y: 0.0, width: drawingWidth, height: drawingHeight))
canvas.drawing = drawing
markupImage = drawing.image(from: canvas.frame, scale: scale)
if pageSize.width != drawingWidth {
canvas.drawing.transform(using: CGAffineTransform(scaleX: pageSize.width / drawingWidth, y: pageSize.height / drawingHeight))
}
//markupImage = drawing.image(from: canvas.frame, scale: self.view.contentScaleFactor) THIS CHANGE MADE A BIG DIFFERENCE
markupImage = drawing.image(from: canvas.frame, scale: 4.0)
UIGraphicsBeginImageContext(pageSize)
backgroundView?.draw(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: pageSize))
templateView?.draw(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: pageSize))
markupImage.draw(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: pageSize))
let pdfImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
let pageRect: CGRect = CGRect(origin: CGPoint.zero, size: pageSize)
UIGraphicsBeginPDFPageWithInfo(pageRect, nil)
let pdfContext: CGContext = UIGraphicsGetCurrentContext()!
pdfContext.scaleBy(x: 1.0, y: 1.0)
pdfImage.draw(at: CGPoint(x: 0.0, y: 0.0))
UIGraphicsEndPDFContext()
Changing my scale from 2.0 to 4.0 cleared it up quite a bit, I hadn't thought to try that. Will edit the code with my change and a comment. Definitely open for other alternatives though if there's a cleaner or clearer way to do this. I'm not the best at resolutions / pixel spaces.
In a playground, I am trying to make a SpriteKit scene with a top left origin going down (ie like a drawing program). Everything works except for text which is flipped. I need to make the text right side up. I am missing something. If I add the translate and scale, the text vanishes.
//: A SpriteKit based Playground
import PlaygroundSupport
import SpriteKit
class GameScene: SKScene
{
static let width = 1000
static let height = 1800
override func didMove(to view: SKView)
{
self.backgroundColor = SKColor.white
let cam = SKCameraNode()
self.camera = cam
cam.yScale = -1
let node = SKSpriteNode(color: SKColor.yellow, size: CGSize(width: 500, height: 300))
node.anchorPoint = CGPoint(x: 0, y: 0)
node.position = CGPoint(x: 10, y: 10)
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 200, height: 200))
let img = renderer.image { ctx in
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attrs = [NSAttributedStringKey.font: UIFont(name: "Futura", size: 72)!, NSAttributedStringKey.paragraphStyle: paragraphStyle]
//ctx.cgContext.translateBy(x: 0, y:200)
//ctx.cgContext.scaleBy(x: 0, y: -1)
let string = "Test"
string.draw(with: CGRect(x: 32, y: 32, width: 200, height: 200), options: .usesLineFragmentOrigin, attributes: attrs, context: nil)
}
let tex = SKTexture(image: img)
let zzz = SKSpriteNode(texture: tex)
zzz.anchorPoint = CGPoint(x: 0, y: 0)
zzz.position = CGPoint(x: 10, y: 10)
node.addChild(zzz)
// let z = SKSpriteNode(color: SKColor.red, size: CGSize(width: 200, height: 150))
// z.anchorPoint = CGPoint(x: 0, y: 0)
// z.position = CGPoint(x: 10, y: 10)
// node.addChild(z)
//
// let q = SKSpriteNode(color: SKColor.green, size: CGSize(width: 80, height: 70))
// q.anchorPoint = CGPoint(x: 0, y: 0)
// q.position = CGPoint(x: 10, y: 10)
// z.addChild(q)
self.addChild(node)
self.addChild(cam)
cam.position = CGPoint(x: GameScene.width/2, y: GameScene.height/2)
}
}
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 480, height: 640))
let scene = GameScene(size: CGSize(width: GameScene.width, height: GameScene.height))
scene.scaleMode = .aspectFit
sceneView.presentScene(scene)
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView
Update:
ctx.cgContext.textMatrix = .identity
ctx.cgContext.translateBy(x: 0, y: 200)
ctx.cgContext.scaleBy(x: 1.0, y: -1.0)
now draws the text rightside up, but it is wrapped at 50% of the width
Final Update:
This worked, its rightsize up. The font size is proportional to the scene size (which is a dimension that fits on all iOS devices.
self.camera = cam
cam.yScale = -1
let boxSize = CGSize(width: 500, height: 500)
let node = SKSpriteNode(color: SKColor.yellow, size: boxSize)
node.anchorPoint = CGPoint(x: 0, y: 0)
node.position = CGPoint(x: 10, y: 10)
let renderer = UIGraphicsImageRenderer(size: boxSize)
let img = renderer.image { ctx in
let bounds = CGRect(x: 16, y: 0, width: boxSize.width-32, height: boxSize.height)
let string = "This is a long test with many lines."
let range = NSRange( location: 0, length: string.count)
guard let context = UIGraphicsGetCurrentContext() else { return }
let path = CGMutablePath()
path.addRect(bounds)
let attrString = NSMutableAttributedString(string: string)
attrString.addAttribute(NSAttributedStringKey.font, value: UIFont(name: "Futura", size: 84)!, range: range )
let framesetter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString)
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrString.length), path, nil)
CTFrameDraw(frame, context)
}
You should read https://www.raywenderlich.com/153591/core-text-tutorial-ios-making-magazine-app
CoreText inverts (technically, rotates) text in iOS. This is possibly because it is shared with MacOS which uses a different coordinate system. (Though why they couldn't fix this for iOS I have no idea).
Whatever the cause, you aren't doing anything wrong, iOS is doing the wrong thing. The simple solution (as per the linked article) is to simply rotate the text before drawing it.
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 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
I had earlier used the google map util library to plot bubble icon in google maps and now I would like to do the same for iOS
https://developers.google.com/maps/documentation/android/utility/
I found this pod for iOS https://github.com/googlemaps/google-maps-ios-utils
Has anybody used it to create bubble icons and if so how do i create a bubble icons for iOS ?
Unfortunately, there is no such utility for IOS. I needed a similar solution, so I've come with the below.
The below function draws a left arrow and returns an UIImage. Note that one can draw the arrow by using only paths. Also, one can also use a pre created image from assets instead of creating all the time or create one re-use it.
func drawArrow() ->UIImage {
//the color of the arrow
let color = UIColor.yellow
// First create empty image choose the size according to your need.
let width = 84
let height = 24
let size = CGSize( width: width, height: height)
//let image: UIImage = makeEmptyImage(size: size)
let rectangle = CGRect(x: 12, y: 0, width: 72, height: 24)
UIGraphicsBeginImageContextWithOptions( size, false, 0.0)
let context = UIGraphicsGetCurrentContext()
//clear the background
context?.clear(CGRect(x: 0, y: 0, width: width, height: height))
//draw the rectangle
context?.addRect(rectangle)
context?.setFillColor(color.cgColor)
context?.fill(rectangle)
//draw the triangle
context?.beginPath()
context?.move( to: CGPoint(x : 12 ,y : 0) )
context?.addLine(to: CGPoint(x : 0, y : 12) )
context?.addLine(to: CGPoint(x : 12, y : 24) )
context?.closePath()
context?.setFillColor(color.cgColor)
context?.fillPath()
// get the image
let image2 = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image2
}
The next function, takes the image and draw a text onto it.
func drawText(text:NSString, inImage:UIImage) -> UIImage? {
let font = UIFont.systemFont(ofSize: 11)
let color = UIColor.black
let style : NSMutableParagraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
style.alignment = .center
let attributes:NSDictionary = [ NSAttributedString.Key.font : font,
NSAttributedString.Key.paragraphStyle : style,
NSAttributedString.Key.foregroundColor : color
]
let myInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0)
let newImage = inImage.resizableImage(withCapInsets: myInsets)
print("rect.width \(inImage.size.width) rect.height \(inImage.size.height)")
let textSize = text.size(withAttributes: attributes as? [NSAttributedString.Key : Any] )
let textRect = CGRect(x: 10, y: 5, width: textSize.width, height: textSize.height)
UIGraphicsBeginImageContextWithOptions(CGSize(width: textSize.width + 20, height: textSize.height + 10), false, 0.0)
newImage.draw(in: CGRect(x: 0, y: 0, width: textSize.width + 20 , height: textSize.height + 10))
text.draw(in: textRect.integral, withAttributes: attributes as? [NSAttributedString.Key : Any] )
let resultImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resultImage
}
so we can use it like
marker.icon = drawText(text: eLoc.name as NSString, inImage: drawArrow() )
marker.groundAnchor = CGPoint(x: 0, y: 0.5)
//If you need some rotation
//let degrees = CLLocationDegrees(exactly: 90.0)
//marker.rotation = degrees!
groundAnchor moves the position of the marker icon, see this anwser. With degree you can rotate the arrow.
Note : Unfortunately, IOS doesn't support 9-patch images. Therefore, not all of the bubble icon factory can be programmed by using resizableImage, see this nice web site and also see this answer
An example output is;
Have you check out the customize icon documentation for google maps ios sdk? It's a good start.