Swift 4 - NSGraphicsContext.setCurrent() has no member 'setCurrent' - swift

So I am trying to overlay text onto a PDF document using a similar method to this answer.
I use a function to set the current Graphics Context, like it says to in the documentation.
let pdfPage: CGPDFPage = pdf.page(at: 1)!
//var pageRect = pdfPage.getBoxRect(CGPDFBox.mediaBox)
//print(pageRect)
let doc: PDFDocument = PDFDocument(url: pdfURL!)!
let page: PDFPage = doc.page(at: 0)!
var mediaBox: CGRect = page.bounds(for: .mediaBox)
let context = CGContext(destinationURL as CFURL, mediaBox: &mediaBox, nil)
let graphicsContext = NSGraphicsContext(cgContext: context!, flipped: false)
NSGraphicsContext.setCurrent(graphicsContext)
context!.beginPDFPage(nil)
page.draw(with: .mediaBox, to: context!)
let style = NSMutableParagraphStyle()
style.alignment = .center
let richText = NSAttributedString(string: "Hello, world!", attributes: [
NSAttributedStringKey.font: NSFont.systemFont(ofSize: 64),
NSAttributedStringKey.foregroundColor: NSColor.red,
NSAttributedStringKey.paragraphStyle: style
])
let richTextBounds = richText.size()
let point = CGPoint(x: mediaBox.midX - richTextBounds.width / 2, y: mediaBox.midY - richTextBounds.height / 2)
context!.saveGState()
do {
context!.translateBy(x: point.x, y: point.y)
context!.rotate(by: .pi / 5)
richText.draw(at: .zero)
}
context!.restoreGState()
context!.endPDFPage()
NSGraphicsContext.setCurrent(nil)
context?.closePDF()
}
And the line:
NSGraphicsContext.setCurrent(graphicsContext)
Throws an error that says "Type 'NSGraphicsContext' has no member 'setCurrent'"
Anyone have any ideas on what's going on? Is there something I'm missing in terms of a framework?

You need to use the current class property.
NSGraphicsContext.current = graphicsContext

Related

MKMarkerAnnotationView title not getting drawn on a MKSnapShot image

I'm trying to draw a marker on an image (MKSnapshot) with a title, but the title is not showing up.
Does anyone has any idea why this would be the case?
let annotation = MKPointAnnotation()
annotation.coordinate = location
annotation.title = "TitleTest"
let pinView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "test")
pinView.titleVisibility = MKFeatureVisibility.visible
pinView.dragState = .none
pinView.animatesWhenAdded = false
pinView.canShowCallout = false
pinView.titleVisibility = .visible
pinView.contentMode = .scaleAspectFit
pinView.bounds = CGRect(x: 0, y: 0, width: 40, height: 40)
if rect!.contains(point) {
let pinCenterOffset = pinView.centerOffset
point.x -= pinView.bounds.size.width / 2
point.y -= pinView.bounds.size.height / 2
point.x += pinCenterOffset.x
point.y += pinCenterOffset.y
}
pinView.drawHierarchy(in: CGRect(
x:point.x,
y:point.y,
width:pinView.bounds.width,
height:pinView.bounds.height),
afterScreenUpdates: true)
print("Draw a marker on iOS 11")
This is a screenshot of my UIImageView with the marker, but without the title:
The title can be drawn on the snapshot below the location of the pin:
determine the position of the annotation in the coordinate system of the image snapshot.point(for: annotation.coordinate)
create the attributes for title text, you can make it similar to what Apple uses:
private func titleAttributes() -> [NSAttributedStringKey: NSObject] {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let titleFont = UIFont.systemFont(ofSize: 10, weight: UIFont.Weight.semibold)
let attrs = [NSAttributedStringKey.font: titleFont,
NSAttributedStringKey.paragraphStyle: paragraphStyle]
return attrs
}
draw it right below the position of the annotation:
private func drawTitle(title: String,
at point: CGPoint,
attributes: [NSAttributedStringKey: NSObject]) {
let titleSize = title.size(withAttributes: attributes)
title.draw(with: CGRect(
x: point.x - titleSize.width / 2.0,
y: point.y + 1,
width: titleSize.width,
height: titleSize.height),
options: .usesLineFragmentOrigin,
attributes: attributes,
context: nil)
}
For a more complete example with code and a screenshot, see my following answer (it draws a custom pin image but the rest of the code can be used here).

Setup NSView for printing under macOs

