How to Draw Something on a PDF in Swift

How do I draw something on a PDF in Swift?

So I've solved my own problem. For anyone else stuck like me, here is what I did. For example to draw a box on a page:

  1. create a CGContext (ctx) for PDFDocuments. You can do this either with data or with a URL you want to write to.
  2. create a CGPDFDocument with the document you want to edit
  3. get the CGPage of the CGPDF you want to edit (cgPage)

and:

ctx?.beginPDFPage(nil)
ctx?.drawPDFPage(cgPage)
ctx?.beginPath()
let path = CGPath(roundedRect: box as CGRect, cornerWidth: 5, cornerHeight: 5, transform: nil)
ctx?.setStrokeColor(CGColor.black)
ctx?.setFillColor(color.cgColor)
ctx?.setLineWidth(10.0)
ctx?.addPath(path)
ctx?.strokePath()
ctx?.addPath(path)
ctx?.fillPath()
ctx?.endPDFPage()
ctx?.closePDF()

(If you created the context with a URL, close will write to disk. Otherwise you'll have to do something with the PDF data.)

SWIFTUI: Draw on a PDF

Take a look at this tutorial: https://medium.com/better-programming/ios-pdfkit-ink-annotations-tutorial-4ba19b474dce

It is just a little bit hacky but it records the drawings directly as PDF Annotations that can be saved with the PDF. The PDF must not be secured in order to alter it.

PencilKit is very nice for the low latency drawing and built-in set of features. You would save the drawing separately as a PKDrawing which is pretty convenient. PKDrawing doesn't allow you to store bitmaps (text from a textbox for example).

Draw on a PDF using Swift on macOS

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:
input PDF

And here's the result from drawing that page into a PDF context, then drawing more text on top of it:

output PDF

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()

Swift PDFKit draw multiple images in a single PDFPage

You probably need to improve the positioning logic for the image in the loop, but this should point you to the right direction.

import UIKit
import PDFKit

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

let pdfView = PDFView()
pdfView.frame = CGRect(x: 0, y: 50, width: view.frame.width, height: view.frame.height - 50)
if let d = createPDFDocument() {
pdfView.document = d
}

view.addSubview(pdfView)
}

func createPDFDocument() -> PDFDocument? {
let pdfDocument = PDFDocument()
let page = PDFPage()
let bounds = page.bounds(for: .cropBox)
let imageRenderer = UIGraphicsImageRenderer(bounds: bounds, format: UIGraphicsImageRendererFormat.default())
let image = imageRenderer.image { (context) in
context.cgContext.saveGState()
context.cgContext.translateBy(x: 0, y: bounds.height)
context.cgContext.concatenate(CGAffineTransform.init(scaleX: 1, y: -1))
page.draw(with: .mediaBox, to: context.cgContext)
context.cgContext.restoreGState()

// Improve logic for image position
Range(1...4).forEach { value in
let image = UIImage(named: "YOUR_IMAGE_NAME")
let rect = CGRect(x: 50 * value, y: 0, width: 40, height: 100)
image?.draw(in: rect)
}
}

let newPage = PDFPage(image: image)!
pdfDocument.insert(newPage, at: 0)

return pdfDocument
}
}

Draw text on all pages of PDF using PDFKit

The main issue here is that context recreated, for multiple pages we should write into the same context (it manages pages by beginPDFPage/endPDFPage pair).

Here is fixed code. Tested with Xcode 13.4 / macOS 12.4

let pdffile = PDFDocument(url: input)
let data = NSMutableData()
let consumer = CGDataConsumer(data: data as CFMutableData)!

// create common context with no mediaBox, we will add it later
// per-page (because, actually they might be different)
let context = CGContext(consumer: consumer, mediaBox: nil, nil)!

for y in stride(from: 0, to: pdffile!.pageCount, by: 1)
{
let page: PDFPage = pdffile!.page(at: y)!

// re-use media box of original document as-is w/o changes !!
var mediaBox = page.bounds(for: PDFDisplayBox.mediaBox)
NSGraphicsContext.current = NSGraphicsContext(cgContext: context, flipped: false)

// prepare mediaBox data for page setup
let rectData = NSData(bytes: &mediaBox, length: MemoryLayout.size(ofValue: mediaBox))

context.beginPDFPage([kCGPDFContextMediaBox as String: rectData] as CFDictionary) // << here !!

page.draw(with: .mediaBox, to: context) // << original !!
text.draw(in:drawrect,withAttributes:textFontAttributes) // << over !!

context.endPDFPage()
}
context.closePDF() // close entire document

let anotherDocument = PDFDocument(data:data as Data)
// ... as used before

PDF Editor/ Draw on PDF in SwiftUI

I already found a solution. I used the PDFFreedraw Repo from ClassicalDude on GitHub. This is a well-implemented open source solution. I was then able to write a wrapper for swift UI like this.

https://github.com/ClassicalDude/pdfView-Freedraw

Example Screenshot

import Foundation
import SwiftUI
import UIKit
import PDFKit

struct PDFKitEditorView: View {
var url: URL
var body: some View {
PDFKitEditorRepresentedView(url)
.ignoresSafeArea(.all)
.navigationBarHidden(true)
}
}

struct PDFKitEditorRepresentedView: UIViewControllerRepresentable {

let url: URL

init(_ url: URL) {
self.url = url
}

func makeUIViewController(context: Context) -> UIViewController {

let storyboard = UIStoryboard(name: "MainPDFView", bundle: Bundle.main)
let controller = storyboard.instantiateViewController(withIdentifier: "PDFViewControllerID") as? PDFViewController
controller!.url = self.url

return controller!
}

func updateUIViewController(_ uiViewController: UIViewController, context: Context) {

}


}


Related Topics



Leave a reply



Submit