How to detect the orientation of a PDF page in Swift with PDFKit IOS - ios

I'm trying to get the orientation property of a PDF document. The purpose is, I would like to add a button widget in a location that depends on the orientation of the PDF document.
For example:
func openPDFDocument() {
if let documentURL = Bundle.main.url(forResource: "PDF document", withExtension: "pdf"),
let document = PDFDocument(url: documentURL),
let page = document.page(at: 0) {
// Set our document to the view, center it, and set a background color
pdfView?.document = document
pdfView?.autoScales = true
pdfView?.backgroundColor = UIColor.lightGray
//I think I should be able to add a code here like:
if page.orientation = Horizontal {
self.insertResetButtonInto(page)
} else {
//do nothing or do something else
}
}
}
This is the function I would like to add in case the document is in Landscape mode:
func insertResetButtonInto(_ page: PDFPage) {
let pageBounds = page.bounds(for: .cropBox)
let resetButtonBounds = CGRect(x: 90, y: pageBounds.size.height - 300, width: 106, height: 32)
let resetButton = PDFAnnotation(bounds: resetButtonBounds, forType: PDFAnnotationSubtype(rawValue: PDFAnnotationSubtype.widget.rawValue), withProperties: nil)
resetButton.widgetFieldType = PDFAnnotationWidgetSubtype(rawValue: PDFAnnotationWidgetSubtype.button.rawValue)
resetButton.widgetControlType = .pushButtonControl
resetButton.caption = "Reset"
page.addAnnotation(resetButton)
// Create PDFActionResetForm action to clear form fields.
let resetFormAction = PDFActionResetForm()
resetFormAction.fieldsIncludedAreCleared = false
resetButton.action = resetFormAction
}
I got the example project from Apple's documentation website. I looked at a previous similar question, however it seems this was in Objective C.
I would appreciate the help in this matter.

There is no direct API to get the orientation from a PDFPage. But you can first get the page size from .mediaBox, then calculate the orientation like below.
let pageSize = page.bounds(for: .mediaBox).size
if pageSize.width > pageSize.height {
//landscape
} else {
//portrait
}

I use another way to get the orientation of my pdf page.
func IsLandscape(page: PDFPage) -> Bool {
let pointZero = pdfView.convert(CGPoint(x: 0, y: 0), from: page)
let pointTen = pdfView.convert(CGPoint(x: 10, y: 10), from: page)
let caculate = pointTen.x - pointZero.x
print("pointZero: \(pointZero), pointTen:\(pointTen)")
if (caculate > 0) {
print("landscape")
return true
}
else {
print("portrait")
return false
}
}

Related

Inserting an Image into a PDF with Swift