I've managed to setup a function that allows me to print out a list from my macOs app.
I've set up a function that takes a String containing HTML
func makePDF(markup: String, amount: Int) {
let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let printOpts: [NSPrintInfo.AttributeKey: Any] = [NSPrintInfo.AttributeKey.jobDisposition: NSPrintInfo.JobDisposition.save, NSPrintInfo.AttributeKey.jobSavingURL: directoryURL]
let printInfo = NSPrintInfo(dictionary: printOpts)
printInfo.horizontalPagination = NSPrintInfo.PaginationMode.autoPagination
printInfo.verticalPagination = NSPrintInfo.PaginationMode.autoPagination
printInfo.topMargin = 20.0
printInfo.leftMargin = 20.0
printInfo.rightMargin = 20.0
printInfo.bottomMargin = 20.0
let view = NSView(frame: NSRect(x: 0, y: 0, width: 560, height: (60 + 36 * amount)))
if let htmlData = markup.data(using: String.Encoding.utf8) {
let attrStr = NSAttributedString(html: htmlData, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:
String.Encoding.utf8.rawValue], documentAttributes: nil)
let frameRect = NSRect(x: 0, y: 0, width: 560, height: (60 + 36 * amount))
let textField = NSTextField(frame: frameRect)
textField.attributedStringValue = attrStr!
view.addSubview(textField)
let printOperation = NSPrintOperation(view: view, printInfo: printInfo)
printOperation.showsPrintPanel = true
printOperation.showsProgressPanel = true
printOperation.run()
}
}
However I have trouble with the correct layout and set up of a Page. The other thing is that I have not managed to have a HTML with a border ( The border attribute is ignored)
Are there some best practices to set up a printed page (US-Letter or DIN A4) that fits without having overflowing border to a next page?
How can I manage to have a border in my HTML Table to be printed out?
Thanks

Swift - Overlay Text on current PDF Document

I have been trying to simply overlay text onto a current PDF document that is essentially a timecard. I copy the file to the downloads folder and that works fine, but then when I try to use a CGContext to add text, it exports a white PDF document. Can anyone see where I'm going wrong?
do {
try fileManager.copyItem(at: pdfURL!, to: destinationURL)
} catch let error as NSError {
print("Copy failed :( with error: \(error)")
}
if let pdf: CGPDFDocument = CGPDFDocument(destinationURL as CFURL) { // Create a PDF Document
if pdf.numberOfPages == 1 {
let pdfPage: CGPDFPage = pdf.page(at: 1)!
let pageRect = pdfPage.getBoxRect(CGPDFBox.mediaBox)
//print(pageRect)
let context = CGContext.init(destinationURL as CFURL, mediaBox: nil, nil)
let font = NSFont(name: "Helvetica Bold", size: 20.0)
let textRect = CGRect(x: 250, y: 250, width: 500, height: 40)
let paragraphStyle: NSParagraphStyle = NSParagraphStyle.default
let textColor = NSColor.black
let textFontAttributes = [
NSAttributedStringKey.font: font!,
NSAttributedStringKey.foregroundColor: textColor,
NSAttributedStringKey.paragraphStyle: paragraphStyle
]
let text: NSString = "Hello world"
text.draw(in: textRect, withAttributes: textFontAttributes)
context?.addRect(textRect)
context?.closePDF()
}
}
The following code is what I used to overlay text on macOS. I've been trying to find a link to the source answer I got this from. If I find it I'll edit this answer with a link.
// Confirm there is a document there
if let doc: PDFDocument = PDFDocument(url: srcURL) {
// Create a document, get the first page, and set the size of the page
let page: PDFPage = doc.page(at: 0)!
var mediaBox: CGRect = CGRect(x: 0, y: 0, width: 792, height: 612)
// This is where the magic happens. Create the drawing context on the PDF
let context = CGContext(dstURL as CFURL, mediaBox: &mediaBox, nil)
let graphicsContext = NSGraphicsContext(cgContext: context!, flipped: false)
NSGraphicsContext.current = graphicsContext
context!.beginPDFPage(nil)
// Draws the PDF into the context
page.draw(with: .mediaBox, to: context!)
// Parse and Draw Text on the context
drawText()
context!.saveGState()
context!.restoreGState()
context!.endPDFPage()
NSGraphicsContext.current = nil
context?.closePDF()
}

Draw on a PDF using Swift on macOS

