使用 PDFKit 创建 PDF 文档时的共享问题
Share issue when using PDFKit to create a PDF document
我正在使用 PDFKit 创建 PDF 文件。
PDF 文档已正确创建,因为如果我尝试使用 PDFView 查看它,我会看到它 (@IBAction func DetailButton)
可能不是...我没看到正方形!
问题是我无法与 UIActivityViewController 共享它。 (@IBAction func ShareButton)
我分享了一个文本文件,就是文件路径。
/var/mobile/Containers/Data/Application/....../Documents/1234.pdf
此外,当我分享到 'Mail' 时如何发送一个默认的电子邮件地址,当我分享到 'Message' 时如何发送一个 phone 号码?
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
}
在我自己使用 PDFKit 的过程中,我将 PDF 文本创建为 NSMutableAttributedString 的实例(就像您所做的那样),然后将其封装在 UISimpleTextPrintFormatter 的实例中。此对象处理文档的格式设置,尤其是在需要分页符时。此实例用于初始化 UIPrintPageRenderer 的实例。该对象 "knows" 关于页面大小、页面内的可打印区域和打印格式化程序。调用 UIGraphicsBeginPDFContextToData 后,您可以调用这些对象以逐页呈现 PDF。
显然这里发生了很多事情,所以我写了一个简单的应用程序来测试它。该场景有两个控件,一个系统样式的 "share" 按钮(在导航栏中)和一个覆盖整个安全区域并声明为 "PDFView" 类型的 UIView 实例。 PDF 仅包含文本:"This is text in a PDF file."。以下视图控制器逻辑提供了如何显示 PDF(在 viewDidLoad
方法中)以及如何完成共享(在 activityButton
操作中)的示例:
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
}
}
旁注:我对你的代码逻辑有点困惑。您使用 "ShareButton" 来显示警报,而警报又具有用于调用 UIActivityViewController 实例的按钮。您的业务需求可能会决定采用这种方法,但通常用户会发现这种方法出乎意料地复杂。点击共享按钮(系统图标是带有向上突出箭头的正方形)通常会直接调用 UIActivityViewController 的实例。也就是说,没有干预警报。
编辑: 我只能在真实设备上使用邮件和消息共享。 None 的模拟器设备在 UIActivityViewController 弹出窗口上提供了 Mail 或 Message 选项(尽管 Message 可用,至少在 iPad Air 模拟器上是这样)。
我正在使用 PDFKit 创建 PDF 文件。
PDF 文档已正确创建,因为如果我尝试使用 PDFView 查看它,我会看到它 (@IBAction func DetailButton) 可能不是...我没看到正方形!
问题是我无法与 UIActivityViewController 共享它。 (@IBAction func ShareButton)
我分享了一个文本文件,就是文件路径。 /var/mobile/Containers/Data/Application/....../Documents/1234.pdf
此外,当我分享到 'Mail' 时如何发送一个默认的电子邮件地址,当我分享到 'Message' 时如何发送一个 phone 号码?
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
}
在我自己使用 PDFKit 的过程中,我将 PDF 文本创建为 NSMutableAttributedString 的实例(就像您所做的那样),然后将其封装在 UISimpleTextPrintFormatter 的实例中。此对象处理文档的格式设置,尤其是在需要分页符时。此实例用于初始化 UIPrintPageRenderer 的实例。该对象 "knows" 关于页面大小、页面内的可打印区域和打印格式化程序。调用 UIGraphicsBeginPDFContextToData 后,您可以调用这些对象以逐页呈现 PDF。
显然这里发生了很多事情,所以我写了一个简单的应用程序来测试它。该场景有两个控件,一个系统样式的 "share" 按钮(在导航栏中)和一个覆盖整个安全区域并声明为 "PDFView" 类型的 UIView 实例。 PDF 仅包含文本:"This is text in a PDF file."。以下视图控制器逻辑提供了如何显示 PDF(在 viewDidLoad
方法中)以及如何完成共享(在 activityButton
操作中)的示例:
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
}
}
旁注:我对你的代码逻辑有点困惑。您使用 "ShareButton" 来显示警报,而警报又具有用于调用 UIActivityViewController 实例的按钮。您的业务需求可能会决定采用这种方法,但通常用户会发现这种方法出乎意料地复杂。点击共享按钮(系统图标是带有向上突出箭头的正方形)通常会直接调用 UIActivityViewController 的实例。也就是说,没有干预警报。
编辑: 我只能在真实设备上使用邮件和消息共享。 None 的模拟器设备在 UIActivityViewController 弹出窗口上提供了 Mail 或 Message 选项(尽管 Message 可用,至少在 iPad Air 模拟器上是这样)。