I want insert company logo image into pdf of every page. So we are referring following site
Tutorial Link
I want to insert logo right bottom page of pdf, I am unable do to that process.
Please find the tried both way code:
let documentURL = url
// Create a `CGPDFDocument` object for accessing the PDF pages.
// We need these pages in order to draw the original/existing content, because `UIGraphicsBeginPDFContextToFile` creates a file with a clean slate.
// We will have the original file contents in memory as long as the `CGPDFDocument` object is around, even after we have started rewriting the file at the path.
guard let originalDocument = CGPDFDocument(documentURL as CFURL) else {
print("Unable to create read document.")
return
}
// Create a new PDF at the same path to draw the contents into.
UIGraphicsBeginPDFContextToFile(documentURL.path, CGRect.zero, nil)
let image = UIImage(named: "inactive")!
guard let pdfContext = UIGraphicsGetCurrentContext() else {
print("Unable to access PDF Context.")
return
}
let pageSize = UIGraphicsGetPDFContextBounds().size
for pageIndex in 0..<originalDocument.numberOfPages {
// Mark the beginning of the page.
pdfContext.beginPDFPage(nil)
// Pages are numbered starting from 1.
// Access the `CGPDFPage` object with the original contents.
guard let currentPage = originalDocument.page(at: pageIndex + 1) else {
return
}
// Draw the existing page contents.
pdfContext.drawPDFPage(currentPage)
// Save the context state to restore after we are done drawing the image.
pdfContext.saveGState()
// Change the PDF context to match the UIKit coordinate system.
pdfContext.translateBy(x: 0, y: pageSize.height)
pdfContext.scaleBy(x: 1, y: -1)
// Location of the image to be drawn in UIKit coordinates.
let imagePosition = CGRect(x: 100, y: 0, width: 50, height: 50)
image.draw(in: imagePosition)
// UIColor.orange.set()
// UIRectFill(CGRect(x: 100, y: 0, width: 50, height: 50))
// Restoring the context back to its original state.
pdfContext.restoreGState()
// Mark the end of the current page.
pdfContext.endPDFPage()
}
// End the PDF context, essentially closing the PDF document context.
UIGraphicsEndPDFContext()
Updated Code:
let documentURL = url
guard let originalDocument = CGPDFDocument(documentURL as CFURL) else {
print("Unable to create read document.")
return
}
UIGraphicsBeginPDFContextToFile(documentURL.path, CGRect.zero, nil)
// let image = UIImage(named: "verified_kuwy")
let image = UIImage(named: "watermark")
guard let pdfContext = UIGraphicsGetCurrentContext() else {
print("Unable to access PDF Context.")
return
}
let pageSize = UIGraphicsGetPDFContextBounds().size
for pageIndex in 0..<originalDocument.numberOfPages {
pdfContext.beginPDFPage(nil)
guard let currentPage = originalDocument.page(at: pageIndex + 1) else {
return
}
pdfContext.drawPDFPage(currentPage)
pdfContext.saveGState()
pdfContext.translateBy(x: 0, y: pageSize.height)
pdfContext.scaleBy(x: 1, y: -1)
let imagePosition = CGRect(x: pageSize.width - 150, y: pageSize.height - 150, width: 100, height: 100)
image!.draw(in: imagePosition)
pdfContext.restoreGState()
pdfContext.endPDFPage()
}
// End the PDF context, essentially closing the PDF document context.
UIGraphicsEndPDFContext()

rotationAngle data is lost when creating a CGPDFPage

I have this PDF file with me. I use following code to render the first page of this PDF file.
let page = self.pdfDocument.page(at: 0 )! // get first page
let rotationAngle = page.rotation // this value is 90
let renderedPdfData = renderPDF(page: page.pageRef!)
where renderPDF method is,
func renderPDF(page: CGPDFPage) -> Data {
let pageRect = page.getBoxRect(.mediaBox)
let renderer = UIGraphicsPDFRenderer(bounds: pageRect)
return renderer.pdfData { (context) in
context.beginPage()
context.cgContext.setFillColor(UIColor.white.cgColor)
context.fill(pageRect)
context.cgContext.translateBy(x: 0.0, y: pageRect.size.height)
context.cgContext.scaleBy(x: 1, y: -1)
context.cgContext.drawPDFPage(page)
}
}
Then later I want to create a CGPDFPage back with above renderedPdfData . For that I use following method.
var newPage: CGPDFPage = createPDFPage(pdfData: renderedPdfData)
where createPDFPage method is,
func createPDFPage(pdfData: Data) -> CGPDFPage? {
guard let provider = CGDataProvider(data: pdfData as CFData) else {
return nil
}
guard let document = CGPDFDocument(provider) else {
return nil
}
// page starting at 1. The input PDF data only has 1 page.
return document.page(at: 1)
}
But how should I set newPage's rotation angle data (ie. 90)? Because 'rotationAngle' is a get-only property. Since I haven't set a value for it, it's value is always 0.
let angle = newPage.rotationAngle // value is 0
Is there a way I can create a CGPDFPage with rotation information available with it?

Swift iOS - Overlay text onto PDF with PDFKit and UI

