Create PDF view programmatically using PDFKit in swift.

Updated 2 December 2020

Save

In the article, I will discuss, how we can create PDF programmatically using the PDFKit framework.

First of all, The PDFKit is a framework available in AppKit since iOS 11.0 and macOS 10.4. Please click here to read more about PDFKit.

PDFView is mainly used to show the pdf file in our application and also it allows users to set zoom level, select content, navigate through a document, and copy the textual content to the Pasteboard, and also keeps track of page history.

Getting Started

Swift version: 5
iOS version: 13
Xcode: 11.3

First Create a project from Xcode.

File ▸ New ▸ Project…. Choose the iOS ▸ Application ▸ Single View App template and create a new project.

Then create a PDFCreator class where we will add all functions and variables which helps us to create the PDF.

Now we will add basic variables that manage the PDF layout.

pageWidth : To define page width of PDF.

pageHeight : To define page Height of PDF.

marginPoint : To define the page margin of PDF.

currentPage : To Store page count of the PDF.

Now create a function that creates the actual PDF data.

Now add a page footer in the PDF.

And, Now show this PDF Data in the ViewController.

 

PDF Screenshot

 

I hope this code will help you better to understand how PDF generates. If you feel any doubt or query please comment below.

Thank you.

author
. . .

Leave a Comment

Your email address will not be published. Required fields are marked*


