Generating a PDF Document in Swift
Created By: Debasis Das (31-Jan-2016)
In this article we will create/ generate a PDF document in swift.
The structure of the PDF Document will be as follows
- Cover Page
- Title
- Credit Information
- Header Text
- Footer Text
- Page Numbers
- Data Pages (paginated)
- Header Text
- Footer Text
- Page Numbers
- Tabular Data with configurable number of rows per page
The final output will look like the following image
Swift PDF Generation Sample Code
The PDF constants that we have used in this sample code are as follows
// PDFConstants.swift
// SwiftPDFGeneration
// Created by Debasis Das on 01/02/16.
// Copyright © 2016 Knowstack. All rights reserved.
import Foundation
let defaultRowHeight = CGFloat(23.0)
let defaultColumnWidth = CGFloat(150.0)
let numberOfRowsPerPage = 50
let topMargin = CGFloat(40.0)
let leftMargin = CGFloat(20.0)
let rightMargin = CGFloat(20.0)
let bottomMargin = CGFloat (40.0)
let textInset = CGFloat(5.0)
let verticalPadding = CGFloat (10.0)
// AppDelegate.swift // SwiftPDFGeneration // Created by Debasis Das on 01/02/16. // Copyright © 2016 Knowstack. All rights reserved. import Cocoa import Quartz @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { @IBOutlet weak var window: NSWindow! var dataArray:[AnyObject] = [] //The list of columns that will be the columns for the tabular data in the pdf file let columnInformationArray = [ ["columnIdentifier":"col1","columnTitle":"Column Title 1"], ["columnIdentifier":"col2","columnTitle":"Column Title 2"], ["columnIdentifier":"col3","columnTitle":"Column Title 3"], ["columnIdentifier":"col4","columnTitle":"Column Title 4"], ["columnIdentifier":"col5","columnTitle":"Column Title 5"], ["columnIdentifier":"col6","columnTitle":"Column Title 6"], ["columnIdentifier":"col7","columnTitle":"Column Title 7"], ["columnIdentifier":"col8","columnTitle":"Column Title 8"], ] func applicationDidFinishLaunching(aNotification: NSNotification) { self.dataArray = self.createDemoData() } func applicationWillTerminate(aNotification: NSNotification) { } func createDemoData()->Array<AnyObject>{ let mArray = NSMutableArray() for var i = 0; i < 800; i++ { let dict = [ "col1":"col 1 data \(i)", "col2":"col 2 data \(i)", "col3":"col 3 data \(i)", "col4":"col 4 data \(i)", "col5":"col 5 data \(i)", "col6":"col 6 data \(i)", "col7":"col 7 data \(i)", "col8":"col 8 data \(i)" ] mArray.addObject(dict) } return Array(mArray) } @IBAction func generatePDF (sender:NSButton){ let aPDFDocument = PDFDocument() let coverPage = CoverPDFPage(hasMargin: true, title: "This is the cover page title. Keep it short or keep it long", creditInformation: "Created By: Knowstack.com \r Jan 2016", headerText: "Some confidential info", footerText: "www.knowstack.com", pageWidth: CGFloat(900.0), pageHeight: CGFloat(1200.0), hasPageNumber: true, pageNumber: 1) aPDFDocument.insertPage(coverPage, atIndex: 0) let pageWidth = (CGFloat(Float(self.columnInformationArray.count)) * defaultColumnWidth) + leftMargin let pageHeight = topMargin + verticalPadding + (CGFloat(numberOfRowsPerPage + 1) * defaultRowHeight) + verticalPadding + bottomMargin var numberOfPages = self.dataArray.count / numberOfRowsPerPage if self.dataArray.count % numberOfRowsPerPage > 0 { numberOfPages++ } for var i = 0; i < numberOfPages; i++ { let startIndex = i * numberOfRowsPerPage var endIndex = i * numberOfRowsPerPage + numberOfRowsPerPage if endIndex > self.dataArray.count{ endIndex = self.dataArray.count } let pdfDataArray:[AnyObject] = Array(self.dataArray[startIndex..<endIndex]) let tabularDataPDF = TabularPDFPage (hasMargin: true, headerText: "confidential info...", footerText: "www.knowstack.com", pageWidth: pageWidth, pageHeight: pageHeight, hasPageNumber: true, pageNumber: i+1, pdfData: pdfDataArray, columnArray: columnInformationArray) aPDFDocument.insertPage(tabularDataPDF, atIndex: i+1) } aPDFDocument.writeToFile("/Users/debasisdas/sample1.pdf") //Replace the folder location where you want to download or use a save panel } } //We have created a Base class called as BasePDFClass that will have common functionality as drawing the header & footer text, page numbers and drawing the margins class BasePDFPage :PDFPage{ var hasMargin = true var headerText = "Default Header Text" var footerText = "Default Footer Text" var hasPageNumber = true var pageNumber = 1 var pdfHeight = CGFloat(1024.0) //This is configurable var pdfWidth = CGFloat(768.0) //This is configurable and is calculated based on the number of columns func drawLine( fromPoint:NSPoint, toPoint:NSPoint){ let path = NSBezierPath() NSColor.lightGrayColor().set() path.moveToPoint(fromPoint) path.lineToPoint(toPoint) path.lineWidth = 0.5 path.stroke() } func drawHeader(){ let headerTextX = leftMargin let headerTextY = self.pdfHeight - CGFloat(35.0) let headerTextWidth = self.pdfWidth - leftMargin - rightMargin let headerTextHeight = CGFloat(20.0) let headerFont = NSFont(name: "Helvetica", size: 15.0) let headerParagraphStyle = NSMutableParagraphStyle() headerParagraphStyle.alignment = NSRightTextAlignment let headerFontAttributes = [ NSFontAttributeName: headerFont ?? NSFont.labelFontOfSize(12), NSParagraphStyleAttributeName:headerParagraphStyle, NSForegroundColorAttributeName:NSColor.lightGrayColor() ] let headerRect = NSMakeRect(headerTextX, headerTextY, headerTextWidth, headerTextHeight) self.headerText.drawInRect(headerRect, withAttributes: headerFontAttributes) } func drawFooter(){ let footerTextX = leftMargin let footerTextY = CGFloat(15.0) let footerTextWidth = self.pdfWidth / 2.1 let footerTextHeight = CGFloat(20.0) let footerFont = NSFont(name: "Helvetica", size: 15.0) let footerParagraphStyle = NSMutableParagraphStyle() footerParagraphStyle.alignment = NSLeftTextAlignment let footerFontAttributes = [ NSFontAttributeName: footerFont ?? NSFont.labelFontOfSize(12), NSParagraphStyleAttributeName:footerParagraphStyle, NSForegroundColorAttributeName:NSColor.lightGrayColor() ] let footerRect = NSMakeRect(footerTextX, footerTextY, footerTextWidth, footerTextHeight) self.footerText.drawInRect(footerRect, withAttributes: footerFontAttributes) } func drawMargins(){ let borderLine = NSMakeRect(leftMargin, bottomMargin, self.pdfWidth - leftMargin - rightMargin, self.pdfHeight - topMargin - bottomMargin) NSColor.grayColor().set() NSFrameRectWithWidth(borderLine, 0.5) } func drawPageNumbers() { let pageNumTextX = self.pdfWidth/2 let pageNumTextY = CGFloat(15.0) let pageNumTextWidth = CGFloat(40.0) let pageNumTextHeight = CGFloat(20.0) let pageNumFont = NSFont(name: "Helvetica", size: 15.0) let pageNumParagraphStyle = NSMutableParagraphStyle() pageNumParagraphStyle.alignment = NSCenterTextAlignment let pageNumFontAttributes = [ NSFontAttributeName: pageNumFont ?? NSFont.labelFontOfSize(12), NSParagraphStyleAttributeName:pageNumParagraphStyle, NSForegroundColorAttributeName: NSColor.darkGrayColor() ] let pageNumRect = NSMakeRect(pageNumTextX, pageNumTextY, pageNumTextWidth, pageNumTextHeight) let pageNumberStr = "\(self.pageNumber)" pageNumberStr.drawInRect(pageNumRect, withAttributes: pageNumFontAttributes) } override func boundsForBox(box: PDFDisplayBox) -> NSRect { return NSMakeRect(0, 0, pdfWidth, pdfHeight) } override func drawWithBox(box: PDFDisplayBox) { if hasPageNumber{ self.drawPageNumbers() } if hasMargin{ self.drawMargins() } if headerText.characters.count > 0 { self.drawHeader() } if footerText.characters.count > 0{ self.drawFooter() } } init(hasMargin:Bool, headerText:String, footerText:String, pageWidth:CGFloat, pageHeight:CGFloat, hasPageNumber:Bool, pageNumber:Int) { super.init() self.hasMargin = hasMargin self.headerText = headerText self.footerText = footerText self.pdfWidth = pageWidth self.pdfHeight = pageHeight self.hasPageNumber = hasPageNumber self.pageNumber = pageNumber } } //The Cover Page for the PDF Document class CoverPDFPage: BasePDFPage{ var pdfTitle:NSString = "Default PDF Title" var creditInformation = "Default Credit Information" init(hasMargin:Bool, title:String, creditInformation:String, headerText:String, footerText:String, pageWidth:CGFloat, pageHeight:CGFloat, hasPageNumber:Bool, pageNumber:Int) { super.init(hasMargin: hasMargin, headerText: headerText, footerText: footerText, pageWidth: pageWidth, pageHeight: pageHeight, hasPageNumber: hasPageNumber, pageNumber: pageNumber) self.pdfTitle = title self.creditInformation = creditInformation } func drawPDFTitle() { let pdfTitleX = 1/4 * self.pdfWidth let pdfTitleY = self.pdfHeight / 2 let pdfTitleWidth = 1/2 * self.pdfWidth let pdfTitleHeight = 1/5 * self.pdfHeight let titleFont = NSFont(name: "Helvetica Bold", size: 30.0) let titleParagraphStyle = NSMutableParagraphStyle() titleParagraphStyle.alignment = NSCenterTextAlignment let titleFontAttributes = [ NSFontAttributeName: titleFont ?? NSFont.labelFontOfSize(12), NSParagraphStyleAttributeName:titleParagraphStyle, NSForegroundColorAttributeName: NSColor.blueColor() ] let titleRect = NSMakeRect(pdfTitleX, pdfTitleY, pdfTitleWidth, pdfTitleHeight) self.pdfTitle.drawInRect(titleRect, withAttributes: titleFontAttributes) } func drawPDFCreditInformation() { let pdfCreditX = 1/4 * self.pdfWidth let pdfCreditY = self.pdfHeight / 2 - 1/5 * self.pdfHeight let pdfCreditWidth = 1/2 * self.pdfWidth let pdfCreditHeight = CGFloat(40.0) let creditFont = NSFont(name: "Helvetica", size: 15.0) let creditParagraphStyle = NSMutableParagraphStyle() creditParagraphStyle.alignment = NSCenterTextAlignment let creditFontAttributes = [ NSFontAttributeName: creditFont ?? NSFont.labelFontOfSize(12), NSParagraphStyleAttributeName:creditParagraphStyle, NSForegroundColorAttributeName: NSColor.darkGrayColor() ] let creditRect = NSMakeRect(pdfCreditX, pdfCreditY, pdfCreditWidth, pdfCreditHeight) self.creditInformation.drawInRect(creditRect, withAttributes: creditFontAttributes) } override func drawWithBox(box: PDFDisplayBox) { super.drawWithBox(box) self.drawPDFTitle() self.drawPDFCreditInformation() } } //Tabular PDF Page class TabularPDFPage: BasePDFPage{ var dataArray = [] var columnsArray = [] var verticalPadding = CGFloat(10.0) init(hasMargin:Bool, headerText:String, footerText:String, pageWidth:CGFloat, pageHeight:CGFloat, hasPageNumber:Bool, pageNumber:Int, pdfData:[AnyObject], columnArray:[AnyObject]) { super.init(hasMargin: hasMargin, headerText: headerText, footerText: footerText, pageWidth: pageWidth, pageHeight: pageHeight, hasPageNumber: hasPageNumber, pageNumber: pageNumber ) self.dataArray = pdfData self.columnsArray = columnArray } func drawTableData(){ //If draws column title = YES let titleFont = NSFont(name: "Helvetica Bold", size: 14.0) let titleParagraphStyle = NSMutableParagraphStyle() titleParagraphStyle.alignment = NSCenterTextAlignment let titleFontAttributes = [ NSFontAttributeName: titleFont ?? NSFont.labelFontOfSize(12), NSParagraphStyleAttributeName:titleParagraphStyle, NSForegroundColorAttributeName: NSColor.grayColor() ] for var i=0 ; i < self.columnsArray.count; i++ { let columnHeader = self.columnsArray[i] let columnTitle = columnHeader["columnTitle"] as! NSString let headerRect = NSMakeRect( leftMargin + (CGFloat(i) * defaultColumnWidth), self.pdfHeight - topMargin - verticalPadding - defaultRowHeight, defaultColumnWidth, defaultRowHeight) columnTitle.drawInRect(headerRect, withAttributes: titleFontAttributes) } let keys = NSMutableArray() for columnInfo in self.columnsArray{ keys.addObject(columnInfo["columnIdentifier"] as! String) } for var i = 0 ; i < self.dataArray.count; i++ { let dataDict = self.dataArray[i] for var j = 0 ; j < keys.count; j++ { let dataText = dataDict[keys[j] as! String] as! NSString let dataRect = NSMakeRect( leftMargin + textInset + (CGFloat(j) * defaultColumnWidth), self.pdfHeight - topMargin - verticalPadding - (2 * defaultRowHeight) - textInset - (CGFloat(i) * defaultRowHeight), defaultColumnWidth, defaultRowHeight ) dataText.drawInRect(dataRect, withAttributes: nil) } } } func drawVerticalGrids(){ for var i = 0 ; i <= self.columnsArray.count; i++ { //draw the vertical lines let fromPoint = NSMakePoint( leftMargin + (CGFloat(i) * defaultColumnWidth), self.pdfHeight - topMargin ) let toPoint = NSMakePoint( leftMargin + (CGFloat(i) * defaultColumnWidth), bottomMargin //self.pdfHeight - (CGFloat(self.dataArray.count + 2) * defaultRowHeight) - topMargin ) drawLine(fromPoint, toPoint: toPoint) } } func drawHorizontalGrids(){ let rowCount = self.dataArray.count for var i = 0 ; i <= rowCount ; i++ { let fromPoint = NSMakePoint( leftMargin , self.pdfHeight - topMargin - verticalPadding - defaultRowHeight - (CGFloat(i) * defaultRowHeight) ) let toPoint = NSMakePoint(self.pdfWidth - rightMargin, self.pdfHeight - topMargin - verticalPadding - defaultRowHeight - (CGFloat(i) * defaultRowHeight) ) drawLine(fromPoint, toPoint: toPoint) } } override func drawWithBox(box: PDFDisplayBox) { super.drawWithBox(box) self.drawTableData() self.drawVerticalGrids() self.drawHorizontalGrids() } }
You can download the sample code hereSwift PDF Generation Sample Code
Nice work!
Hope you don’t mind but I took your source code and augmented it a little.
Made it pop up with a dialog to choose the save location, and added a second PDF button that generates a PDF using the Mustache template library.
https://github.com/johnantoni/SwiftPDFGeneration
Thanks John for the feedback. Will check the Mustache implementation.
could you please add bookmark while generating pdf
This is rad. How would it work if I wanted to put an image in a column for each row? Everything I’m seeing online shows that I can only use text for this.
I have plans to write a sample for downloading images into table column in a pdf
Thanks for the Sample.
Can You help me with a little sample, to insert a NSImage from xcassets in to the PDF?
I can work with NSImage in Storyboard / Viewcontroller but it is no works in the PDF.
I have solved the problem.
let myImage = NSImage(named: “Logo”)!
let imageRect = NSMakeRect(10.0, 10.0, 200.0, 200.0)
myImage.draw(in: imageRect)
thx
I’ve been looking for something exactly like this on Swift, but for iOS. Can this be converted for that. I haven’t experience developing for Mac yet.