I'm working in Swift 5 and on iOS. I'm trying to overlay text onto a current PDF I have. I'm essentially porting code I made from an app for macOS. This is the code from the Mac version:
func executeContext(at srcURL: URL, to dstURL: URL) {
// Confirm there is a document there
if let doc: PDFDocument = PDFDocument(url: srcURL) {
// Create a document, get the first page, and set the size of the page
let page: PDFPage = doc.page(at: 0)!
var mediaBox: CGRect = CGRect(x: 0, y: 0, width: 792, height: 612)
// This is where the magic happens. Create the drawing context on the PDF
let context = CGContext(dstURL as CFURL, mediaBox: &mediaBox, nil)
let graphicsContext = NSGraphicsContext(cgContext: context!, flipped: false)
NSGraphicsContext.current = graphicsContext
context!.beginPDFPage(nil)
// Draws the PDF into the context
page.draw(with: .mediaBox, to: context!)
// Parse and Draw Text on the context
//drawText()
let attributes = [
NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 72)
]
let text = "I'm a PDF!"
text.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
context!.saveGState()
context!.restoreGState()
context!.endPDFPage()
NSGraphicsContext.current = nil
context?.closePDF()
}
}
The drawText() function did most of the text overlaying that was needed, but I put another "draw "method below it to test it out.
I'm understandably getting an error Cannot find 'NSGraphicsContext' in scope since NSGraphicsContext doesn't exist on iOS. I've tried to find an equivalent translation with UIGraphicsPDFRenderer or UIGraphicsBeginPDFContextToData, and using some code from a Ray Wenderlich tutorial, I was able to create a new PDF and place text on it with the below code:
func createDocument(url: URL) -> Data {
//let pdfData = try? Data.init(contentsOf: url)
// 1
let pdfMetaData = [
kCGPDFContextCreator: "Timecard App",
kCGPDFContextAuthor: "Timecard App"
]
let format = UIGraphicsPDFRendererFormat()
format.documentInfo = pdfMetaData as [String: Any]
// 2
let pageWidth = 8.5 * 72.0
let pageHeight = 11 * 72.0
let pageRect = CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
// 3
let renderer = UIGraphicsPDFRenderer(bounds: pageRect, format: format)
// 4
let data = renderer.pdfData { (context) in
// 5
context.beginPage()
// 6
let attributes = [
NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 72)
]
let text = "I'm a PDF!"
text.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
}
return data
}
...but I couldn't find a way to load in current PDF "data" to the renderer and then draw from there. Does anyone have any suggestions on the proper way to do this?
Here is possible solution - actually you just need to operate with CoreGraphics context directly, set current, flip transform, etc. (style and conventions of original code preserved).
Tested with Xcode 12 / iOS 14.
func executeContext(at srcURL: URL, to dstURL: URL) {
// Confirm there is a document there
if let doc: PDFDocument = PDFDocument(url: srcURL) {
// Create a document, get the first page, and set the size of the page
let page: PDFPage = doc.page(at: 0)!
var mediaBox: CGRect = page.bounds(for: .mediaBox)
// This is where the magic happens. Create the drawing context on the PDF
let context = CGContext(dstURL as CFURL, mediaBox: &mediaBox, nil)
UIGraphicsPushContext(context!)
context!.beginPDFPage(nil)
// Draws the PDF into the context
page.draw(with: .mediaBox, to: context!)
let flipVertical: CGAffineTransform = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: mediaBox.size.height)
context!.concatenate(flipVertical)
let attributes = [
NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 72)
]
let text = "I'm a PDF!"
text.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
context!.endPDFPage()
context?.closePDF()
UIGraphicsPopContext()
}
}
Edit additional pages using the following function
// add a new page
func addPage(number: Int){
// index is one less than document page number
let index = number - 1
context.endPDFPage()
if let page = document.page(at: index) {
context.beginPDFPage(nil)
page.draw(with: .mediaBox, to: context)
context.concatenate(flipVertical)
}
}
Where document is the PDF document you wish to edit.
Then start editing that new page. The X and Y coordinates reset to 0,0 again for the new page.

iOS : Add a UIView returned by Braintree to the screen