15 comments

  • Dipesh
    Can you link you git project how you have worked it , tried same but could not get something working
    • Debabrata Roy (Moderator)
      We don’t have any git project for this blog. please share issue details here, we will help you to fix it.
      • Ronald
        Please explain how to “Add that PDF Data in the ViewController”.
        • Debabrata Roy (Moderator)
          I have add complete code in comment, please check
  • Hubert
    hello,

    It would be great if you could write a similar post dedicated to macOS ! How generate a pdf on macOS big sur ? Thanks

    • Debabrata Roy (Moderator)
      Please try GraphicsContext to create PDF for macOS.
      • Andrew
        Is there any guides on using GraphicsContext to create PDF for MacOS? I can’t seem to find anything online…
        • Debabrata Roy (Moderator)
          Please check this doc.https://www.hackingwithswift.com/read/27/3/drawing-into-a-core-graphics-context-with-uigraphicsimagerenderer
  • Esteban
    Hi Roy,
    For some reason this is the topic that gives me the hugest headache of my life, is there any chance that you can make a video of create a PDF file (single page) to show more details on this? You are the most updated and I’ve many videos that are out of date
    • Debabrata Roy (Moderator)
      I have added full code in below.
  • Debabrata Roy (Moderator)
    import UIKit
    import PDFKit

    class PDFCreator: NSObject {
    var headerImage = UIImage(named: “qr-code”)
    let defaultOffset: CGFloat = 20
    let tableDataItems: String
    var yPosition : CGFloat = 0
    var drawContext: CGContext!
    var topMargin : CGFloat = 70
    var bottomMargin : CGFloat = 30
    var currentPageNo = 1
    var title = “”
    var topTitle = “CUSTOM REPORT”

    var backgroundColor = UIColor(hexString: “#F19A37”)
    init(tableDataItems: String,headerImage:UIImage,title:String,topTitle:String) {
    self.tableDataItems = tableDataItems
    self.headerImage = headerImage
    self.title = title
    self.topTitle = topTitle
    }

    func create() -> Data {
    // default page format
    let pageWidth = 8.3 * 72.0
    let pageHeight = 11 * 72.0
    let pageRect = CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
    let renderer = UIGraphicsPDFRenderer(bounds: pageRect, format: UIGraphicsPDFRendererFormat())
    let data = renderer.pdfData { context in
    drawContext = context.cgContext
    context.beginPage()
    drawTableHeaderTitles( pageRect: pageRect)
    footer(pageRect: pageRect)
    drawtitle(pageRect: pageRect)
    drawTableData(drawContext: drawContext, pageRect: pageRect, data: tableDataItems)
    if currentPageNo != 1{
    waterMark(pageRect: pageRect, context: drawContext)
    }
    }
    return data
    }

    func calculateNumberOfElementsPerPage(with pageRect: CGRect) -> Int {
    let rowHeight = (defaultOffset * 3)
    let number = Int((pageRect.height – rowHeight) / rowHeight)
    return number
    }
    }

    // Drawings
    extension PDFCreator {
    func drawtitle(pageRect: CGRect){
    if title != “”{
    drawContext.saveGState()

    let style = NSMutableParagraphStyle()

    style.alignment = NSTextAlignment.center
    var att = boldAttribute(size: 16, color: .black)
    att[.paragraphStyle] = style
    let attributedText = NSAttributedString(string: title, attributes: att)
    let height = attributedText.height(containerWidth: pageRect.width – (2*defaultOffset))
    let textRect = CGRect(x: 10 ,
    y: yPosition + 10,
    width: pageRect.width,
    height: height)
    attributedText.draw(in: textRect)
    title = “”
    yPosition += height + 20
    drawContext.restoreGState()
    }
    }

    func drawTableTopImage(drawContext: CGContext,pageRect: CGRect,image:UIImage) {
    drawContext.saveGState()
    drawContext.restoreGState()
    image.draw(in: pageRect)
    let textFont = UIFont.systemFont(ofSize: 45)
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.alignment = .left
    paragraphStyle.lineBreakMode = .byWordWrapping
    paragraphStyle.alignment = NSTextAlignment.center
    let textAttributes = [
    NSAttributedString.Key.paragraphStyle: paragraphStyle,
    NSAttributedString.Key.font: textFont,
    NSAttributedString.Key.foregroundColor : UIColor.white
    ]

    let attributedText = NSAttributedString(string: topTitle, attributes: textAttributes)
    let textRect = CGRect(x: 110 ,
    y: 195,
    width: 350,
    height: 140)
    attributedText.draw(in: textRect)
    yPosition += pageRect.height

    }

    func drawTableData(drawContext: CGContext,pageRect: CGRect,data: String) {

    drawContext.setLineWidth(1.0)
    drawContext.saveGState()

    drawContext.move(to: CGPoint(x: defaultOffset, y: defaultOffset ))

    var lastDrawnFrame = CGRect(x: 0, y: 0, width: 0, height: 0)
    let textAttributes = normalAttribute()
    let actualRiskName = NSAttributedString(string: data, attributes: textAttributes)

    lastDrawnFrame = drawAttributedText(actualRiskName,width:pageRect.width – 20, pageRect: pageRect, currentOffset: CGPoint(x: defaultOffset + lastDrawnFrame.width + 13, y: yPosition-1 ))
    yPosition = lastDrawnFrame.origin.y + lastDrawnFrame.height

    yPosition += 10

    drawContext.restoreGState()

    }

    func footer (pageRect: CGRect){
    drawContext.saveGState()
    drawContext.setFillColor(backgroundColor.cgColor)
    let textFont = UIFont.systemFont(ofSize: 10)
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.alignment = .center
    paragraphStyle.lineBreakMode = .byWordWrapping
    let textAttributes = [
    NSAttributedString.Key.paragraphStyle: paragraphStyle,
    NSAttributedString.Key.font: textFont,
    NSAttributedString.Key.foregroundColor:UIColor.white
    ]
    drawContext.move(to: CGPoint(x: 0, y: pageRect.height – bottomMargin))
    drawContext.addRect(CGRect(x: 0, y: Int(pageRect.height – bottomMargin), width: Int((pageRect.width – 60)/3), height: Int(bottomMargin)))
    drawContext.fill(CGRect(x: 0, y: Int(pageRect.height – bottomMargin), width: Int((pageRect.width – 60)/3), height: Int(bottomMargin)))
    var text = NSAttributedString(string: “Webkul Software PVT LTD.”, attributes: textAttributes)

    text.draw(in: CGRect(x: 0, y: Int(pageRect.height – bottomMargin + 7), width: Int((pageRect.width – 60)/3), height: Int(bottomMargin)))
    drawContext.setFillColor(backgroundColor.cgColor)
    drawContext.addRect(CGRect(x: Int((pageRect.width – 60)/3) + 2, y: Int(pageRect.height – bottomMargin), width: Int((pageRect.width – 60)/3), height: Int(bottomMargin)))
    drawContext.fill(CGRect(x: Int((pageRect.width – 60)/3) + 2, y: Int(pageRect.height – bottomMargin), width: Int((pageRect.width – 60)/3), height: Int(bottomMargin)))
    text = NSAttributedString(string: “000-000-0000”, attributes: textAttributes)

    text.draw(in: CGRect(x: Int((pageRect.width – 60)/3) + 2, y: Int(pageRect.height – bottomMargin + 7), width: Int((pageRect.width – 60)/3), height: Int(bottomMargin)))

    drawContext.setFillColor(backgroundColor.cgColor)

    drawContext.addRect(CGRect(x: (Int((pageRect.width – 60)/3) * 2) + 4, y: Int(pageRect.height – bottomMargin), width: Int((pageRect.width – 60)/3), height: Int(bottomMargin)))
    drawContext.fill(CGRect(x: (Int((pageRect.width – 60)/3) * 2) + 4, y: Int(pageRect.height – bottomMargin), width: Int((pageRect.width – 60)/3), height: Int(bottomMargin)))
    text = NSAttributedString(string: “[email protected]”, attributes: textAttributes)

    text.draw(in: CGRect(x: (Int((pageRect.width – 60)/3) * 2) + 4, y: Int(pageRect.height – bottomMargin + 7), width: Int((pageRect.width – 60)/3), height: Int(bottomMargin)))

    drawContext.setFillColor(backgroundColor.cgColor)
    drawContext.addRect(CGRect(x: Int(pageRect.width – 54), y: Int(pageRect.height – bottomMargin), width: 54, height: Int(bottomMargin)))
    drawContext.fill(CGRect(x: Int(pageRect.width – 54), y: Int(pageRect.height – bottomMargin), width: 54, height: Int(bottomMargin)))
    text = NSAttributedString(string: “\(currentPageNo)”, attributes: textAttributes)

    text.draw(in: CGRect(x: Int(pageRect.width – 54), y: Int(pageRect.height – bottomMargin + 7), width: 54, height: Int(bottomMargin)))

    drawContext.strokePath()
    drawContext.restoreGState()
    currentPageNo += 1
    }
    func drawTableHeaderTitles( pageRect: CGRect) {
    drawContext.saveGState()
    drawContext.move(to: CGPoint(x: 100, y: 0 ))
    headerImage?.draw(in: CGRect(x: 0, y: 0, width: 50, height: 50))
    headerImage?.draw(in: CGRect(x: pageRect.width – 50, y: 0, width: 50, height: 50))
    drawContext.setFillColor(UIColor(hexString: “#F19A37”).cgColor)
    drawContext.move(to: CGPoint(x: 0, y: topMargin – 2))
    drawContext.addLine(to: CGPoint(x: pageRect.width, y: topMargin – 2))
    drawContext.strokePath()
    drawContext.restoreGState()

    }
    //paragraphStyle.alignment = NSTextAlignment.Justified

    fileprivate func drawAttributedText( _ attributedText: NSAttributedString,width:CGFloat, pageRect: CGRect,currentOffset: CGPoint) -> CGRect {

    var drawingYoffset = currentOffset.y

    let currentText = CFAttributedStringCreateCopy(nil, attributedText as CFAttributedString)
    let framesetter = CTFramesetterCreateWithAttributedString(currentText!)
    var currentRange = CFRange(location: 0, length: 0)
    var done = false

    var lastDrawnFrame: CGRect!

    repeat {
    drawContext.saveGState()

    drawContext.textMatrix = CGAffineTransform.identity

    let textMaxWidth = width – defaultOffset * 2
    let textMaxHeight = pageRect.height – (drawingYoffset + topMargin + 10) – bottomMargin – 10

    let frameRect = CGRect(x: currentOffset.x, y: drawingYoffset , width: textMaxWidth, height: textMaxHeight)
    let framePath = UIBezierPath(rect: frameRect).cgPath

    let frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, nil)

    drawContext.translateBy(x: 0, y: pageRect.height + drawingYoffset – bottomMargin)
    drawContext.scaleBy(x: 1.0, y: -1.0)

    // Draw the frame.
    CTFrameDraw(frameRef, drawContext)

    // Pop state
    drawContext.restoreGState()

    // Update the current range based on what was drawn.
    let visibleRange = CTFrameGetVisibleStringRange(frameRef)
    currentRange = CFRange(location: visibleRange.location + visibleRange.length , length: 0)

    // Update last drawn frame
    let constraintSize = CGSize(width: textMaxWidth, height: textMaxHeight)
    let drawnSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, visibleRange, nil, constraintSize, nil)
    lastDrawnFrame = CGRect(x: currentOffset.x, y: drawingYoffset, width: drawnSize.width, height: drawnSize.height)

    if currentRange.location == CFAttributedStringGetLength(currentText) {
    done = true
    // print(“exit”)
    } else {
    // begin a new page to draw text that is remaining.

    createPage(pageRect:pageRect)
    drawingYoffset = 0
    yPosition = 0
    // print(“begin a new page to draw text that is remaining”)
    }

    } while(!done)

    return lastDrawnFrame
    }
    func boldAttribute(size: CGFloat,color:UIColor) -> [NSAttributedString.Key : NSObject]{
    let textFont = UIFont.boldSystemFont(ofSize: size)
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.alignment = .left
    paragraphStyle.lineBreakMode = .byWordWrapping
    let textAttributes = [
    NSAttributedString.Key.paragraphStyle: paragraphStyle,
    NSAttributedString.Key.font: textFont,
    NSAttributedString.Key.foregroundColor : color
    ]
    return textAttributes
    }
    func normalAttribute() -> [NSAttributedString.Key : NSObject]{
    let textFont = UIFont.systemFont(ofSize: 10.0)
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.alignment = .left
    paragraphStyle.lineBreakMode = .byWordWrapping
    let textAttributes = [
    NSAttributedString.Key.paragraphStyle: paragraphStyle,
    NSAttributedString.Key.font: textFont
    // NSAttributedString.Key.foregroundColor : UIColor(hexString: “333333”)

    ]
    return textAttributes
    }
    func waterMark(pageRect: CGRect, context: CGContext) {

    // Draw rotated overlay string
    UIGraphicsPushContext(context)
    context.saveGState()
    let attributes: [NSAttributedString.Key: Any] = [
    NSAttributedString.Key.foregroundColor: #colorLiteral(red: 0.5899451484, green: 0.5899451484, blue: 0.5899451484, alpha: 0.2536386986),
    NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 64)
    ]
    drawVerticalText(text: “PDFKit app”, withAttributes: attributes, origin: CGPoint(x: 250, y: 450) , context: drawContext)

    context.restoreGState()
    UIGraphicsPopContext()

    }
    func drawVerticalText(text: String, withAttributes attributes: [NSAttributedString.Key: Any]?, origin: CGPoint, context: CGContext) {

    let halfRadian : CGFloat = -(CGFloat.pi / 5.0)

    context.rotate(by: halfRadian )

    let translatedOrigin = CGPoint(x: -origin.x, y: origin.y)

    // Draw the text.

    text.draw(at: translatedOrigin, withAttributes: attributes)

    context.rotate(by: -halfRadian )
    }
    func createPage(pageRect:CGRect){
    if currentPageNo != 1{
    waterMark(pageRect: pageRect, context: drawContext)
    }
    UIGraphicsBeginPDFPageWithInfo(pageRect, nil)
    footer(pageRect: pageRect)
    drawTableHeaderTitles( pageRect: pageRect)
    yPosition = 0
    }

    }

    extension NSAttributedString {

    func height(containerWidth: CGFloat) -> CGFloat {

    let rect = self.boundingRect(with: CGSize.init(width: containerWidth, height: CGFloat.greatestFiniteMagnitude),
    options: [.usesLineFragmentOrigin, .usesFontLeading],
    context: nil)
    return ceil(rect.size.height)
    }

    func width(containerHeight: CGFloat) -> CGFloat {

    let rect = self.boundingRect(with: CGSize.init(width: CGFloat.greatestFiniteMagnitude, height: containerHeight),
    options: [.usesLineFragmentOrigin, .usesFontLeading],
    context: nil)
    return ceil(rect.size.width)
    }
    }

    • Debabrata Roy (Moderator)
       let pdfCreator = PDFCreator(tableDataItems: “PDF data”, headerImage:UIImage(named:”qr-code”)!,title:”Custom Report For ” , topTitle: “CUSTOM REPORT”)
              if let vc = self.storyboard?.instantiateViewController(withIdentifier: “PDFViewerViewController”) as? PDFViewerViewController{
                  vc.fileName = “test.pdf”
                  vc.pdfData = pdfCreator.create()
                  self.navigationController?.pushViewController(vc, animated: true)
              }
      • Debabrata Roy (Moderator)
        import WebKit
        class PDFViewerViewController: UIViewController {
        var url:URL!
        var pdfData : Data!
        var pdfView: PDFView!
        var fileName = “”
        override func viewDidLoad() {
        super.viewDidLoad()
        pdfView = PDFView()
        pdfView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(pdfView)
        pdfView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor,constant: 0).isActive = true
        pdfView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        pdfView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        pdfView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
        pdfView.document = PDFDocument(data: pdfData)
        pdfView.autoScales = true
        }
        }
  • Björn
    Hi, thank you for the code.
    Can you tell me, how to implement the sum of pages like Page 1/4 – Page 2/4?
    Thank you
    • Debabrata Roy (Moderator)
      For this, you have to Edit in PDFDocument.
      Please checkout the below link you will get an idea.https://github.com/foliojs/pdfkit/issues/953#issuecomment-579578022
  • Start a Project


      Message Sent!

      If you have more details or questions, you can reply to the received confirmation email.

      Back to Home