Setup NSView for printing under macOs - swift

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

Related

How can I properly pass an array in iOS PDFKit

I'm trying to in an [string] array for a PDF. Below is what I have so far. I am guess I need to do a foreach somewhere, but I'm not entirely sure.
I thought something like this might work, but it does not.
for entry in body {
let attributedText = NSAttributedString(
string: entry,
attributes: textAttributes
)
}
private func addBody(body: [String], pageRect: CGRect, textTop: CGFloat) {
let pageWidth = 8.5 * 72.0
let pageHeight = 11 * 72.0
let pageRect = CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
let bodyCG = addInstructor(instructor: "", pageRect: pageRect)
let textFont = UIFont.systemFont(ofSize: 12.0, weight: .regular)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .natural
paragraphStyle.lineBreakMode = .byWordWrapping
let textAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: textFont
]
let attributedText = NSAttributedString(
string: body,
attributes: textAttributes
)
let textRect = CGRect(
x: 15,
y: bodyCG + 30,
width: pageRect.width - 20,
height: pageRect.height - textTop - pageRect.height / 5.0
)
attributedText.draw(in: textRect)
}
Adding some additional details. If I don't use [String] and just use String everything works fine. The PDF is generated, The concept I'm struggling to understand is how can I pass an array for the PDF.
var courseAttendees : [String] = ["name", "name", "name", "name"]
For Example, I want to pass courseAttendees and then loop through the array the names are just overlapped and shown below.
Final code.
private func addBody(body: [String], textTop: CGFloat) {
let pageWidth = 8.5 * 72.0
let pageHeight = 11 * 72.0
let pageRect = CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
let bodyCG = addInstructor(instructor: "", pageRect: pageRect)
let textFont = UIFont.systemFont(ofSize: 12.0, weight: .regular)
let textAttributes: [NSAttributedString.Key: Any] =
[NSAttributedString.Key.font: textFont]
// keep track of the y position on the page. You might need
// to set this globally as you have multiple pages
var currentYPos: CGFloat = bodyCG
// Loop through the array
for entry in body {
let attributedText = NSAttributedString(
string: "\(entry)",
attributes: textAttributes
)
// Update the currentYPos
currentYPos += 15
// Use the currentYPos in the textRect
let textRect = CGRect(
x: 15,
y: currentYPos,
width: pageRect.width - 20,
height: pageRect.height - textTop - pageRect.height / 5.0
)
attributedText.draw(in: textRect)
}
}
Based on your question and image, I am assuming the PDF creation works fine but the data is not rendered as desired.
I think the two questions to answer here are:
Where to loop through your array
How to keep track of the current y coordinate in the page which is responsible for the vertical positioning
Here are some additions I made to try and fix your issue, I have added comments to what I have changed
// Somewhere appropriate in your code
var courseAttendees : [String] = ["name1", "name2", "name3", "name4"]
// Call the function, 15 is just a random number for textTop,
// give it what you feel is appropriate
addBody(body: courseAttendees, textTop: 15)
// I have removed the pageRect parameter since you create it
// in the function
private func addBody(body: [String], textTop: CGFloat) {
let pageWidth = 8.5 * 72.0
let pageHeight = 11 * 72.0
let pageRect = CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
let bodyCG = addInstructor(instructor: "", pageRect: pageRect)
let textFont = UIFont.systemFont(ofSize: 12.0, weight: .regular)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .natural
paragraphStyle.lineBreakMode = .byWordWrapping
let textAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: textFont
]
// keep track of the y position on the page. You might need
// to set this globally as you have multiple pages
var currentYPos: CGFloat = 0.0
// Loop through the array
for entry in body {
let attributedText = NSAttributedString(
string: entry,
attributes: textAttributes
)
// Update the currentYPos
currentYPos += bodyCG + 30
// Use the currentYPos in the textRect
let textRect = CGRect(
x: 15,
y: currentYPos,
width: pageRect.width - 20,
height: pageRect.height - textTop - pageRect.height / 5.0
)
attributedText.draw(in: textRect)
}
}
I have not tested the above so please give this a try and check if it solves your issue.
If not, comment and I will update this accordingly.

Create PDF of UITableView to AirPrint Xcode and Swift