My goal is to write text on a PDF, like an annotation.
I achieved it transforming the PDFPage to a NSImage, I drew on the NSImage then I saved the PDF formed by the images.
let image = NSImage(size: pageImage.size)
image.lockFocus()
let rect: NSRect = NSRect(x: 50, y: 50, width: 60, height: 20)
"Write it on the page!".draw(in: rect, withAttributes: someAttributes)
image.unlockFocus()
let out = PDFPage(image: image)
The problem is obviously that out (the new page of the output PDF) is a PDFPage of images and not a regular one. So the output PDF is very big in size and you can't copy and paste anything on it. It's just a sequence of images.
My question is if there's a way to add simple text on a PDF page programmatically without using NSImage. Any idea?
Note: There's this class in iOS programming UIGraphicsBeginPDFPageWithInfo which could be very helpful in my case. But I can't find the similar class for macOS development.
You can create a PDF graphics context on macOS and draw a PDFPage into it. Then you can draw more objects into the context using either Core Graphics or AppKit graphics.
Here's a test PDF I created by printing your question:
And here's the result from drawing that page into a PDF context, then drawing more text on top of it:
Here's the code I wrote to transform the first PDF into the second PDF:
import Cocoa
import Quartz
let inUrl: URL = URL(fileURLWithPath: "/Users/mayoff/Desktop/test.pdf")
let outUrl: CFURL = URL(fileURLWithPath: "/Users/mayoff/Desktop/testout.pdf") as CFURL
let doc: PDFDocument = PDFDocument(url: inUrl)!
let page: PDFPage = doc.page(at: 0)!
var mediaBox: CGRect = page.bounds(for: .mediaBox)
let gc = CGContext(outUrl, mediaBox: &mediaBox, nil)!
let nsgc = NSGraphicsContext(cgContext: gc, flipped: false)
NSGraphicsContext.current = nsgc
gc.beginPDFPage(nil); do {
page.draw(with: .mediaBox, to: gc)
let style = NSMutableParagraphStyle()
style.alignment = .center
let richText = NSAttributedString(string: "Hello, world!", attributes: [
NSFontAttributeName: NSFont.systemFont(ofSize: 64),
NSForegroundColorAttributeName: NSColor.red,
NSParagraphStyleAttributeName: style
])
let richTextBounds = richText.size()
let point = CGPoint(x: mediaBox.midX - richTextBounds.width / 2, y: mediaBox.midY - richTextBounds.height / 2)
gc.saveGState(); do {
gc.translateBy(x: point.x, y: point.y)
gc.rotate(by: .pi / 5)
richText.draw(at: .zero)
}; gc.restoreGState()
}; gc.endPDFPage()
NSGraphicsContext.current = nil
gc.closePDF()

How to create a PDF in Swift with Cocoa (Mac)

Xcode 7.3.2, Swift 2, Cocoa (Mac).
My app involves the user entering in some text, which can be exported to a PDF.
In the iOS version of my app, I can create the PDF relatively easily with the CoreText framework:
let html = "<font face=\'Futura\' color=\"SlateGray\"><h2>\(title)</h2></font><font face=\"Avenir\" color=\"SlateGray\"><h4>\(string)</h4></font>"
let fmt = UIMarkupTextPrintFormatter(markupText: html)
// 2. Assign print formatter to UIPrintPageRenderer
let render = UIPrintPageRenderer()
render.addPrintFormatter(fmt, startingAtPageAt: 0)
// 3. Assign paperRect and printableRect
let page = CGRect(x: 10, y: 10, width: 595.2, height: 841.8) // A4, 72 dpi, margin of 10 from top and left.
let printable = page.insetBy(dx: 0, dy: 0)
render.setValue(NSValue(cgRect: page), forKey: "paperRect")
render.setValue(NSValue(cgRect: printable), forKey: "printableRect")
// 4. Create PDF context and draw
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)
for i in 1...render.numberOfPages {
UIGraphicsBeginPDFPage();
let bounds = UIGraphicsGetPDFContextBounds()
render.drawPage(at: i - 1, in: bounds)
}
UIGraphicsEndPDFContext();
// 5. Save PDF file
path = "\(NSTemporaryDirectory())\(title).pdf"
pdfData.write(toFile: path, atomically: true)
However, UIMarkupTextPrintFormatter, UIPrintPageRenderer, UIGraphicsBeginPDFContextToData, and UIGraphicsEndPDFContext all do not exist on OS X. How can I do the exact same thing as I am doing with this iOS code (create a basic PDF from some HTML and write it to a certain file path as a paginated PDF) with Mac and Cocoa?
EDIT: The answer to this question is here: Create a paginated PDF—Mac OS X.
Here is a function that will generate a PDF from pure HTML.
func makePDF(markup: String) {
let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let printOpts: [NSPrintInfo.AttributeKey: Any] = [NSPrintInfo.AttributeKey.jobDisposition: NSPrintInfo.JobDisposition.save, NSPrintInfo.AttributeKey.jobSavingURL: directoryURL]
let printInfo = NSPrintInfo(dictionary: printOpts)
printInfo.horizontalPagination = NSPrintingPaginationMode.AutoPagination
printInfo.verticalPagination = NSPrintingPaginationMode.AutoPagination
printInfo.topMargin = 20.0
printInfo.leftMargin = 20.0
printInfo.rightMargin = 20.0
printInfo.bottomMargin = 20.0
let view = NSView(frame: NSRect(x: 0, y: 0, width: 570, height: 740))
if let htmlData = markup.dataUsingEncoding(NSUTF8StringEncoding) {
if let attrStr = NSAttributedString(HTML: htmlData, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil) {
let frameRect = NSRect(x: 0, y: 0, width: 570, height: 740)
let textField = NSTextField(frame: frameRect)
textField.attributedStringValue = attrStr
view.addSubview(textField)
let printOperation = NSPrintOperation(view: view, printInfo: printInfo)
printOperation.showsPrintPanel = false
printOperation.showsProgressPanel = false
printOperation.run()
}
}
}
What is happening:
Put the HTML into a NSAttributedString.
Render the NSAttributedString to a NSTextField.
Render the NSTextField to a NSView.
Create a NSPrintOperation with that NSView.
Set the printing parameters to save as a PDF.
Run the print operation (which actually opens a dialog to save the PDF)
Everyone is happy.
This is not a perfect solution. Note the hard coded integer values.