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:
- create a CGContext (ctx) for PDFDocuments. You can do this either with data or with a URL you want to write to.
- create a CGPDFDocument with the document you want to edit
- 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:
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()
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
Swift Pattern Match on Array<Any>
Nsattributedstring Boundingrect Returns Wrong Height
Playing Multiple Wav Out Multiple Channels Avaudioengine
Protocol Variable Implementing Another Protocol
Disable Bringing App Window to Front. After Closing Another Window
Swift: How to Continuously Send an Action from a Nstextfield
How Pass Data from Button in Tableviewcell to View Controller
Data Structure for Fast Lookup with Multiple Criteria
Cannot Use Mutating Member on Immutable Value of Type 'string'
Double Variable in Mkmapitem Array
How to Programmatically Scroll iOS Wkwebview, Swift 4
How to Get The Range of The First Line in a String
Restrictions Around Protocols and Generics in Swift
Swift: How to Get Image Name from Assets
Swiching Between 2 Diferent Nsviewcontrollers with Data
Wkwebview on Macos Cuts Off Top
Moving from Nsurlconnection to Nsurlsession for Soap Post in Swift