Looking for a solution to create a pdf programatically with the contents of my UITabelView with a label on top and allow the users to print on standard A4 paper.
This is my not working code:
func createPdfFromView(aView: UIView, saveToDocumentsWithFileName fileName: String) {
let pdfTitle = "\(pilotName)'s Past Duty"
let pageSize = CGRect(x: 0, y: 0, width: 11 * 70, height: 8.5 * 70)
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, pageSize, nil)
UIGraphicsBeginPDFPage()
let font = UIFont.preferredFont(forTextStyle: .largeTitle)
let attributedPDFTitle = NSAttributedString(string: pdfTitle, attributes: [NSAttributedString.Key.font: font])
let stringSize = attributedPDFTitle.size()
let stringRect = CGRect(x: (pageSize.width / 2 - stringSize.width / 2), y: 20, width: stringSize.width, height: stringSize.height)
attributedPDFTitle.draw(in: stringRect)
aView.layer.render(in: UIGraphicsGetCurrentContext()!)
UIGraphicsGetCurrentContext()!.translateBy(x: 0, y: 40)
UIGraphicsEndPDFContext()
if let documentDirectories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
let documentsFileName = documentDirectories + "/" + fileName
debugPrint(documentsFileName)
pdfData.write(toFile: documentsFileName, atomically: true)
}
}

Generate multipage PDF UIView

I want to create multipage PDF of self.view. I have following viewController with tableView now I want to create the pdf of the view.
This is what I have done but it's create single page pdf only
func pdfDataWithTableView(tableView: UITableView) {
let priorBounds = tableView.bounds
let fittedSize = tableView.sizeThatFits(CGSize(width:priorBounds.size.width, height:tableView.contentSize.height))
tableView.bounds = CGRect(x:0, y:0, width:fittedSize.width, height:fittedSize.height)
let pdfPageBounds = CGRect(x:0, y:0, width:tableView.frame.width, height:self.view.frame.height)
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, pdfPageBounds,nil)
var pageOriginY: CGFloat = 0
while pageOriginY < fittedSize.height {
UIGraphicsBeginPDFPageWithInfo(pdfPageBounds, nil)
UIGraphicsGetCurrentContext()!.saveGState()
UIGraphicsGetCurrentContext()!.translateBy(x: 0, y: -pageOriginY)
tableView.layer.render(in: UIGraphicsGetCurrentContext()!)
UIGraphicsGetCurrentContext()!.restoreGState()
pageOriginY += pdfPageBounds.size.height
}
UIGraphicsEndPDFContext()
tableView.bounds = priorBounds
var docURL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)).last! as URL
docURL = docURL.appendingPathComponent("myPassBook.pdf")
pdfData.write(to: docURL as URL, atomically: true)
share()
}
On save Button Call Below function
func createImg() -> UIImage {
guard tblView.numberOfSections > 0, tblView.numberOfRows(inSection: 0) > 0 else {
let errorImage = UIImage(named: "Error Image")
return errorImage!
}
UIGraphicsBeginImageContextWithOptions(CGSize(width: tblView.contentSize.width, height: tblView.contentSize.height), false, 0.0)
let context = UIGraphicsGetCurrentContext()
let previousFrame = tblView.frame
tblView.frame = CGRect(x: tblView.frame.origin.x, y: tblView.frame.origin.y, width: tblView.contentSize.width, height: tblView.contentSize.height)
// Draw view in that context
tblView.layer.render(in: context!)
tblView.frame = previousFrame
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
createPDFDataFromImage(image: image)
return image;
}
Create PDF from image and Share
func createPDFDataFromImage(image: UIImage){
let pdfData = NSMutableData()
let imgView = UIImageView.init(image: image)
let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
UIGraphicsBeginPDFContextToData(pdfData, imageRect, nil)
UIGraphicsBeginPDFPage()
let context = UIGraphicsGetCurrentContext()
imgView.layer.render(in: context!)
UIGraphicsEndPDFContext()
//try saving in doc dir to confirm:
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last
let path = dir?.appendingPathComponent("file.pdf")
do {
try pdfData.write(to: path!, options: NSData.WritingOptions.atomic)
} catch {
print("error catched")
}
let activityViewController = UIActivityViewController(activityItems: [pdfData] , applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}

Swift macOS how to print with NSPrintInfo and NSAttributedString

I want to print out a list in my App.
However if the list is too large to fit on one page there is a problem with
the last and the first line on a page.
e.g. the last line on a page is cropped and some parts are shown on top of next page.
Screenshot of bottom of page 1
Screenshot of top of page 2
How can I manage to set up my print job that the text is set up correctly on every page?
Currently I have the following code snippet:
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()
}
}

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.