I'm using PDFKit to create a PDF File.
PDF document is correctly created because if I try to view it with PDFView, I see it (@IBAction func DetailButton) May be not... I don't see the square !
The issue is that I can't share it with UIActivityViewController. (@IBAction func ShareButton)
I share a text file which is the file path. /var/mobile/Containers/Data/Application/....../Documents/1234.pdf
Moreover, how could I send a default email address when I share to 'Mail', and a phone number when I share to 'Message' ?
func createPDFwithPDFKit(filePath:String, share:Bool) {
let pdfTitle = "Swift-Generated PDF"
let pdfMetadata = [
// The name of the application creating the PDF.
kCGPDFContextCreator: "XXX",
// The name of the PDF's author.
kCGPDFContextAuthor: "xxx",
// The title of the PDF.
kCGPDFContextTitle: game!.name,
// Encrypts the document with the value as the owner password. Used to enable/disable different permissions.
kCGPDFContextOwnerPassword: "myPassword123"
]
// Creates a new PDF file at the specified path.
UIGraphicsBeginPDFContextToFile(filePath, CGRect.zero, pdfMetadata)
// Creates a new page in the current PDF context.
UIGraphicsBeginPDFPage()
// Default size of the page is 612x72.
let pageSize = UIGraphicsGetPDFContextBounds().size
let font = UIFont.preferredFont(forTextStyle: .largeTitle)
// Let's draw the title of the PDF on top of the page.
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)
// Closes the current PDF context and ends writing to the file.
UIGraphicsEndPDFContext()
if share {
let vc = UIActivityViewController(activityItems: [filePath], applicationActivities: [])
vc.excludedActivityTypes = [
UIActivity.ActivityType.assignToContact,
UIActivity.ActivityType.saveToCameraRoll,
UIActivity.ActivityType.postToFlickr,
UIActivity.ActivityType.postToVimeo,
UIActivity.ActivityType.postToTencentWeibo,
UIActivity.ActivityType.postToTwitter,
UIActivity.ActivityType.postToFacebook,
UIActivity.ActivityType.openInIBooks
]
present(vc, animated: true, completion: nil)
}
}
@IBAction func ShareButton(_ sender: UIBarButtonItem) {
alert.addAction(UIAlertAction(title: "Exporter au format PDF", style: .default, handler: { _ in
let fileName = "1234.pdf"
let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filePath = (documentsDirectory as NSString).appendingPathComponent(fileName) as String
self.createPDFwithPDFKit(filePath:filePath, share:true)
}))
alert.addAction(UIAlertAction.init(title: "Annuler", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
@IBAction func DetailButton(_ sender: UIBarButtonItem) {
// Create and add a PDFView to the view hierarchy.
let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filePath = (documentsDirectory as NSString).appendingPathComponent("scores de '" + game!.name + "'.pdf") as String
self.createPDFwithPDFKit(filePath:filePath, share:false)
//View the PDF
let pdfView = PDFView(frame: view.bounds)
pdfView.autoScales = true
pdfView.displayMode = .singlePageContinuous
view.addSubview(pdfView)
// Create a PDFDocument object and set it as PDFView's document to load the document in that view.
let pdfDocument = PDFDocument(url: URL(fileURLWithPath: filePath))!
pdfView.document = pdfDocument
}
In my own use of PDFKit I've created the PDF text as an instance of NSMutableAttributedString (as you do), but then encapsulate that in an instance of UISimpleTextPrintFormatter. This object handles formatting a document, especially when there is a need for page breaks. This instance is used to initialize an instance of UIPrintPageRenderer. That object "knows" about page sizes, the printable area within the page, and the print formatter. Once you've invoked UIGraphicsBeginPDFContextToData you can call on these objects to render your PDF on a page-by-page basis.
Obviously there is a lot going on here, so I wrote up a trivial app to test it. The scene has two controls, a system-style "share" button (in the navigation bar) and an instance of UIView that covers the entire safe area and is declared to be of type "PDFView". The PDF only contains text: "This is text in a PDF file.". The following view controller logic provides an example of how to display the PDF (in the viewDidLoad
method) and how to get the sharing accomplished (in the activityButton
action):
import UIKit
import PDFKit
class PDFViewController: UIViewController {
// MARK: iVars
private var reportBuffer = NSMutableData()
// MARK: outlets and actions
@IBOutlet weak var pdfReportView: PDFView!
@IBAction func activityButton(_ sender: Any)
{
let activityViewController =
getActivityViewController()
self.present( activityViewController,
animated: true,
completion: nil )
}
// MARK: - Action convenience functions
private func getActivityViewController() -> UIActivityViewController
{
let manager = FileManager.default
let path = pdfURL.path
if manager.fileExists(atPath: path ) {
do {
try manager.removeItem(at: pdfURL )
} catch {
fatalError()
}
}
do {
try reportBuffer.write(to: pdfURL, options: [.atomic])
} catch {
fatalError()
}
let activityVC =
UIActivityViewController( activityItems: [ pdfURL ],
applicationActivities: nil)
activityVC.completionWithItemsHandler =
{
[weak self] activity, wasCompleted, returnedItems, error in
if manager.fileExists(atPath: path ) {
do {
try manager.removeItem(at: self!.pdfURL )
} catch {
fatalError()
}
}
}
activityVC.popoverPresentationController?.sourceView =
self.pdfReportView
activityVC.modalPresentationStyle = .popover
return activityVC
}
// MARK: lifecycle
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear( animated )
// create PDF content
let report =
NSMutableAttributedString(string: "This is text in a PDF file." )
// create renderer
let renderer = UIPrintPageRenderer()
renderer.setValue( NSValue( cgRect: paperRect ),
forKey: "paperRect" )
renderer.setValue( NSValue( cgRect: printableRect ),
forKey: "printableRect" )
let printFormatter =
UISimpleTextPrintFormatter( attributedText: report )
renderer.addPrintFormatter(printFormatter,
startingAtPageAt: 0)
// draw the PDF into an NSMutableData buffer
UIGraphicsBeginPDFContextToData( reportBuffer,
paperRect,
nil )
let pageRange = NSMakeRange( 0, renderer.numberOfPages )
renderer.prepare( forDrawingPages: pageRange )
let bounds = UIGraphicsGetPDFContextBounds()
for i in 0 ..< renderer.numberOfPages {
UIGraphicsBeginPDFPage()
renderer.drawPage( at: i, in: bounds )
}
UIGraphicsEndPDFContext()
// display the PDF
if let document =
PDFDocument( data: reportBuffer as Data )
{
pdfReportView.document = document
}
}
// MARK: - constants
private struct Page
{
static let size =
CGSize( width: 612.0, height: 792.0 )
static let margins =
UIEdgeInsets(top: 72.0,
left: 72.0,
bottom: 72.0,
right: 72.0)
}
private let printableRect =
CGRect(x: Page.margins.left,
y: Page.margins.top,
width: Page.size.width
- Page.margins.left
- Page.margins.right,
height: Page.size.height
- Page.margins.top
- Page.margins.bottom)
private let paperRect =
CGRect( x: 0.0,
y: 0.0,
width: Page.size.width,
height: Page.size.height )
private var pdfURL: URL {
let manager = FileManager.default
let url = manager.urls(for: .cachesDirectory,
in: .userDomainMask).first!
return url.appendingPathComponent("temporary.pdf") as URL
}
}
Side note: I was a little confused by the logic of your code. You use a "ShareButton" to present an alert, which in turn has a button used to invoke an instance of UIActivityViewController. Your business requirements may dictate this approach, but in general users will find this to be unexpectedly complicated. Tapping a share button (the system icon is a square with a protruding up-arrow) ordinarily invokes an instance of UIActivityViewController directly. That is, without an intervening alert.
EDIT: I was only able to get Mail and Message sharing to work on a real device. None of the Simulator devices offered either Mail or Message options on the UIActivityViewController popover (in spite of the fact that Message was available, at least on the iPad Air simulator).