I'm following Braintree's guidelines to add the dropIn functionality to an iOS app. In order to show the dropIn I use the following method:
func showDropIn(clientTokenOrTokenizationKey: String) {
let request = BTDropInRequest()
let dropIn = BTDropInController(authorization: clientTokenOrTokenizationKey, request: request)
{ (controller, result, error) in
if (error != nil) {
print("ERROR")
} else if (result?.isCancelled == true) {
print("CANCELLED")
} else if let result = result {
let paymentMethod = result.paymentMethod!.nonce
self.paymentNonce = paymentMethod
self.paymentLabel.text = result.paymentDescription
print(result.paymentIcon)
}
controller.dismiss(animated: true, completion: nil)
}
self.present(dropIn!, animated: true, completion: nil)
}
What I want to do after the user picks a specific form of payment is to show both the description ("ending in XX") and the card's icon on the screen. I have no problem with the description, but I don't know how to get the card icon to show.
The following line:
print(result.paymentIcon)
returns the following:
<BTUIKMasterCardVectorArtView: 0x7fd04ad15280; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x600000230c20>>
According to documentation, result.paymentIcon is a UIView.
#property(readonly, nonatomic) UIView *_Nonnull paymentIcon;
A UIView (BTUIKPaymentOptionCardView) that represents the payment option
I have tried setting up a UIView and then adding the paymentIcon as a subview, but still I can never get it to show.
let paymentIcon = result.paymentIcon
self.iconView.addSubview(paymentIcon)
You can use the BTUIKViewUtil.vectorArtView method to retrieve a image and show in UIImageView, like so:
let size = CGSize(width: WIDTH_HERE, height: HEIGHT_HERE)
let imageView = UIImageView(frame: CGRect(origin: .zero, size: size))
imageView.image = BTUIKViewUtil.vectorArtView(for: result.paymentOptionType).image(of: size)
Example-2, using paymentIcon view:
let view = result.paymentIcon as? BTUIKVectorArtView
imageView.image = view?.image(of: size)

PdfKit highlight annotation

I'm trying to add highlight annotations to documents using PDFKit on iOS.
let highlight = PDFAnnotation(bounds: selection.bounds(for: page),
forType: PDFAnnotationSubtype.highlight,
withProperties: nil)
highlight.color = color
page.addAnnotation(highlight)
page.displaysAnnotations = true
When adding them using the code above, they appear as two differently shaped layers. When saving them to the PDF file and reopening it they display correctly.
In this screen capture
The top and bottom highlights have been added the same way using the snippet provided here. The top one has been saved to the pdf document and appears as expected when reopening it, the bottom one has just been added.
Does anyone know how to have them display correctly (i.e. like the top one) without resorting to saving and reopening the file?
So this is a know bug in 10.13. There is a workaround by scrolling the page away and then back to the highlight
You can create the highlight using this code:
let page = self.pdfDocument?.page(at: 10)
let bounds = CGRect(x: 85.8660965, y: 786.8891167, width: 298.41, height: 12.1485)
let annotation = PDFAnnotation(bounds: bounds, forType: .highlight, withProperties: nil)
annotation.color = NSColor.blue
page?.addAnnotation(annotation)
Then you need to scroll away from the page and back to the highlight
func annotationScrollHack(page: PDFPage) {
guard let pdfDocument = self.pdfDocument else { return }
//When adding highlights to macOS 10.13 it seems like 2 highlights are added.
//If you scroll to a different page and back the "extra" highlight is removed
//This function scrolls to the first/last page in the book and then back to the current page
//rdar://34784917
let bookScrollView = self.pdfView.documentView?.enclosingScrollView
let currentVisibleRect = bookScrollView?.contentView.documentVisibleRect
if (0 ... 3).contains(pdfDocument.index(for: page)) {
if self.pdfView.canGoToLastPage {
self.pdfView.goToLastPage(self)
}
} else {
if self.pdfView.canGoToFirstPage {
self.pdfView.goToFirstPage(self)
}
}
if let currentVisibleRect = currentVisibleRect {
bookScrollView?.contentView.scroll(to: CGPoint(x: currentVisibleRect.origin.x, y: currentVisibleRect.origin.y))
}
}
Response from Apple:
There is no workaround other than the “hack" they described: scrolling
away then back is the best option. Changing zoom factor and reverting
it might fix it in the same way, but not guaranteed.
I've ended up with this hack method which takes into consideration all cases in my opinion. Hope that will help.
private func hackScroll(to page: PDFPage) {
switch (pdfView.canGoToFirstPage(), pdfView.canGoToLastPage()) {
case (true, false):
pdfView.goToFirstPage(nil)
pdfView.go(to: page)
case (false, true):
pdfView.goToLastPage(nil)
pdfView.go(to: page)
case (false, false):
guard let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let file = "temp.pdf"
/// Save to temp file
let fileURL = dir.appendingPathComponent(file)
pdfView.document?.write(to: fileURL)
/// Read from temp file
guard let document = PDFDocument(url: fileURL) else { return }
pdfView.document = document
pdfView.autoScales = true
default:
pdfView.goToFirstPage(nil)
pdfView.go(to: page)
}
}

Resources